PHP 8.0: Attribute (3/4)
PHP Version 8.0 ist erschienen. Sie ist so vollgepackt mit Neuerungen wie keine Version zuvor. Ihre Vorstellung erforderte gleich vier separate Artikel. In diesem dritten Teil werfen wir einen Blick auf Attribute.

Attribute bringen eine völlig neue Art und Weise, strukturierte Metadaten zu Klassen und all ihren Mitgliedern, sowie zu Funktionen, Closures und deren Parametern zu schreiben. Zu diesem Zweck wurden bisher phpDoc-Kommentare verwendet, aber ihre Syntax war immer so frei und uneinheitlich, dass es nicht möglich war, sie maschinell zu verarbeiten. Daher werden sie durch Attribute mit fester Syntax und Unterstützung in Reflection-Klassen ersetzt.
Daher können Bibliotheken, die bisher Metadaten durch Parsen von
phpDoc-Kommentaren gewonnen haben, diese durch Attribute ersetzen. Ein Beispiel
dafür ist Nette, wo Sie in den neuesten Versionen von Application und DI
bereits statt der Annotationen @persistent
,
@crossOrigin
und @inject
die Attribute
Persistent
, CrossOrigin
und Inject
verwenden können.
Code mit Annotationen:
/**
* @persistent(comp1, comp2)
*/
class SomePresenter
{
/** @persistent */
public $page = 1;
/** @inject */
public Facade $facade;
/**
* @crossOrigin
*/
public function handleSomething()
{
}
}
Dasselbe mit Attributen:
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 wertet die Namen von Attributen genauso aus, als wären es Klassen, also
im Kontext des Namensraums und der use
-Klauseln. Man könnte sie
also beispielsweise auch so schreiben:
use Nette\Application\Attributes;
class SomePresenter
{
#[Attributes\Persistent]
public int $page = 1;
#[\Nette\DI\Attributes\Inject]
public Facade $facade;
Die Klasse, die das Attribut repräsentiert, kann existieren oder auch nicht. Es ist aber definitiv besser, wenn sie existiert, da der Editor sie dann beim Schreiben vorschlagen kann, der statische Analysator Tippfehler erkennt usw.
Syntax
Praktisch ist, dass PHP vor Version 8 Attribute nur als Kommentare sieht, sodass sie auch in Code verwendet werden können, der in älteren Versionen funktionieren soll.
Die Schreibweise eines einzelnen Attributs sieht aus wie die Erstellung einer
Objektinstanz, wenn wir den new
-Operator weglassen würden. Also
der Klassenname, gefolgt von optionalen Argumenten in Klammern:
#[Column('string', 32, true, false)]#
protected $username;
Und hier kommt das neue Feature von PHP 8.0 zum Einsatz – benannte Argumente:
#[Column(
type: 'string',
length: 32,
unique: true,
nullable: false,
)]#
protected $username;
Jedes Element kann mehrere Attribute haben, die einzeln oder durch Komma getrennt geschrieben werden können:
#[Inject]
#[Lazy]
public Foo $foo;
#[Inject, Lazy]
public Bar $bar;
Das folgende Attribut gilt für alle drei Properties:
#[Common]
private $a, $b, $c;
In den Standardwerten von Properties können einfache Ausdrücke und Konstanten verwendet werden, die während der Kompilierung ausgewertet werden können, und dasselbe gilt auch für die Argumente von Attributen:
#[
ScalarExpression(1 + 1),
ClassNameAndConstants(PDO::class, PHP_VERSION_ID),
BitShift(4 >> 1, 4 << 1),
BitLogic(1 | 2, JSON_HEX_TAG | JSON_HEX_APOS),
]
Leider kann der Wert eines Arguments kein weiteres Attribut sein, d.h. Attribute können nicht verschachtelt werden. Zum Beispiel lässt sich die folgende in Doctrine verwendete Annotation nicht ganz direkt in Attribute umwandeln:
/**
* @Table(name="products",uniqueConstraints={@UniqueConstraint(columns={"name", "email"})})
*/
Es gibt auch kein Attribut-Äquivalent für die Datei phpDoc, d. h. einen Kommentar am Anfang einer Datei, der z. B. von Nette Tester verwendet wird.
Ermitteln von Attributen
Welche Attribute einzelne Elemente haben, ermitteln wir mittels Reflection.
Reflection-Klassen verfügen über die neue Methode
getAttributes()
, die ein Array von
ReflectionAttribute
-Objekten zurückgibt.
use MyAttributes\Example;
#[Example('Hallo', 123)]
class Foo
{}
$reflection = new ReflectionClass(Foo::class);
foreach ($reflection->getAttributes() as $attribute) {
$attribute->getName(); // vollständiger Attributname, z.B. MyAttributes\Example
$attribute->getArguments(); // ['Hallo', 123]
$attribute->newInstance(); // gibt Instanz new MyAttributes\Example('Hallo', 123) zurück
}
Die zurückgegebenen Attribute können nach Parametern gefiltert werden, z.B.
gibt $reflection->getAttributes(Example::class)
nur Attribute
vom Typ Example
zurück.
Attributklassen
Die Attributklasse MyAttributes\Example
muss nicht existieren.
Ihre Existenz erfordert nur der Aufruf der Methode newInstance()
,
da diese ihre Instanz erstellt. Schreiben wir sie also. Es handelt sich um eine
ganz normale Klasse, nur müssen wir bei ihr das Attribut Attribute
angeben (d.h. aus dem globalen System-Namensraum):
namespace MyAttributes;
use Attribute;
#[Attribute]
class Example
{
public function __construct(string $message, int $number)
{
...
}
}
Es kann eingeschränkt werden, für welche Sprachelemente die Verwendung des Attributs legal ist. So zum Beispiel nur für Klassen und Properties:
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_PROPERTY)]
class Example
{
...
}
Zur Verfügung stehen die Flags TARGET_CLASS
,
TARGET_FUNCTION
, TARGET_METHOD
,
TARGET_PROPERTY
, TARGET_CLASS_CONSTANT
,
TARGET_PARAMETER
und der Standard TARGET_ALL
.
Aber Achtung, die Überprüfung der korrekten oder falschen Verwendung
erfolgt überraschenderweise erst beim Aufruf der Methode
newInstance()
. Der Compiler selbst führt diese Prüfung
nicht durch.
Zukunft mit Attributen
Dank Attributen und neuen
Typen werden PHP-Dokumentationskommentare zum ersten Mal in ihrer Geschichte
wirklich nur noch dokumentarische Kommentare sein. PhpStorm verfügt bereits
über benutzerdefinierte
Attribute, die z.B. die Annotation @deprecated
ersetzen
können. Und es ist davon auszugehen, dass dieses Attribut eines Tages in PHP
standardmäßig vorhanden sein wird. In ähnlicher Weise werden auch andere
Annotationen ersetzt werden, wie z. B. @throws
usw.
Obwohl Nette Annotationen seit seiner allerersten Version zur Kennzeichnung persistenter Parameter und Komponenten verwendet, kam es nicht zu ihrer massiveren Nutzung, da es sich nicht um ein natives Sprachkonstrukt handelte, sodass Editoren sie nicht vorschlugen und es leicht war, Fehler darin zu machen. Dies lösen zwar heute Plugins für Editoren, aber der wirklich native Weg, den Attribute bringen, eröffnet völlig neue Möglichkeiten.
Übrigens haben Attribute eine Ausnahme im Nette Coding Standard erhalten,
der fordert, dass der Klassenname neben der Spezifität (z.B.
Product
, InvalidValue
) auch die Allgemeinheit enthält
(also ProductPresenter
, InvalidValueException
).
Andernfalls wäre bei der Verwendung im Code nicht ersichtlich, was die Klasse
genau darstellt. Bei Attributen ist dies hingegen nicht erwünscht, daher heißt
die Klasse Inject
anstelle von InjectAttribute
.
Im letzten Teil werfen wir einen Blick darauf, welche neuen Funktionen und Klassen in PHP aufgetaucht sind und stellen den Just-in-Time-Compiler vor.
Um einen Kommentar abzugeben, loggen Sie sich bitte ein