PHP 8.0: Attribútumok (3/4)
Megjelent a PHP 8.0-s verziója. Annyira tele van újdonságokkal, mint még egyetlen verzió sem korábban. Bemutatásukhoz egyenesen négy különálló cikkre volt szükség. Ebben a harmadikban az attribútumokat nézzük meg.

Az attribútumok teljesen új módot hoznak a strukturált metaadatok írására az osztályokhoz és azok minden tagjához, valamint a függvényekhez, closure-ökhöz és azok paramétereihez. Erre a célra eddig a phpDoc kommenteket használták, de azok szintaxisa mindig annyira laza és következetlen volt, hogy nem lehetett őket gépi feldolgozásra használni. Ezért helyettesítik őket az attribútumok, amelyek fix szintaxissal és a reflection osztályokban való támogatással rendelkeznek.
Ennek következtében azok a könyvtárak, amelyek eddig a phpDoc kommentek
elemzésével nyertek metaadatokat, lecserélhetik őket attribútumokra. Példa
erre a Nette, ahol a legújabb Application és DI verziókban már a
@persistent
, @crossOrigin
és @inject
annotációk helyett a Persistent
, CrossOrigin
és
Inject
attribútumokat használhatja.
Annotációkat használó kód:
/**
* @persistent(comp1, comp2)
*/
class SomePresenter
{
/** @persistent */
public $page = 1;
/** @inject */
public Facade $facade;
/**
* @crossOrigin
*/
public function handleSomething()
{
}
}
Ugyanez attribútumokkal:
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()
{
}
}
A PHP az attribútumok neveit ugyanúgy értékeli, mintha osztályok
lennének, tehát a névtér és a use
klauzulák kontextusában.
Tehát például így is le lehetne írni őket:
use Nette\Application\Attributes;
class SomePresenter
{
#[Attributes\Persistent]
public int $page = 1;
#[\Nette\DI\Attributes\Inject]
public Facade $facade;
Az attribútumot reprezentáló osztály létezhet, de nem kötelező. De mindenképpen jobb, ha létezik, mert akkor a szerkesztő súgni tudja írás közben, a statikus elemző felismeri az elírásokat stb.
Szintaxis
Praktikus, hogy a PHP 8 előtti verziók az attribútumokat csak kommentként látják, így használhatók olyan kódban is, amelynek régebbi verziókban is működnie kell.
Egyetlen attribútum írása úgy néz ki, mint egy objektum
példányosítása, ha kihagynánk a new
operátort. Tehát az
osztály neve, amelyet zárójelben argumentumok követhetnek:
#[Column('string', 32, true, false)]#
protected $username;
És itt van az a hely, ahol a PHP 8.0 új, forró funkcióját, a named arguments használhatjuk:
#[Column(
type: 'string',
length: 32,
unique: true,
nullable: false,
)]#
protected $username;
Minden elemnek több attribútuma lehet, amelyeket külön-külön vagy vesszővel elválasztva lehet írni:
#[Inject]
#[Lazy]
public Foo $foo;
#[Inject, Lazy]
public Bar $bar;
A következő attribútum mindhárom property-re vonatkozik:
#[Common]
private $a, $b, $c;
A property-k alapértelmezett értékeiben egyszerű kifejezéseket és konstansokat lehet használni, amelyeket fordítás közben lehet kiértékelni, és ugyanez vonatkozik az attribútumok argumentumaira is:
#[
ScalarExpression(1 + 1),
ClassNameAndConstants(PDO::class, PHP_VERSION_ID),
BitShift(4 >> 1, 4 << 1),
BitLogic(1 | 2, JSON_HEX_TAG | JSON_HEX_APOS),
]
Sajnos az argumentum értéke nem lehet egy másik attribútum, azaz az attribútumokat nem lehet egymásba ágyazni. Például a Doctrine-ben használt következő annotációt nem lehet teljesen közvetlenül attribútumokká alakítani:
/**
* @Table(name="products",uniqueConstraints={@UniqueConstraint(columns={"name", "email"})})
*/
Szintén nincs attribútum megfelelője a phpDoc fájlnak, azaz a fájl elején található megjegyzésnek, amelyet például a Nette Tester használ.
Attribútumok lekérdezése
Hogy mely elemeknek milyen attribútumai vannak, azt a reflection
segítségével tudjuk meg. A reflection osztályok új
getAttributes()
metódussal rendelkeznek, amely
ReflectionAttribute
objektumok tömbjét adja vissza.
use MyAttributes\Example;
#[Example('Szia', 123)]
class Foo
{}
$reflection = new ReflectionClass(Foo::class);
foreach ($reflection->getAttributes() as $attribute) {
$attribute->getName(); // attribútum teljes neve, pl. MyAttributes\Example
$attribute->getArguments(); // ['Szia', 123]
$attribute->newInstance(); // visszaadja a new MyAttributes\Example('Szia', 123) példányt
}
A visszaadott attribútumokat paraméterrel lehet szűrni, pl. a
$reflection->getAttributes(Example::class)
csak az
Example
attribútumokat adja vissza.
Attribútum osztályok
Az MyAttributes\Example
attribútum osztálynak nem kell
léteznie. Létezését csak a newInstance()
metódus hívása
követeli meg, mert annak példányát hozza létre. Írjuk meg tehát. Egy
teljesen átlagos osztály lesz, csak meg kell adnunk hozzá az
Attribute
attribútumot (azaz a globális rendszer
névtérből):
namespace MyAttributes;
use Attribute;
#[Attribute]
class Example
{
public function __construct(string $message, int $number)
{
...
}
}
Lehet korlátozni, hogy mely nyelvi elemeknél lesz legális az attribútum használata. Így például csak osztályoknál és property-knél:
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_PROPERTY)]
class Example
{
...
}
Rendelkezésre állnak a TARGET_CLASS
,
TARGET_FUNCTION
, TARGET_METHOD
,
TARGET_PROPERTY
, TARGET_CLASS_CONSTANT
,
TARGET_PARAMETER
flag-ek és az alapértelmezett
TARGET_ALL
.
De vigyázat, a helyes vagy helytelen használat ellenőrzése meglepő
módon csak a newInstance()
metódus hívásakor történik meg.
Maga a fordító ezt az ellenőrzést nem végzi el.
Jövő az attribútumokkal
Az attribútumoknak és az új típusoknak
köszönhetően a PHP dokumentációs kommentek történetük során először
válnak valóban csak dokumentáló kommentekké. Már most is a PhpStorm saját
attribútumokkal áll elő, amelyek helyettesíthetik például a
@deprecated
annotációt. És feltételezhető, hogy ez az
attribútum egyszer szabványosan a PHP-ban lesz. Hasonlóan helyettesítődnek
majd más annotációk is, mint a @throws
stb.
Bár a Nette a legelső verziójától kezdve használ annotációkat a perzisztens paraméterek és komponensek megjelölésére, masszívabb kihasználásukra nem került sor, mert nem natív nyelvi konstrukcióról volt szó, így a szerkesztők nem súgták őket, és könnyű volt bennük hibát ejteni. Ezt ma már ugyan megoldják a szerkesztő pluginok, de az igazán natív út, amelyet az attribútumok hoznak, teljesen új lehetőségeket nyit meg.
Mellesleg, az attribútumok kivételt kaptak a Nette Coding Standardban,
amely megköveteli, hogy az osztály neve a specifikusságon (pl.
Product
, InvalidValue
) kívül tartalmazza az
általánosságot is (tehát ProductPresenter
,
InvalidValueException
). Különben a kódban való használatkor
nem lenne egyértelmű, mit is képvisel pontosan az osztály. Az
attribútumoknál ez viszont nem kívánatos, tehát az osztály neve
Inject
InjectAttribute
helyett.
Az utolsó részben megnézzük, milyen új függvények és osztályok jelentek meg a PHP-ban, és bemutatjuk a Just in Time Compilert.
A hozzászólás elküldéséhez kérjük, jelentkezzen be