Skip to content

Commit

Permalink
smartobject: info about PHP 8.4
Browse files Browse the repository at this point in the history
  • Loading branch information
dg committed Nov 24, 2024
1 parent 1db65f7 commit cfaa617
Show file tree
Hide file tree
Showing 16 changed files with 1,428 additions and 1,365 deletions.
180 changes: 92 additions & 88 deletions utils/bg/smartobject.texy

Large diffs are not rendered by default.

142 changes: 73 additions & 69 deletions utils/cs/smartobject.texy
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ SmartObject
***********

.[perex]
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*.
SmartObject po léta vylepšoval chování objektů v PHP. Od verze PHP 8.4 jsou již všechny jeho funkce součástí samotného PHP, čímž završil svou historickou misi být průkopníkem moderního objektového přístupu v PHP.


Instalace:
Expand All @@ -11,19 +11,64 @@ Instalace:
composer require nette/utils
```

SmartObject vznikl v roce 2007 jako revoluční řešení nedostatků tehdejšího objektového modelu PHP. V době, kdy PHP trpělo řadou problémů s objektovým návrhem, přinesl výrazné vylepšení a zjednodušení práce pro vývojáře. Stal se legendární součástí frameworku Nette. Nabízel funkcionalitu, kterou PHP získalo až o mnoho let později - od kontrolu přístupu k vlastnostem objektů až po sofistikované syntaktické cukrátka. S příchodem PHP 8.4 završil svou historickou misi, protože všechny jeho funkce se staly nativní součástí jazyka. Předběhl vývoj PHP o pozoruhodných 17 let.

Properties, gettery a settery
=============================
Technicky prošel SmartObject zajímavým vývojem. Původně byl implementován jako třída `Nette\Object`, od které ostatní třídy dědily potřebnou funkcionalitu. Zásadní změna přišla s PHP 5.4, které přineslo podporu trait. To umožnilo transformaci do podoby traity `Nette\SmartObject`, což přineslo větší flexibilitu - vývojáři mohli funkcionalitu využít i ve třídách, které již dědily od jiné třídy. Zatímco původní třída `Nette\Object` zanikla s příchodem PHP 7.2 (které zakázalo pojmenování tříd slovem `Object`), traita `Nette\SmartObject` žije dál.

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|https://en.wikipedia.org/wiki/Property_(programming)], 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.
Pojďme si projít vlastnosti, které kdysi `Nette\Object` a později `Nette\SmartObject` nabízeli. Každá z těchto funkcí ve své době představovala významný krok vpřed v oblasti objektově orientovaného programování v PHP.

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()` nebo `isXyz()`, setter s názvem `setXyz()`
- Getter a setter musí být *public* nebo *protected* a jsou volitelné, mohou tedy existovat *read-only* nebo *write-only* property
Konzistentní chybové stavy
--------------------------
Jedním z nejpalčivějších problémů raného PHP bylo nekonzistentní chování při práci s objekty. `Nette\Object` přinesl do tohoto chaosu řád a předvídatelnost. Podívejme se, jak vypadalo původní chování PHP:

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:
```php
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`, což umožnilo programátorům na chyby reagovat a řešit je.

```php
echo $obj->undeclared; // vyhodí Nette\MemberAccessException
$obj->undeclared = 1; // vyhodí Nette\MemberAccessException
$obj->unknownMethod(); // vyhodí Nette\MemberAccessException
```

Od PHP 7.0 již jazyk nezpůsobuje nezachytitelné fatal error a od PHP 8.2 je přístup k nedeklarovaným členům považován za chybu.


Nápověda "Did you mean?"
------------------------
`Nette\Object` přišel s velmi příjemnou funkcí: inteligentní nápovědou při překlepech. Když vývojář udělal chybu v názvu metody nebo proměnné, nejen oznámil chybu, ale také nabídl pomocnou ruku v podobě návrhu správného názvu. Tato ikonická hláška, známá jako "did you mean?", ušetřila programátorům hodiny hledání překlepů:

```php
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|tracy:]. A dokonce takové chyby i [samo opravovat |tracy:open-files-in-ide#Ukázky].


Properties s kontrolovaným přístupem
------------------------------------
Významnou inovací, kterou SmartObject přinesl do PHP, byly properties s kontrolovaným přístupem. Tento koncept, běžný v jazycích jako C# nebo Python, umožnil vývojářům elegantně kontrolovat přístup k datům objektu a zajistit jejich konzistenci. Properties jsou mocným nástrojem objektově orientovaného programování. Fungují jako proměnné, ale ve skutečnosti jsou reprezentovány metodami (gettery a settery). To umožňuje validovat vstupy nebo generovat hodnoty až v momentě čtení.

Pro používání properties musíte:
- Přidat třídě anotaci ve tvaru `@property <type> $xyz`
- Vytvořit getter s názvem `getXyz()` nebo `isXyz()`, setter s názvem `setXyz()`
- Zajistit, aby getter a setter byly *public* nebo *protected*. Jsou volitelné - mohou tedy existovat jako *read-only* nebo *write-only* property

Ukažme si praktický příklad na třídě Circle, kde properties využijeme k zajištění, že poloměr bude vždy nezáporné číslo. Nahradíme původní `public $radius` za property:

```php
/**
Expand Down Expand Up @@ -62,64 +107,25 @@ echo $circle->radius; // volá getRadius()
echo $circle->visible; // volá isVisible()
```

Properties jsou především "syntaktickým cukříkem"((syntactic sugar)), 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|#Properties, gettery a settery].

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í:

```php
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`.
Od PHP 8.4 lze dosáhnout stejné funkcionality pomocí property hooks, které nabízí mnohem elegantnější a stručnější syntaxi:

```php
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?“.

```php
class Foo extends Nette\Object
class Circle
{
public static function from($var)
{
public float $radius = 0.0 {
set => max(0.0, $value);
}
}

$foo = Foo::form($var);
// vyhodí Nette\MemberAccessException
// "Call to undefined static method Foo::form(), did you mean from()?"
public bool $visible {
get => $this->radius > 0;
}
}
```

Dnešní PHP sice nemá žádnou podobu „did you mean?“, ale tento dovětek umí do chyb doplňovat [Tracy|tracy:]. A dokonce takové chyby i [samo opravovat |tracy:open-files-in-ide#Ukázky].


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.
`Nette\Object` přinesl do PHP další zajímavý koncept inspirovaný moderními programovacími jazyky - extension methods. Tato funkce, převzatá z C#, umožnila vývojářům elegantně rozšiřovat existující třídy o nové metody bez nutnosti je upravovat nebo od nich dědit. Třeba jste si mohli do formuláře přidat metodu `addDateTime()`, která přidá vlastní DateTimePicker:

```php
Form::extensionMethod(
Expand All @@ -131,22 +137,22 @@ $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.
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. Dnes je běžnější využívat kompozici nebo dědičnost pro rozšíření funkcionality tříd.


Zjištění názvu třídy:
---------------------
Zjištění názvu třídy
--------------------
Pro zjištění názvu třídy nabízel SmartObject jednoduchou metodu:

```php
$class = $obj->getClass(); // pomocí Nette\Object
$class = $obj::class; // od PHP 8.0
```


Přístup k reflexi a anotacem
Přístup k reflexi a anotacím
----------------------------

`Nette\Object` nabízel přístup k reflexi a anotacím pomocí metod `getReflection()` a `getAnnotation()`:
`Nette\Object` nabízel přístup k reflexi a anotacím pomocí metod `getReflection()` a `getAnnotation()`. Tento přístup významně zjednodušil práci s metainformacemi tříd:

```php
/**
Expand All @@ -161,7 +167,7 @@ $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ů:
Od PHP 8.0 je možné přistupovat k metainformacím v podobě atributů, které nabízí ještě větší možnosti a lepší typovou kontrolu:

```php
#[Author('John Doe')]
Expand All @@ -177,7 +183,6 @@ $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é:

```php
Expand All @@ -194,7 +199,7 @@ $method = $obj->adder;
echo $method(2, 3); // 5
```

Od PHP 8.1 je možné využít tzv. "first-class callable syntax":https://www.php.net/manual/en/functions.first_class_callable_syntax:
Od PHP 8.1 je možné využít tzv. "first-class callable syntax":https://www.php.net/manual/en/functions.first_class_callable_syntax, která tento koncept posouvá ještě dál:

```php
$obj = new Foo;
Expand All @@ -205,8 +210,7 @@ echo $method(2, 3); // 5

Události
--------

`Nette\Object` nabízel syntaktický cukr pro vyvolání [události|nette:glossary#události]:
SmartObject nabízí zjednodušenou syntax pro práci s [událostmi|nette:glossary#události]. Události umožňují objektům informovat ostatní části aplikace o změnách svého stavu:

```php
class Circle extends Nette\Object
Expand All @@ -221,7 +225,7 @@ class Circle extends Nette\Object
}
```

Kód `$this->onChange($this, $radius)` je ekvivalentní následujícímu:
Kód `$this->onChange($this, $radius)` je ekvivalentní následujícímu cyklu:

```php
foreach ($this->onChange as $callback) {
Expand Down
Loading

0 comments on commit cfaa617

Please sign in to comment.