PHP 8.0 : Attributs (3/4)
La version 8.0 de PHP est sortie. Elle est tellement pleine de nouveautés qu'aucune version précédente ne l'a été. Leur présentation a nécessité quatre articles distincts. Dans ce troisième, nous allons examiner les attributs.

Les attributs apportent une toute nouvelle façon d'écrire des métadonnées structurées pour les classes et tous leurs membres, ainsi que pour les fonctions, les closures et leurs paramètres. À cette fin, les commentaires phpDoc étaient utilisés jusqu'à présent, mais leur syntaxe a toujours été si libre et incohérente qu'il n'était pas possible de commencer à les traiter de manière automatisée. C'est pourquoi ils sont remplacés par des attributs avec une syntaxe fixe et une prise en charge dans les classes de réflexion.
Par conséquent, les bibliothèques qui obtenaient jusqu'à présent des
métadonnées en analysant les commentaires phpDoc pourront les remplacer par
des attributs. Un exemple est Nette, où dans les dernières versions
d'Application et de DI, vous pouvez déjà utiliser les attributs
Persistent
, CrossOrigin
et Inject
au lieu
des annotations @persistent
, @crossOrigin
et
@inject
.
Code utilisant des annotations :
/**
* @persistent(comp1, comp2)
*/
class SomePresenter
{
/** @persistent */
public $page = 1;
/** @inject */
public Facade $facade;
/**
* @crossOrigin
*/
public function handleSomething()
{
}
}
La même chose en utilisant des attributs :
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 évalue les noms des attributs de la même manière que s'il s'agissait
de classes, c'est-à-dire dans le contexte de l'espace de noms et des clauses
use
. Ainsi, il serait possible de les écrire par exemple comme
ceci :
use Nette\Application\Attributes;
class SomePresenter
{
#[Attributes\Persistent]
public int $page = 1;
#[\Nette\DI\Attributes\Inject]
public Facade $facade;
La classe représentant l'attribut peut exister ou non. Mais il est certainement préférable qu'elle existe, car l'éditeur peut alors la suggérer lors de l'écriture, l'analyseur statique peut détecter les fautes de frappe, etc.
Syntaxe
Ce qui est pratique, c'est que PHP avant la version 8 ne voit les attributs que comme des commentaires, ils peuvent donc être utilisés même dans du code qui doit fonctionner dans des versions plus anciennes.
L'écriture d'un attribut individuel ressemble à la création d'une instance
d'objet, si nous omettions l'opérateur new
. C'est-à-dire le nom
de la classe suivi éventuellement d'arguments entre parenthèses :
#[Column('string', 32, true, false)]#
protected $username;
Et c'est ici que la nouvelle fonctionnalité de PHP 8.0 peut être mise à profit : les arguments nommés:
#[Column(
type: 'string',
length: 32,
unique: true,
nullable: false,
)]#
protected $username;
Chaque élément peut avoir plusieurs attributs, qui peuvent être écrits séparément ou séparés par une virgule :
#[Inject]
#[Lazy]
public Foo $foo;
#[Inject, Lazy]
public Bar $bar;
L'attribut suivant s'applique aux trois propriétés :
#[Common]
private $a, $b, $c;
Dans les valeurs par défaut des propriétés, on peut utiliser des expressions simples et des constantes qui peuvent être évaluées pendant la compilation, et il en va de même pour les arguments des attributs :
#[
ScalarExpression(1 + 1),
ClassNameAndConstants(PDO::class, PHP_VERSION_ID),
BitShift(4 >> 1, 4 << 1),
BitLogic(1 | 2, JSON_HEX_TAG | JSON_HEX_APOS),
]
Malheureusement, la valeur d'un argument ne peut pas être un autre attribut, c'est-à-dire qu'il n'est pas possible d'imbriquer les attributs. Par exemple, l'annotation suivante utilisée dans Doctrine ne peut pas être transformée de manière tout à fait directe en attributs :
/**
* @Table(name="products",uniqueConstraints={@UniqueConstraint(columns={"name", "email"})})
*/
De même, il n'existe pas d'équivalent sous forme d'attribut pour le phpDoc de fichier, c'est-à-dire le commentaire situé au début du fichier, qui est utilisé par exemple par Nette Tester.
Récupération des attributs
Nous pouvons découvrir quels attributs possèdent les différents éléments
à l'aide de la réflexion. Les classes de réflexion disposent d'une nouvelle
méthode getAttributes()
, qui renvoie un tableau d'objets
ReflectionAttribute
.
use MyAttributes\Example;
#[Example('Hello', 123)]
class Foo
{}
$reflection = new ReflectionClass(Foo::class);
foreach ($reflection->getAttributes() as $attribute) {
$attribute->getName(); // nom complet de l'attribut, par ex. MyAttributes\Example
$attribute->getArguments(); // ['Hello', 123]
$attribute->newInstance(); // renvoie l'instance new MyAttributes\Example('Hello', 123)
}
Les attributs retournés peuvent être filtrés par paramètre, par exemple
$reflection->getAttributes(Example::class)
ne renverra que les
attributs Example
.
Classes d'attributs
La classe d'attribut MyAttributes\Example
n'a pas besoin
d'exister. L'existence n'est requise que par l'appel de la méthode
newInstance()
car elle crée son instance. Écrivons-la donc. Il
s'agira d'une classe tout à fait ordinaire, il suffit d'y indiquer l'attribut
Attribute
(c'est-à-dire depuis l'espace de noms système
global) :
namespace MyAttributes;
use Attribute;
#[Attribute]
class Example
{
public function __construct(string $message, int $number)
{
...
}
}
Il est possible de limiter les éléments de langage sur lesquels il sera légal d'utiliser l'attribut. Ainsi, par exemple, uniquement sur les classes et les propriétés :
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_PROPERTY)]
class Example
{
...
}
Les drapeaux disponibles sont TARGET_CLASS
,
TARGET_FUNCTION
, TARGET_METHOD
,
TARGET_PROPERTY
, TARGET_CLASS_CONSTANT
,
TARGET_PARAMETER
et la valeur par défaut
TARGET_ALL
.
Mais attention, la vérification de l'utilisation correcte ou incorrecte se
produit étonnamment seulement lors de l'appel de la méthode
newInstance()
. Le compilateur lui-même n'effectue pas ce
contrôle.
L'avenir avec les attributs
Grâce aux attributs et aux nouveaux types,
les commentaires de documentation PHP deviendront pour la première fois de leur
histoire de véritables commentaires de documentation. PhpStorm propose déjà
ses propres
attributs, qui peuvent remplacer par exemple l'annotation
@deprecated
. Et on peut supposer que cet attribut sera un jour
standard en PHP. De même, d'autres annotations seront remplacées, comme
@throws
, etc.
Bien que Nette utilise des annotations depuis sa toute première version pour marquer les paramètres et composants persistants, leur utilisation plus massive n'a pas eu lieu car il ne s'agissait pas d'une construction native du langage, les éditeurs ne les suggéraient donc pas et il était facile de faire des erreurs. Bien que cela soit aujourd'hui résolu par des plugins d'éditeur, la voie vraiment native qu'apportent les attributs ouvre de toutes nouvelles possibilités.
D'ailleurs, les attributs ont obtenu une exception dans le Nette Coding
Standard, qui exige que le nom de la classe, en plus de la spécificité (par
exemple Product
, InvalidValue
), contienne également
la généralité (donc ProductPresenter
,
InvalidValueException
). Sinon, il ne serait pas clair lors de
l'utilisation dans le code ce que la classe représente exactement. Pour les
attributs, ce n'est au contraire pas souhaitable, la classe s'appelle donc
Inject
au lieu de InjectAttribute
.
Dans la dernière partie, nous examinerons quelles nouvelles fonctions et classes sont apparues en PHP et nous présenterons le Just in Time Compiler.
Pour soumettre un commentaire, veuillez vous connecter