SmartObject és StaticClass

A SmartObject a PHP osztályok tulajdonságok támogatását adja hozzá. A StaticClass a statikus osztályok jelölésére szolgál.

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.

Statikus osztályok

A statikus osztályokat, azaz azokat az osztályokat, amelyeket nem szándékoznak példányosítani, a Nette\StaticClass tulajdonsággal lehet jelölni:

class Strings
{
	use Nette\StaticClass;
}

Amikor megpróbál egy példányt létrehozni, a Error kivétel dobódik, jelezve, hogy az osztály statikus.

Egy pillantás a történelembe

A SmartObject korábban sokféleképpen javította és javította az osztályok viselkedését, de a PHP fejlődése az eredeti funkciók nagy részét feleslegessé tette. A következőkben tehát a dolgok fejlődésének történetébe pillantunk bele.

A PHP objektummodellje a kezdetektől fogva számos komoly hibától és hiányosságtól szenvedett. Ez volt az oka a Nette\Object osztály létrehozásának (2007-ben), amely megpróbálta ezeket orvosolni és javítani a PHP használatának élményét. Ez elég volt ahhoz, hogy más osztályok is örököljenek belőle, és elnyerjék az általa nyújtott előnyöket. Amikor a PHP 5.4-ben megjelent a trait-támogatás, a Nette\Object osztályt a Nette\SmartObject osztály váltotta fel. Így már nem volt szükség arra, hogy egy közös őstől örököljenek. Ráadásul a trait olyan osztályokban is használható volt, amelyek már egy másik osztálytól örököltek. A Nette\Object végleges végét a PHP 7.2 kiadása jelentette, amely megtiltotta az osztályok Object nevének használatát.

A PHP fejlesztésének előrehaladtával az objektummodell és a nyelvi képességek tovább fejlődtek. A SmartObject osztály egyes függvényei feleslegessé váltak. A PHP 8.2 kiadása óta az egyetlen olyan funkció, amely megmaradt, és amelyet a PHP még nem támogat közvetlenül, az úgynevezett tulajdonságok használatának lehetősége.

Milyen funkciókat kínált egykor a Nette\Object és a Nette\Object? Íme egy áttekintés. (A példák a Nette\Object osztályt használják, de a tulajdonságok többsége 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()?"

A mai PHP-ben talán nincs a “did you mean?” semmilyen formája, de Tracy ezt a kiegészítést hozzáadja a hibákhoz. És még maga is képes kijavítani az ilyen hibákat.

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