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() vagy isXyz() nevű gettert, egy settert, amelynek a neve setXyz()
  • 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);
verzió: 4.0