PHP 8.0: Attribute (3/4)
Die PHP-Version 8.0 wird gerade veröffentlicht. Sie ist voll von neuen Dingen, wie keine andere Version zuvor. Ihre Einführung hat vier separate Artikel verdient. Im dritten Teil werden wir einen Blick auf Attribute werfen.
Attribute bieten eine völlig neue Möglichkeit, strukturierte Metadaten für Klassen und alle ihre Mitglieder sowie für Funktionen, Closures und ihre Parameter zu schreiben. PhpDoc-Kommentare wurden bisher für diesen Zweck verwendet, aber ihre Syntax war immer so locker und inkonsistent, dass es nicht möglich war, sie maschinell zu verarbeiten. Daher werden sie durch Attribute mit festgelegter Syntax und Unterstützung in Reflection-Klassen ersetzt.
Aus diesem Grund werden Bibliotheken, die bisher Metadaten durch das Parsen
von phpDoc-Kommentaren abgerufen haben, diese durch Attribute ersetzen können.
Ein Beispiel ist Nette, wo man in den neuesten Versionen von Application und DI
bereits die Attribute Persistent
, CrossOrigin
und
Inject
anstelle der Annotationen @persistent
,
@crossOrigin
und @inject
verwenden kann.
Code mit Annotationen:
/**
* @persistent(comp1, comp2)
*/
class SomePresenter
{
/** @persistent */
public $page = 1;
/** @inject */
public Facade $facade;
/**
* @crossOrigin
*/
public function handleSomething()
{
}
}
Dasselbe gilt für Attribute:
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 Attributnamen im Kontext von Namespaces und use
-Klauseln genauso aus, als wären sie Klassen. Es wäre also sogar möglich, sie
z.B. wie folgt zu 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. Aber es ist auf jeden Fall besser, wenn sie existiert, denn dann kann der Editor sie beim Schreiben vorschlagen, der statische Analysator erkennt Tippfehler usw.
Syntax
Es ist clever, dass PHP vor Version 8 Attribute nur als Kommentare sieht, so dass sie auch in Code verwendet werden können, der in älteren Versionen funktionieren sollte.
Die Syntax eines einzelnen Attributs sieht aus wie die Erstellung einer
Objektinstanz, wenn wir den new
Operator weglassen. Also, der Name
der Klasse 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 ein Komma getrennt geschrieben werden können:
#[Inject]
#[Lazy]
public Foo $foo;
#[Inject, Lazy]
public Bar $bar;
Das folgende Attribut gilt für alle drei Eigenschaften:
#[Common]
private $a, $b, $c;
Einfache Ausdrücke und Konstanten, die beim Kompilieren ausgewertet werden können und als Standardwerte für Eigenschaften verwendet werden, können als Argumente in Attributen verwendet werden:
#[
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. Es gibt zum Beispiel keine einfache Möglichkeit, die folgende in Doctrine verwendete Anmerkung in Attribute umzuwandeln:
/**
* @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.
Attribut Reflexion
Welche Attribute die einzelnen Elemente haben, kann mit Hilfe von Reflection
ermittelt werden. Reflection-Klassen haben eine neue Methode
getAttributes()
, die ein Array von Objekten
ReflectionAttribute
zurückgibt.
use MyAttributes\Example;
#[Example('Hello', 123)]
class Foo
{}
$reflection = new ReflectionClass(Foo::class);
foreach ($reflection->getAttributes() as $attribute) {
$attribute->getName(); // full attribute name, e.g. MyAttributes\Example
$attribute->getArguments(); // ['Hello', 123]
$attribute->newInstance(); // returns an instance: new MyAttributes\Example('Hello', 123)
}
Die zurückgegebenen Attribute können durch einen Parameter gefiltert
werden, z. B. $reflection->getAttributes(Example::class)
gibt
nur Attribute zurück Example
.
Attribut-Klassen
Die Attributklasse MyAttributes\Example
darf nicht existieren.
Nur ein Methodenaufruf newInstance()
setzt ihre Existenz voraus,
weil er sie instanziiert. Also schreiben wir sie. Es wird eine ganz normale
Klasse sein, nur müssen wir das Attribut Attribute
bereitstellen
(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 erlaubt sein soll. Zum Beispiel, nur für Klassen und Eigenschaften:
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_PROPERTY)]
class Example
{
...
}
Es stehen die Flags TARGET_CLASS
, TARGET_FUNCTION
,
TARGET_METHOD
, TARGET_PROPERTY
,
TARGET_CLASS_CONSTANT
, TARGET_PARAMETER
und
standardmäßig TARGET_ALL
zur Verfügung.
Aber Achtung, die Überprüfung der richtigen oder falschen Verwendung
erfolgt überraschenderweise erst beim Aufruf der Methode
newInstance()
. Der Compiler selbst führt diese Prüfung
nicht durch.
Die 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 seit seiner allerersten Version Annotationen verwendet, um persistente Parameter und Komponenten zu kennzeichnen, wurden sie nicht in größerem Umfang verwendet, weil sie kein natives Sprachkonstrukt waren, so dass die Redakteure sie nicht vorschlugen und man leicht einen Fehler machen konnte. Auch wenn dies bereits durch Editor-Plugins behoben wird, eröffnet der wirklich native Weg, der durch Attribute beschritten wird, völlig neue Möglichkeiten.
Attribute haben übrigens eine Ausnahme im Nette Coding Standard erhalten,
der verlangt, dass der Klassenname neben der Spezifität (z.B.
Product
, InvalidValue
) auch eine Generalität (z.B.
ProductPresenter
, InvalidValueException
) enthält.
Andernfalls wäre bei der Verwendung im Code nicht klar, was genau die Klasse
repräsentiert. Bei Attributen ist dies nicht erwünscht, daher heißt die
Klasse Inject
statt InjectAttribute
.
*Im letzten Teil werden wir uns ansehen, welche neuen Funktionen und Klassen in PHP aufgetaucht sind und den Just in Time Compiler vorstellen.
Um einen Kommentar abzugeben, loggen Sie sich bitte ein