SmartObject
SmartObject opravovala chování objektů v mnoha směrech, ale dnešní PHP již obsahuje většinu vylepšení nativně. Stále však přidává podporu pro tzv. property.
Instalace:
composer require nette/utils
Properties, gettery a settery
Termínem property (česky vlastnost) se v moderních objektově orientovaných jazycích (např. C#, Python, Ruby, JavaScript) označují speciální členy tříd, které se tváří jako proměnné, ale ve skutečnosti jsou reprezentovány metodami. Při přiřazení nebo čtení hodnoty této „proměnné“ se zavolá příslušná metoda (tzv. getter nebo setter). Jde o velice šikovnou věc, díky ní máme přístup k proměnným plně pod kontrolou. Můžeme tak validovat vstupy nebo generovat výsledky až ve chvíli, kdy se property čte.
PHP property nepodporují, ale traita Nette\SmartObject
je umí imitovat. Jak na to?
- Přidejte třídě anotaci ve tvaru
@property <type> $xyz
- Vytvořte getter s názvem
getXyz()
neboisXyz()
, setter s názvemsetXyz()
- Getter a setter musí být public nebo protected a jsou volitelné, mohou tedy existovat read-only nebo write-only property
Property využijeme u třídy Circle, abychom zajistili, že do proměnné $radius
se dostanou jen nezáporná
čísla. Nahradíme public $radius
za property:
/**
* @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; // ve skutečnosti volá setRadius(10)
echo $circle->radius; // volá getRadius()
echo $circle->visible; // volá isVisible()
Properties jsou především syntaktickým cukříkem, jehož smyslem je zpřehlednit kód a osladit tak programátorovi život. Pokud nechcete, nemusíte je používat.
Pohled do historie
SmartObject opravovala chování objektů v mnoha směrech, ale dnešní PHP již obsahuje většinu vylepšení nativně. Následující text je tak nostalgickým pohledem do historie a připomínkou toho, jak se věci vyvíjely.
Objektový model PHP trpěl od počátku celou řadou vážných nedostatků a necnostní. To byl důvod vzniku třídy
Nette\Object
(v roce 2007), která se je pokoušela napravovat a zlepšit komfort při používání PHP. Stačilo,
aby ostatní třídy od ní dědily, a získaly výhody, které přinášela. Když PHP 5.4 přišlo s podporou trait, byla
třída Nette\Object
nahrazena traitou Nette\SmartObject
. Nebylo tak nutné už dědit od společného
předka. Navíc traita se dala použít i ve třídách, které již dědily od jiné třídy. Definitivní konec
Nette\Object
přišel s vydáním PHP 7.2, které zakázalo třídám jmenovat se Object
.
Jak šel vývoj PHP dál, objektový model a schopnosti jazyka se vylepšovaly. Jednotlivé funkce třídy
SmartObject
se stávaly zbytečnými. Od vydání PHP 8.2 zůstala jediná feature, která ještě není v PHP
přímo podporována, a to možnost používat tzv. property.
Jaké vlastnosti kdysi Nette\Object
a potažmo Nette\Object
nabízely? Nabízíme přehled.
(V ukázkách se používá třída Nette\Object
, ale většina vlastnosti se týká i traity
Nette\SmartObject
).
Nekonzistentní chyby
PHP mělo nekonzistentní chování při přístupu k nedeklarovaným členům. Stav v době vzniku Nette\Object
byl následující:
echo $obj->undeclared; // E_NOTICE, později E_WARNING
$obj->undeclared = 1; // projde tiše bez hlášení
$obj->unknownMethod(); // Fatal error (nezachytitelný pomocí try/catch)
Fatal error ukončil aplikaci bez možnosti jakkoliv reagovat. Tichý zápis do neexistujících členů bez upozornění mohl
vést k závažným chybám, které šly obtížné odhalit. Nette\Object
všechny tyto případy zachytával a
vyhazoval výjimku MemberAccessException
.
echo $obj->undeclared; // vyhodí Nette\MemberAccessException
$obj->undeclared = 1; // vyhodí Nette\MemberAccessException
$obj->unknownMethod(); // vyhodí Nette\MemberAccessException
PHP od verze PHP 7.0 už nezachytitelné fatal error nezpůsobuje a přístup k nedeklarovaným členům se stává chybou od PHP 8.2.
Did you mean?
Pokud došlo k vyhození chyby Nette\MemberAccessException
, třeba z důvodu překlepu při přístupu
k proměnné objektu nebo volání metody, pokusilo se Nette\Object
v chybové hlášce napovědět, jak chybu
opravit, a to v podobě ikonického dovětku „did you mean?“.
class Foo extends Nette\Object
{
public static function from($var)
{
}
}
$foo = Foo::form($var);
// vyhodí Nette\MemberAccessException
// "Call to undefined static method Foo::form(), did you mean from()?"
Dnešní PHP sice nemá žádnou podobu „did you mean?“, ale tento dovětek umí do chyb doplňovat Tracy. A dokonce takové chyby i samo opravovat.
Extension methods
Inspirací byly extension methods z jazyka C#. Dávaly možnost do existujících tříd přidávat nové metody. Třeba jste
si mohli do formuláře přidat metodu addDateTime()
, která přidá vlastní DateTimePicker.
Form::extensionMethod(
'addDateTime',
fn(Form $form, string $name) => $form[$name] = new DateTimePicker,
);
$form = new Form;
$form->addDateTime('date');
Extension metody se ukázaly jako nepraktické, protože jejich názvy nenapovídaly editory, naopak hlásily, že metoda neexistuje. Proto byla jejich podpora ukončena.
Zjištění názvu třídy:
$class = $obj->getClass(); // pomocí Nette\Object
$class = $obj::class; // od PHP 8.0
Přístup k reflexi a anotacem
Nette\Object
nabízel přístup k reflexi a anotacím pomocí metod getReflection()
a
getAnnotation()
:
/**
* @author John Doe
*/
class Foo extends Nette\Object
{
}
$obj = new Foo;
$reflection = $obj->getReflection();
$reflection->getAnnotation('author'); // vrátí 'John Doe'
Od PHP 8.0 je možné přistupovat k metainformacím v podobě atributů:
#[Author('John Doe')]
class Foo
{
}
$obj = new Foo;
$reflection = new ReflectionObject($obj);
$reflection->getAttributes(Author::class)[0];
Method gettery
Nette\Object
nabízel elegantní způsob, jak předávat metody jako kdyby šlo o proměnné:
class Foo extends Nette\Object
{
public function adder($a, $b)
{
return $a + $b;
}
}
$obj = new Foo;
$method = $obj->adder;
echo $method(2, 3); // 5
Od PHP 8.1 je možné využít tzv. first-class callable syntax:
$obj = new Foo;
$method = $obj->adder(...);
echo $method(2, 3); // 5
Události
Nette\Object
nabízel syntaktický cukr pro vyvolání události:
class Circle extends Nette\Object
{
public array $onChange = [];
public function setRadius(float $radius): void
{
$this->onChange($this, $radius);
$this->radius = $radius;
}
}
Kód $this->onChange($this, $radius)
je ekvivalentní následujícímu:
foreach ($this->onChange as $callback) {
$callback($this, $radius);
}
Kvůli srozumitelnosti doporučujeme se magické metodě $this->onChange()
vyhnout. Praktickou náhradou je
třeba funkce Nette\Utils\Arrays::invoke:
Nette\Utils\Arrays::invoke($this->onChange, $this, $radius);