SmartObject
A SmartObject évekig javította az objektumok viselkedését PHP-ban. A PHP 8.4 verziótól kezdve minden funkciója a PHP részévé vált, ezzel beteljesítve történelmi küldetését, hogy úttörője legyen a modern objektumorientált megközelítésnek PHP-ban.
Telepítés:
composer require nette/utils
A SmartObject 2007-ben jött létre forradalmi megoldásként a PHP akkori objektummodelljének hiányosságaira. Abban az időben, amikor a PHP számos objektumtervezési problémával küzdött, jelentős javulást és egyszerűsítést hozott a fejlesztők munkájába. A Nette keretrendszer legendás részévé vált. Olyan funkcionalitást kínált, amelyet a PHP csak sok évvel később kapott meg – az objektumtulajdonságokhoz való hozzáférés ellenőrzésétől a kifinomult szintaktikai cukorkákig. A PHP 8.4 megjelenésével beteljesítette történelmi küldetését, mivel minden funkciója a nyelv natív részévé vált. Figyelemre méltó 17 évvel előzte meg a PHP fejlődését.
Technikailag a SmartObject érdekes fejlődésen ment keresztül. Eredetileg Nette\Object
osztályként valósult
meg, amelyből más osztályok örökölték a szükséges funkcionalitást. Alapvető változás következett be a PHP 5.4-gyel,
amely bevezette a trait-ek támogatását. Ez lehetővé tette az átalakulást Nette\SmartObject
trait-té, ami
nagyobb rugalmasságot hozott – a fejlesztők olyan osztályokban is használhatták a funkcionalitást, amelyek már egy
másik osztályból örököltek. Míg az eredeti Nette\Object
osztály a PHP 7.2 megjelenésével megszűnt (amely
megtiltotta az osztályok Object
szóval való elnevezését), a Nette\SmartObject
trait
tovább él.
Nézzük át azokat a tulajdonságokat, amelyeket egykor a Nette\Object
, később pedig a
Nette\SmartObject
kínált. Ezen funkciók mindegyike a maga idejében jelentős előrelépést jelentett az
objektumorientált programozás területén PHP-ban.
Konzisztens hibaállapotok
A korai PHP egyik legégetőbb problémája az objektumokkal való munka inkonzisztens viselkedése volt. A
Nette\Object
rendet és kiszámíthatóságot hozott ebbe a káoszba. Nézzük meg, hogyan nézett ki a PHP eredeti
viselkedése:
echo $obj->undeclared; // E_NOTICE, később E_WARNING
$obj->undeclared = 1; // projde tiše bez hlášení
$obj->unknownMethod(); // Fatal error (nem fogható el try/catch segítségével)
A Fatal error leállította az alkalmazást anélkül, hogy bármilyen módon reagálni lehetett volna rá. A nem létező
tagokba történő csendes írás figyelmeztetés nélkül súlyos, nehezen felderíthető hibákhoz vezethetett. A
Nette\Object
mindezeket az eseteket elfogta és MemberAccessException
kivételt dobott, ami lehetővé
tette a programozóknak, hogy reagáljanak a hibákra és kezeljék azokat.
echo $obj->undeclared; // Nette\MemberAccessException kivételt dob
$obj->undeclared = 1; // Nette\MemberAccessException kivételt dob
$obj->unknownMethod(); // Nette\MemberAccessException kivételt dob
A PHP 7.0 óta a nyelv már nem okoz elfoghatatlan fatal error-t, és a PHP 8.2 óta a nem deklarált tagokhoz való hozzáférés hibának minősül.
“Did you mean?” súgó
A Nette\Object
egy nagyon kellemes funkcióval érkezett: intelligens súgóval elgépelések esetén. Amikor a
fejlesztő hibát vétett egy metódus vagy változó nevében, nemcsak a hibát jelezte, hanem segítő kezet is nyújtott a
helyes név javaslatának formájában. Ez az ikonikus üzenet, amelyet “did you mean?” néven ismerünk, órákat spórolt
meg a programozóknak az elgépelések keresésével:
class Foo extends Nette\Object
{
public static function from($var)
{
}
}
$foo = Foo::form($var);
// Nette\MemberAccessException kivételt dob
// "Call to undefined static method Foo::form(), did you mean from()?"
A mai PHP ugyan nem rendelkezik „did you mean?” funkcióval, de ezt a kiegészítést a Tracy hozzá tudja adni a hibákhoz. Sőt, az ilyen hibákat automatikusan ki is tudja javítani.
Property-k ellenőrzött hozzáféréssel
Jelentős innováció, amelyet a SmartObject hozott a PHP-ba, az ellenőrzött hozzáféréssel rendelkező property-k voltak. Ez a koncepció, amely a C# vagy Python nyelvekben elterjedt, lehetővé tette a fejlesztők számára, hogy elegánsan ellenőrizzék az objektum adataihoz való hozzáférést és biztosítsák azok konzisztenciáját. A property-k az objektumorientált programozás erőteljes eszközei. Változókként működnek, de valójában metódusok (getterek és setterek) képviselik őket. Ez lehetővé teszi a bemenetek validálását vagy az értékek generálását csak az olvasás pillanatában.
A property-k használatához:
- Adjon hozzá egy
@property <type> $xyz
alakú annotációt az osztályhoz - Hozzon létre egy
getXyz()
vagyisXyz()
nevű gettert, és egysetXyz()
nevű settert - Biztosítsa, hogy a getter és a setter public vagy protected legyen. Opcionálisak – tehát létezhetnek read-only vagy write-only property-ként is
Nézzünk egy gyakorlati példát a Circle osztályra, ahol a property-ket arra használjuk, hogy biztosítsuk, hogy a sugár
mindig nemnegatív szám legyen. Cseréljük le az eredeti public $radius
-t property-re:
/**
* @property float $radius
* @property-read bool $visible
*/
class Circle
{
use Nette\SmartObject;
private float $radius = 0.0; // není public!
// getter pro property $radius
protected function getRadius(): float
{
return $this->radius;
}
// setter pro property $radius
protected function setRadius(float $radius): void
{
// hodnotu před uložením sanitizujeme
$this->radius = max(0.0, $radius);
}
// getter pro property $visible
protected function isVisible(): bool
{
return $this->radius > 0;
}
}
$circle = new Circle;
$circle->radius = 10; // valójában a setRadius(10)-et hívja
echo $circle->radius; // a getRadius()-t hívja
echo $circle->visible; // az isVisible()-t hívja
A PHP 8.4 óta ugyanezt a funkcionalitást property hook-okkal lehet elérni, amelyek sokkal elegánsabb és tömörebb szintaxist kínálnak:
class Circle
{
public float $radius = 0.0 {
set => max(0.0, $value);
}
public bool $visible {
get => $this->radius > 0;
}
}
Extension methods
A Nette\Object
egy másik érdekes koncepciót hozott a PHP-ba, amelyet a modern programozási nyelvek
ihlettek – az extension metódusokat. Ez a funkció, amelyet a C#-ból vettek át, lehetővé tette a fejlesztők számára,
hogy elegánsan bővítsék a meglévő osztályokat új metódusokkal anélkül, hogy módosítaniuk vagy örökölniük kellene
tőlük. Például hozzáadhatott egy addDateTime()
metódust az űrlaphoz, amely egy saját DateTimePickert
ad hozzá:
Form::extensionMethod(
'addDateTime',
fn(Form $form, string $name) => $form[$name] = new DateTimePicker,
);
$form = new Form;
$form->addDateTime('date');
Az extension metódusok nem bizonyultak praktikusnak, mert a nevüket nem ismerték fel az editorok, sőt, azt jelezték, hogy a metódus nem létezik. Ezért a támogatásuk megszűnt. Ma már gyakoribb a kompozíció vagy az öröklődés használata az osztályok funkcionalitásának bővítésére.
Osztálynév lekérdezése
Az osztálynév lekérdezéséhez a SmartObject egyszerű metódust kínált:
$class = $obj->getClass(); // Nette\Object segítségével
$class = $obj::class; // PHP 8.0 óta
Hozzáférés a reflexióhoz és annotációkhoz
A Nette\Object
hozzáférést kínált a reflexióhoz és annotációkhoz a getReflection()
és
getAnnotation()
metódusok segítségével. Ez a megközelítés jelentősen leegyszerűsítette az osztályok
metaadataival való munkát:
/**
* @author John Doe
*/
class Foo extends Nette\Object
{
}
$obj = new Foo;
$reflection = $obj->getReflection();
$reflection->getAnnotation('author'); // 'John Doe'-t ad vissza
A PHP 8.0 óta lehetséges a metaadatokhoz attribútumok formájában hozzáférni, amelyek még több lehetőséget és jobb típusellenőrzést kínálnak:
#[Author('John Doe')]
class Foo
{
}
$obj = new Foo;
$reflection = new ReflectionObject($obj);
$reflection->getAttributes(Author::class)[0];
Metódus getterek
A Nette\Object
elegáns módot kínált arra, hogy a metódusokat úgy adjuk át, 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 óta lehetséges az ún. first-class callable syntax használata, amely ezt a koncepciót még tovább viszi:
$obj = new Foo;
$method = $obj->adder(...);
echo $method(2, 3); // 5
Események
A SmartObject egyszerűsített szintaxist kínál az eseményekkel való munkához. Az események lehetővé teszik az objektumok számára, hogy tájékoztassák az alkalmazás többi részét állapotuk változásairól:
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ő ciklussal ekvivalens:
foreach ($this->onChange as $callback) {
$callback($this, $radius);
}
Az érthetőség kedvéért javasoljuk a mágikus $this->onChange()
metódus elkerülését. Praktikus
helyettesítője például a Nette\Utils\Arrays::invoke
függvény:
Nette\Utils\Arrays::invoke($this->onChange, $this, $radius);