PHP 8.0: Atributi (3/4)
Izšla je različica PHP 8.0. Tako je polna novosti, kot še nobena različica pred njo. Njihova predstavitev je zahtevala kar štiri ločene članke. V tem tretjem si bomo pogledali atribute.

Atributi prinašajo popolnoma nov način, kako zapisovati strukturirane metapodatke k razredom in vsem njihovim članom, prav tako funkcijam, closuram in njihovim parametrom. V ta namen so se doslej uporabljali phpDoc komentarji, vendar je bila njihova sintaksa vedno tako ohlapna in neenotna, da jih ni bilo mogoče začeti strojno obdelovati. Zato jih nadomeščajo atributi s fiksno sintakso in podporo v refleksijskih razredih.
Zato bodo knjižnice, ki so doslej pridobivale metapodatke s parsiranjem
phpDoc komentarjev, jih lahko nadomestile z atributi. Primer je na primer
Nette, kjer v najnovejših različicah Application in DI že lahko namesto
anotacij @persistent
, @crossOrigin
in
@inject
uporabljate atribute Persistent
,
CrossOrigin
in Inject
.
Koda, ki uporablja anotacije:
/**
* @persistent(comp1, comp2)
*/
class SomePresenter
{
/** @persistent */
public $page = 1;
/** @inject */
public Facade $facade;
/**
* @crossOrigin
*/
public function handleSomething()
{
}
}
Enako s pomočjo atributov:
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 vrednoti imena atributov enako, kot da bi šlo za razrede, torej
v kontekstu imenskega prostora in klavzul use
. Torej bi jih bilo
mogoče zapisati na primer tudi tako:
use Nette\Application\Attributes;
class SomePresenter
{
#[Attributes\Persistent]
public int $page = 1;
#[\Nette\DI\Attributes\Inject]
public Facade $facade;
Razred, ki predstavlja atribut, lahko obstaja ali pa ne. Je pa vsekakor bolje, če obstaja, saj ga potem lahko urejevalnik predlaga pri pisanju, statični analizator prepozna tipkarske napake itd.
Sintaksa
Priročno je, da PHP pred različico 8 vidi atribute le kot komentarje, zato jih lahko uporabljamo tudi v kodi, ki mora delovati v starejših različicah.
Zapis posameznega atributa izgleda kot ustvarjanje instance objekta, če bi
izpustili operator new
. Torej ime razreda, za katerim lahko sledijo
v oklepajih argumenti:
#[Column('string', 32, true, false)]#
protected $username;
In tukaj lahko odlično uporabimo še eno vročo novost PHP 8.0 – poimenovane argumente:
#[Column(
type: 'string',
length: 32,
unique: true,
nullable: false,
)]#
protected $username;
Vsak element lahko ima več atributov, ki jih lahko zapišemo ločeno ali ločene z vejico:
#[Inject]
#[Lazy]
public Foo $foo;
#[Inject, Lazy]
public Bar $bar;
Naslednji atribut velja za vse tri lastnosti:
#[Common]
private $a, $b, $c;
V privzetih vrednostih lastnosti lahko uporabljamo preproste izraze in konstante, ki jih je mogoče ovrednotiti med prevajanjem, in enako velja tudi za argumente atributov:
#[
ScalarExpression(1 + 1),
ClassNameAndConstants(PDO::class, PHP_VERSION_ID),
BitShift(4 >> 1, 4 << 1),
BitLogic(1 | 2, JSON_HEX_TAG | JSON_HEX_APOS),
]
Na žalost vrednost argumenta ne more biti drug atribut, tj. atributov ni mogoče gnezditi. Na primer naslednje anotacije, ki se uporablja v Doctrine, ni mogoče popolnoma neposredno preoblikovati v atribute:
/**
* @Table(name="products",uniqueConstraints={@UniqueConstraint(columns={"name", "email"})})
*/
Prav tako ne obstaja atributna ustreznica za datotečni phpDoc, torej komentar, ki se nahaja na začetku datoteke, ki ga uporablja na primer Nette Tester.
Pridobivanje atributov
Katere atribute imajo posamezni elementi, ugotovimo s pomočjo refleksije.
Refleksijski razredi imajo novo metodo getAttributes()
, ki vrne
polje objektov ReflectionAttribute
.
use MyAttributes\Example;
#[Example('Zdravo', 123)]
class Foo
{}
$reflection = new ReflectionClass(Foo::class);
foreach ($reflection->getAttributes() as $attribute) {
$attribute->getName(); // polno ime atributa, npr. MyAttributes\Example
$attribute->getArguments(); // ['Zdravo', 123]
$attribute->newInstance(); // vrne instanco new MyAttributes\Example('Zdravo', 123)
}
Vrnjene atribute lahko filtriramo s parametrom, npr.
$reflection->getAttributes(Example::class)
vrne le atribute
Example
.
Razredi atributov
Razred atributa MyAttributes\Example
ni nujno, da obstaja.
Obstoj zahteva le klic metode newInstance()
, ker ustvarja njeno
instanco. Pa jo napišimo. Gre za popolnoma običajen razred, le pri njem moramo
navesti atribut Attribute
(tj. iz globalnega sistemskega imenskega
prostora):
namespace MyAttributes;
use Attribute;
#[Attribute]
class Example
{
public function __construct(string $message, int $number)
{
...
}
}
Lahko omejimo, pri katerih jezikovnih elementih bo legalno uporabiti atribut. Tako na primer le pri razredih in lastnostih:
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_PROPERTY)]
class Example
{
...
}
Na voljo so zastavice TARGET_CLASS
,
TARGET_FUNCTION
, TARGET_METHOD
,
TARGET_PROPERTY
, TARGET_CLASS_CONSTANT
,
TARGET_PARAMETER
in privzeta TARGET_ALL
.
Ampak pozor, do preverjanja pravilne ali nepravilne uporabe pride
presenetljivo šele ob klicu metode newInstance()
. Sam prevajalnik
te kontrole ne izvaja.
Prihodnost z atributi
Zahvaljujoč atributom in novim tipom se bodo iz
PHP dokumentacijskih komentarjev prvič v njihovi zgodovini postali resnično
le dokumentirajoči komentarji. Že zdaj prihaja PhpStorm z lastnimi
atributi, ki lahko nadomestijo na primer anotacijo @deprecated
.
In da se predpostaviti, da bo ta atribut nekoč standardno v PHP. Podobno se
bodo nadomestile tudi druge anotacije, kot je @throws
itd.
Čeprav Nette uporablja anotacije od svoje čisto prve različice za označevanje persistentnih parametrov in komponent, do njihove masivnejše uporabe ni prišlo zato, ker ni šlo za nativni jezikovni konstrukt, zato jih urejevalniki niso predlagali in je bilo enostavno v njih narediti napako. To sicer danes že rešujejo vtičniki za urejevalnike, ampak resnično nativna pot, kakršno prinašajo atributi, odpira popolnoma nove možnosti.
Mimogrede, atributi so dobili izjemo v Nette Coding Standard, ki zahteva, da
ime razreda poleg specifičnosti (npr. Product
,
InvalidValue
) vsebuje tudi splošnost (torej
ProductPresenter
, InvalidValueException
). Sicer ob
uporabi v kodi ne bi bilo očitno, kaj točno razred predstavlja. Pri atributih
to nasprotno ni zaželeno, torej se razred imenuje Inject
namesto
InjectAttribute
.
V zadnjem delu si bomo pogledali, katere nove funkcije in razredi so se pojavili v PHP in predstavili si bomo Just in Time Compiler.
Če želite oddati komentar, se prijavite