PHP 8.0: Atributy (3/4)
Vyšlo PHP verze 8.0. Je tak nadupané novinkami, jako nebyla žádná verze předtím. Jejich představení si vyžádalo rovnou čtyři samostatné články. V tomto třetím se podíváme na atributy.
Atributy přinášejí zcela nový způsob, jak zapisovat strukturovaná metadata ke třídám a všem jejím členům, taktéž funkcím, closurám a jejich parametrům. K tomuto účelu se dosud využívaly phpDoc komentáře, ale jejich syntax byla vždy natolik volná a nejednotná, že nebylo možné je začít strojově zpracovávat. Proto je nahrazují atributy s pevnou syntaxí a podporou v reflexních třídách.
Tudíž knihovny, které dosud získávaly metadata parsováním phpDoc
komentářů, je budou moci nahradit za atributy. Příkladem je třeba Nette,
kde v nejnovějších verzích Application a DI už můžete místo anotací
@persistent
, @crossOrigin
a @inject
používat atributy Persistent
, CrossOrigin
a
Inject
.
Kód používající anotace:
/**
* @persistent(comp1, comp2)
*/
class SomePresenter
{
/** @persistent */
public $page = 1;
/** @inject */
public Facade $facade;
/**
* @crossOrigin
*/
public function handleSomething()
{
}
}
Totéž pomocí atributů:
use Nette\Application\Attributes\CrossOrigin;
use Nette\Application\Attributes\Persistent;
use Nette\DI\Attributes\Inject;
#[Persistent('comp1', 'comp2')]
class SomePresenter
{
#[Persistent]
public int $page = 1;
#[Inject]
public Facade $facade;
#[CrossOrigin]
public function handleSomething()
{
}
}
PHP vyhodnocuje názvy atributů stejně, jako by se jednalo o třídy, tedy
v kontextu jmenného prostoru a klauzulí use
. Tedy bylo by
možné je zapsat kupříkladu i takto:
use Nette\Application\Attributes;
class SomePresenter
{
#[Attributes\Persistent]
public int $page = 1;
#[\Nette\DI\Attributes\Inject]
public Facade $facade;
Třída představující atribut existovat může i nemusí. Je ale určitě lepší, pokud existuje, protože pak ji může editor napovídat při psaní, statický analyzátor rozpozná překlepy atd.
Syntax
Šikovné je, že PHP před verzí 8 vidí atributy jen jako komentáře, takže je lze používat i v kódu, který má fungovat ve starších verzích.
Zápis jednotlivého atributu vypadá jako vytváření instance objektu,
pokud bychom vynechali operátor new
. Tedy název třídy za
kterým mohou následovat v závorkách argumenty:
#[Column('string', 32, true, false)]#
protected $username;
A tady lze skvěle uplatnit další horkou novinku PHP 8.0 – pojmenované argumenty:
#[Column(
type: 'string',
length: 32,
unique: true,
nullable: false,
)]#
protected $username;
Každý element může mít více atributů, které lze zapsat samostatně nebo oddělené čárkou:
#[Inject]
#[Lazy]
public Foo $foo;
#[Inject, Lazy]
public Bar $bar;
Následující atribut platí pro všechny tři properties:
#[Common]
private $a, $b, $c;
Ve výchozích hodnotách properties lze používat jednoduché výrazy a konstanty, které lze vyhodnotit během kompilace, a totéž platí i pro argumenty atributů:
#[
ScalarExpression(1 + 1),
ClassNameAndConstants(PDO::class, PHP_VERSION_ID),
BitShift(4 >> 1, 4 << 1),
BitLogic(1 | 2, JSON_HEX_TAG | JSON_HEX_APOS),
]
Bohužel hodnotou argumentu nemůže být další atribut, tj. nelze atributy vnořovat. Například následující anotaci používanou v Doctrine nelze zcela přímočaře přetvořit do atributů:
/**
* @Table(name="products",uniqueConstraints={@UniqueConstraint(columns={"name", "email"})})
*/
Taktéž neexistuje atributová obdoba pro souborový phpDoc, tedy komentář nacházející se na začátku souboru, který využívá například Nette Tester.
Zjišťování atributů
Jaké atributy mají jednotlivé elementy zjistíme pomocí reflexe.
Reflexní třídy disponují novou metodou getAttributes()
, která
vrátí pole objektů ReflectionAttribute
.
use MyAttributes\Example;
#[Example('Hello', 123)]
class Foo
{}
$reflection = new ReflectionClass(Foo::class);
foreach ($reflection->getAttributes() as $attribute) {
$attribute->getName(); // plný název atributu, např. MyAttributes\Example
$attribute->getArguments(); // ['Hello', 123]
$attribute->newInstance(); // vrací instanci new MyAttributes\Example('Hello', 123)
}
Vrácené atributy lze filtrovat parametrem, např.
$reflection->getAttributes(Example::class)
vrátí jen atributy
Example
.
Třídy atributů
Třída atributu MyAttributes\Example
nemusí existovat.
Existenci vyžaduje pouze volání metody newInstance()
protože
vytváří její instanci. Pojďme ji tedy napsat. Půjde o úplně běžnou
třídu, jen u ní musíme uvést atribut Attribute
(tj.
z globálního systémového namespace):
namespace MyAttributes;
use Attribute;
#[Attribute]
class Example
{
public function __construct(string $message, int $number)
{
...
}
}
Lze omezit, u jakých jazykových elementů bude legální atribut použít. Takto třeba jen u tříd a properties:
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_PROPERTY)]
class Example
{
...
}
K dispozici jsou flagy TARGET_CLASS
,
TARGET_FUNCTION
, TARGET_METHOD
,
TARGET_PROPERTY
, TARGET_CLASS_CONSTANT
,
TARGET_PARAMETER
a výchozí TARGET_ALL
.
Ale pozor, k ověření správného či nesprávného použití dochází
překvapivě až při zavolání metody newInstance()
. Samotný
kompilátor tuto kontrolu neprovádí.
Budoucnost s atributy
Díky atributům a novým typům se
z PHP dokumentačních komentářů poprvé v jejich historii stanou opravdu
jen dokumentující komentáře. Už teď přichází PhpStorm s vlastními
atributy, které mohou nahradit třeba anotaci @deprecated
.
A dá se předpokládat, že tento atribut bude jednou standardně v PHP.
Podobně se nahradí i další anotace, jako @throws
atd.
Ačkoliv Nette používá anotace od své úplně první verze pro označení persistentních parametrů a komponent, k jejich masivnějšímu využití nedošlo proto, že nešlo o nativní jazykový konstrukt, takže editory je nenapovídaly a bylo snadné v nich udělat chybu. Tohle sice dnes už řeší pluginy do editorů, ale opravdu nativní cesta, jakou přinášejí atributy, otevírá zcela nové možnosti.
Mimochodem, atributy získaly výjimku v Nette Coding Standard, který
požaduje, aby název třídy kromě specifičnosti (např.
Product
, InvalidValue
) obsahoval i obecnost (tedy
ProductPresenter
, InvalidValueException
). Jinak by
nebylo při užití v kódu zřejmé, co přesně třída představuje.
U atributů tohle naopak není žádoucí, tedy třída se jmenuje
Inject
místo InjectAttribute
.
V posledním díle se podíváme, jaké se v PHP objevily nové funkce a třídy a představíme si Just in Time Compiler.
Komentáře
Děkujeme. Je dobré, že to tak sepisuješ.
Fakt super práce!
Super, luxusní, díky!
PHP 8.0 mě hodně překvapilo – příjemně.
Chcete-li odeslat komentář, přihlaste se