SmartObject
A SmartObject éveken át fejlesztette a PHP objektumok viselkedését. A PHP 8.4 verziótól kezdve minden funkciója beépült magába a PHP nyelvbe, így teljesítve történelmi küldetését a modern objektumorientált megközelítés úttörőjeként.
Telepítés:
composer require nette/utils
A SmartObject 2007-ben jött létre, mint forradalmi megoldás az akkori PHP objektummodell hiányosságaira. Amikor a PHP számos objektumorientált tervezési problémával küzdött, jelentős fejlesztéseket és egyszerűsítéseket hozott a fejlesztők munkájába. A Nette framework legendás részévé vált. Olyan funkcionalitást kínált, amelyet a PHP csak sok évvel később ért el – az objektumtulajdonságok validálásától a kifinomult hibakezelésig. A PHP 8.4 megjelenésével befejezte történelmi küldetését, mivel minden funkciója a nyelv natív részévé vált. Figyelemreméltó módon 17 évvel előzte meg a PHP fejlődését.
A SmartObject érdekes technikai fejlődésen ment keresztül. Eredetileg Nette\Object
osztályként
implementálták, amelyből más osztályok örökölték a szükséges funkcionalitást. Jelentős változás jött 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 örököltek egy másik osztályból. Míg az eredeti Nette\Object
osztály megszűnt a PHP
7.2 megjelenésével (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
, majd később a
Nette\SmartObject
kínált. Mindegyik funkció jelentős előrelépést jelentett a PHP objektumorientált
programozásában a maga idejében.
Konzisztens hibaállapotok
A korai PHP egyik legégetőbb problémája az objektumokkal való munka következetlen viselkedése volt. A
Nette\Object
rendet és kiszámíthatóságot hozott ebbe a káoszba. Nézzük meg, hogyan működött eredetileg
a PHP:
echo $obj->undeclared; // E_NOTICE, később E_WARNING
$obj->undeclared = 1; // csendben lefut, figyelmeztetés nélkül
$obj->unknownMethod(); // Fatal error (nem elfogható try/catch-csel)
A Fatal error leállította az alkalmazást anélkül, hogy bármilyen reakcióra lehetőség lett volna. A nem létező
tagok csendes írása figyelmeztetés nélkül súlyos hibákhoz vezethetett, amelyeket nehéz volt felderíteni. A
Nette\Object
minden ilyen esetet elkapott és MemberAccessException
kivételt dobott, lehetővé téve a
programozóknak, hogy reagáljanak és kezeljék ezeket a hibákat:
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 tagok elérése hibaként kezelendő.
“Did you mean?” segítség
A Nette\Object
egy nagyon hasznos funkcióval érkezett: intelligens javaslatokkal elgépelések esetén. Amikor
egy fejlesztő hibát vétett egy metódus vagy változó nevében, nemcsak jelezte a hibát, hanem segítséget is nyújtott a
helyes név javaslatával. Ez az ikonikus üzenet, amely “did you mean?” néven vált ismertté, órákat spórolt meg a
programozóknak az elgépelések keresésében:
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()?"
Bár a mai PHP-nek nincs beépített “did you mean?” funkciója, ezt a kiegészítést a Tracy biztosítja. Sőt, az ilyen hibákat automatikusan javítani is tudja.
Tulajdonságok ellenőrzött hozzáféréssel
Jelentős újítás, amit a SmartObject hozott a PHP-ba, az ellenőrzött hozzáféréssel rendelkező tulajdonságok voltak. Ez a koncepció, amely olyan nyelvekben általános, mint a C# vagy Python, lehetővé tette a fejlesztőknek, hogy elegánsan szabályozzák az objektumadatok elérését és biztosítsák azok konzisztenciáját. A tulajdonságok az objektumorientált programozás hatékony eszközei. Változókként működnek, de valójában metódusok (getter-ek és setter-ek) reprezentálják őket. Ez lehetővé teszi a bemenetek validálását vagy az értékek generálását olvasás időpontjában.
A tulajdonságok használatához szükséges:
- A
@property <type> $xyz
annotáció hozzáadása az osztályhoz - Getter létrehozása
getXyz()
vagyisXyz()
néven, setter létrehozásasetXyz()
néven - A getter és setter public vagy protected láthatóságú legyen. Opcionálisak – így létezhetnek csak olvasható vagy csak írható tulajdonságokként
Nézzünk egy gyakorlati példát a Circle osztályon, ahol a tulajdonságokat használjuk annak biztosítására, hogy a
sugár mindig nem negatív szám legyen. A public $radius
-t tulajdonságra cseréljük:
/**
* @property float $radius
* @property-read bool $visible
*/
class Circle
{
use Nette\SmartObject;
private float $radius = 0.0; // nem public!
// getter a $radius tulajdonsághoz
protected function getRadius(): float
{
return $this->radius;
}
// setter a $radius tulajdonsághoz
protected function setRadius(float $radius): void
{
// érték sanitizálása mentés előtt
$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)-et hívja
echo $circle->radius; // getRadius()-t hív
echo $circle->visible; // isVisible()-t hív
A PHP 8.4 óta ugyanez a funkcionalitás elérhető property hooks segítségével, amely sokkal elegánsabb és tömörebb szintaxist kínál:
class Circle
{
public float $radius = 0.0 {
set => max(0.0, $value);
}
public bool $visible {
get => $this->radius > 0;
}
}
Extension methods (Kiterjesztő metódusok)
A Nette\Object
egy másik érdekes koncepciót hozott a PHP-ba a modern programozási nyelvekből – az
extension methods-t. Ez a C#-ból kölcsönzött funkció lehetővé tette a fejlesztőknek, hogy elegánsan bővítsék a
meglévő osztályokat új metódusokkal anélkül, hogy módosítaniuk kellene azokat vagy örökölniük kellene belőlük.
Például hozzáadhattak egy addDateTime()
metódust egy űrlaphoz, amely egy egyéni DateTimePicker-t
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 az editorok nem ajánlották fel a nevüket, sőt jelezték, hogy a metódus nem létezik. Ezért a támogatásuk megszűnt. Ma már általánosabb a kompozíció vagy öröklődés használata az osztályfunkcionalitás kiterjesztésére.
Osztálynév lekérdezése
A SmartObject egyszerű metódust kínált az osztálynév lekérdezésére:
$class = $obj->getClass(); // Nette\Object használatával
$class = $obj::class; // PHP 8.0 óta
Reflexió és annotáció hozzáférés
A Nette\Object
hozzáférést biztosított a reflexióhoz és az annotációkhoz a getReflection()
és getAnnotation()
metódusok segítségével. Ez a megközelítés jelentősen egyszerűsítette az osztály
metainformációkkal 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 metainformációk elérése attribútumok formájában, 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];
Method getterek
A Nette\Object
elegáns módot kínált metódusok átadására, 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 használható az úgynevezett first-class callable syntax, amely még tovább viszi ezt a koncepciót:
$obj = new Foo;
$method = $obj->adder(...);
echo $method(2, 3); // 5
Események
A SmartObject egyszerűsített szintaxist kínál az események kezeléséhez. Az események lehetővé teszik az objektumoknak, hogy értesítsé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 egyenértékű a következő ciklussal:
foreach ($this->onChange as $callback) {
$callback($this, $radius);
}
Az átláthatóság érdekében javasoljuk a $this->onChange()
mágikus metódus kerülését. Praktikus
helyettesítője a Nette\Utils\Arrays::invoke függvény:
Nette\Utils\Arrays::invoke($this->onChange, $this, $radius);