SmartObject
A SmartObject korábban sokféleképpen javította az objektumok viselkedését, de a mai PHP már natívan tartalmazza ezen fejlesztések nagy részét. Azonban még mindig hozzáadja a tulajdonságok támogatását.
Telepítés:
composer require nette/utils
Tulajdonságok, Getterek és Setterek
A modern objektumorientált nyelvekben (pl. C#, Python, Ruby, JavaScript) a tulajdonság kifejezés az osztályok speciális tagjaira utal, amelyek változóknak tűnnek, de valójában metódusok képviselik őket. Amikor ennek a “változónak” az értékét hozzárendeljük vagy kiolvassuk, a megfelelő metódust (az úgynevezett gettert vagy settert) hívjuk meg. Ez egy nagyon praktikus dolog, teljes kontrollt biztosít számunkra a változókhoz való hozzáférés felett. Csak akkor tudjuk érvényesíteni a bemenetet, vagy csak akkor generálhatunk eredményt, ha a tulajdonságot kiolvassuk.
A PHP tulajdonságok nem támogatottak, de a Nette\SmartObject
trait képes utánozni őket. Hogyan
használjuk?
- Adjunk hozzá egy megjegyzést az osztályhoz a következő formában
@property <type> $xyz
- Hozzon létre egy
getXyz()
vagyisXyz()
nevű gettert, egy settert, amelynek a nevesetXyz()
- A getter és a setter nyilvános vagy védett kell, hogy legyen, és opcionális, tehát lehet read-only vagy write-only tulajdonság.
A Circle osztály tulajdonságát fogjuk használni annak biztosítására, hogy a $radius
változóba csak nem
negatív számok kerüljenek. Helyettesítsük a public $radius
címet a tulajdonsággal:
/**
* @property float $radius
* @property-read bool $visible
*/
class Circle
{
use Nette\SmartObject;
private float $radius = 0.0; // nem nyilvános
// getter a $radius tulajdonsághoz
protected function getRadius(): float
{
return $this->radius;
}
// setter a $radius tulajdonsághoz
protected function setRadius(float $radius): void
{
// az érték mentés előtti szanálása
$this->radius = max(0.0, $radius);
}
// getter a $visible tulajdonsághoz
protected function isVisible(): bool
{
return $this->radius > 0;
}
}
$circle = new Circle;
$circle->radius = 10; // valójában a setRadius(10) hívása.
echo $circle->radius; // meghívja a getRadius() függvényt.
echo $circle->visible; // hívja az isVisible() függvényt
A tulajdonságok elsősorban szintaktikai cukor, amelynek célja, hogy a kód egyszerűsítésével megédesítse a programozó életét. Ha nem akarod őket, nem kell használni őket.
Pillantás a történelembe
A SmartObject korábban számos módon finomította az objektumok viselkedését, de a mai PHP már natívan tartalmazza ezen fejlesztések nagy részét. A következő szöveg egy nosztalgikus visszatekintés a történelembe, emlékeztetve minket a dolgok fejlődésére.
A PHP objektummodellje a kezdetektől fogva számtalan komoly hiányosságtól és hiányosságtól szenvedett. Ez vezetett a
Nette\Object
osztály létrehozásához (2007-ben), amelynek célja ezen problémák orvoslása és a PHP
használatának kényelmesebbé tétele volt. Mindössze arra volt szükség, hogy más osztályok is örököljenek belőle, és
máris élvezhették az általa kínált előnyöket. Amikor a PHP 5.4 bevezette a tulajdonságok támogatását, a
Nette\Object
osztály helyébe a Nette\SmartObject
tulajdonság lépett. Ezzel megszűnt a közös
őstől való öröklés szükségessége. Sőt, a tulajdonságot olyan osztályokban is lehetett használni, amelyek már
örököltek egy másik osztálytól. A Nette\Object
végleges megszűnését a PHP 7.2 kiadása jelentette, amely
megtiltotta, hogy az osztályok neve Object
legyen.
A PHP fejlesztésének előrehaladtával az objektummodell és a nyelvi képességek tovább fejlődtek. A
SmartObject
osztály különböző funkciói feleslegessé váltak. A PHP 8.2 kiadása óta csak egyetlen olyan
funkció maradt, amelyet a PHP közvetlenül nem támogat: az úgynevezett tulajdonságok használatának lehetősége.
Milyen funkciókat kínált a Nette\Object
és ennek folytán a Nette\SmartObject
? Íme egy
áttekintés. (A példákban a Nette\Object
osztályt használjuk, de a legtöbb funkció a
Nette\SmartObject
tulajdonságra is vonatkozik).
Inkonzisztens hibák
A PHP következetlenül viselkedett a nem deklarált tagok elérésekor. Az állapot a Nette\Object
idején a
következő volt:
echo $obj->undeclared; // E_NOTICE, később E_WARNING
$obj->undeclared = 1; // csendben átmegy jelentés nélkül.
$obj->unknownMethod(); // Végzetes hiba (try/catch-el nem fogható)
A végzetes hiba mindenféle reagálási lehetőség nélkül megszakította az alkalmazást. A nem létező tagokba való
csendes írás figyelmeztetés nélkül súlyos, nehezen észlelhető hibákhoz vezethetett. Nette\Object
Minden
ilyen esetet elkaptunk, és a MemberAccessException
címen egy kivételt dobtunk.
echo $obj->undeclared; // throw Nette\MemberAccessException
$obj->undeclared = 1; // throw Nette\MemberAccessException
$obj->unknownMethod(); // throw Nette\MemberAccessException
A PHP 7.0 óta a PHP már nem okoz nem fogható halálos hibákat, a nem deklarált tagokhoz való hozzáférés pedig a PHP 8.2 óta hiba.
Úgy értetted?
Ha egy Nette\MemberAccessException
hiba lépett fel, például egy objektumváltozó elérésekor vagy egy
metódus hívásakor elírás miatt, a Nette\Object
megpróbált a hibaüzenetben egy tippet adni a hiba
kijavítására, az ikonikus “did you mean?” kiegészítés formájában.
class Foo extends Nette\Object
{
public static function from($var)
{
}
}
$foo = Foo::form($var);
// throw Nette\MemberAccessException
// "Call to undefined static method Foo::form(), did you mean from()?"
Bár a mai PHP nem rendelkezik “úgy értetted?” funkcióval, ez a mondat hozzáadható a hibákhoz a Tracy által. Még az ilyen hibákat is képes automatikusan kijavítani.
Bővítési módszerek
A C# bővítési metódusok által inspirálva. Lehetőséget adtak új metódusok hozzáadására a meglévő osztályokhoz.
Például a addDateTime()
metódust hozzáadhatta egy űrlaphoz, hogy saját DateTimePickert adjon hozzá.
Form::extensionMethod(
'addDateTime',
fn(Form $form, string $name) => $form[$name] = new DateTimePicker,
);
$form = new Form;
$form->addDateTime('date');
A kiterjesztési metódusok nem bizonyultak praktikusnak, mert a nevüket a szerkesztők nem töltötték ki automatikusan, hanem azt jelentették, hogy a metódus nem létezik. Ezért a támogatásuk megszűnt.
Az osztály nevének megadása
$class = $obj->getClass(); // using Nette\Object
$class = $obj::class; // PHP 8.0 óta
Hozzáférés a reflexióhoz és a megjegyzésekhez
Nette\Object
a getReflection()
és a getAnnotation()
metódusok segítségével kínált
hozzáférést a reflexióhoz és a megjegyzésekhez:
/**
* @author John Doe
*/
class Foo extends Nette\Object
{
}
$obj = new Foo;
$reflection = $obj->getReflection();
$reflection->getAnnotation('author'); // visszaadja 'John Doe'
A PHP 8.0-tól kezdve a metainformációkat attribútumok formájában is elérhetjük:
#[Author('John Doe')]
class Foo
{
}
$obj = new Foo;
$reflection = new ReflectionObject($obj);
$reflection->getAttributes(Author::class)[0];
Metódus Getterek
Nette\Object
elegáns módot kínált arra, hogy a módszereket úgy kezeljük, mintha változók lennének:
class Foo extends Nette\Object
{
public function adder($a, $b)
{
return $a + $b;
}
}
$obj = new Foo;
$method = $obj->adder;
echo $method(2, 3); // 5
A PHP 8.1-től kezdve használhatod az úgynevezett első osztályú hívható szintaxist:
$obj = new Foo;
$method = $obj->adder(...);
echo $method(2, 3); // 5
Események
Nette\Object
szintaktikai cukrot kínált az esemény
kiváltásához:
class Circle extends Nette\Object
{
public array $onChange = [];
public function setRadius(float $radius): void
{
$this->onChange($this, $radius);
$this->radius = $radius;
}
}
A $this->onChange($this, $radius)
kód a következővel egyenértékű:
foreach ($this->onChange as $callback) {
$callback($this, $radius);
}
Az áttekinthetőség kedvéért javasoljuk, hogy kerüljük a $this->onChange()
varázsmódszert. Ennek
praktikus helyettesítője a Nette\Utils\Arrays::invoke
függvény:
Nette\Utils\Arrays::invoke($this->onChange, $this, $radius);