PHP 8.0 : Attributs (3/4)
La version 8.0 de PHP sort en ce moment même. Elle est pleine de nouveautés comme aucune autre version auparavant. Leur introduction méritait quatre articles distincts. Dans la troisième partie, nous allons jeter un coup d'oeil aux attributs.
Les attributs offrent 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 fermetures et leurs paramètres. Les commentaires PhpDoc ont été utilisés à cette fin jusqu'à présent, mais leur syntaxe a toujours été si lâche et incohérente qu'il n'était pas possible de commencer à les traiter par machine. Ils sont donc remplacés par des attributs dont la syntaxe est déterminée et qui sont pris en charge par les classes de réflexion.
Pour cette raison, les bibliothèques qui récupéraient auparavant les
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 DI vous pouvez déjà utiliser les attributs
Persistent
, CrossOrigin
et Inject
au lieu
des annotations @persistent
, @crossOrigin
et
@inject
.
Code utilisant les annotations :
/**
* @persistent(comp1, comp2)
*/
class SomePresenter
{
/** @persistent */
public $page = 1;
/** @inject */
public Facade $facade;
/**
* @crossOrigin
*/
public function handleSomething()
{
}
}
La même chose avec les 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 d'attributs de la même manière que s'il s'agissait de
classes, dans le contexte des espaces de noms et des clauses use
.
Il serait donc même possible de les écrire, par exemple, comme suit :
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 vaut mieux qu'elle existe, car l'éditeur peut alors la suggérer pendant l'écriture, l'analyseur statique reconnaît les fautes de frappe, etc.
Syntaxe
Il est astucieux que PHP avant la version 8 ne voit les attributs que comme des commentaires, donc ils peuvent aussi être utilisés dans du code qui devrait fonctionner dans des versions plus anciennes.
La syntaxe d'un attribut individuel ressemble à la création d'une instance
d'objet si l'on omet l'opérateur new
. Donc, le nom de la classe
suivi d'arguments optionnels 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 individuellement 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;
Les expressions simples et les constantes, qui peuvent être évaluées lors de la compilation et sont utilisées comme valeurs par défaut pour les propriétés, peuvent être utilisées comme arguments dans les 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 que les attributs ne peuvent pas être imbriqués. Par exemple, il n'y a pas de moyen direct de convertir l'annotation suivante utilisée dans Doctrine en attributs :
/**
* @Table(name="products",uniqueConstraints={@UniqueConstraint(columns={"name", "email"})})
*/
De même, il n'existe pas d'attribut équivalent pour le fichier phpDoc, c'est-à-dire un commentaire situé au début d'un fichier, qui est utilisé, par exemple, par Nette Tester.
Réflexion sur l'attribut
Les attributs que possèdent les éléments individuels peuvent être
déterminés en utilisant la réflexion. Les classes Reflection 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(); // full attribute name, e.g. MyAttributes\Example
$attribute->getArguments(); // ['Hello', 123]
$attribute->newInstance(); // returns an instance: new MyAttributes\Example('Hello', 123)
}
Les attributs renvoyés peuvent être filtrés par un paramètre, par
exemple, $reflection->getAttributes(Example::class)
renvoie
uniquement les attributs Example
.
Classes d'attributs
La classe d'attributs MyAttributes\Example
peut ne pas exister.
Seul un appel de méthode newInstance()
nécessite son existence
car il l'instancie. Alors, écrivons-la. Ce sera une classe tout à fait
ordinaire, seulement nous devons fournir l'attribut Attribute
(c'est-à-dire de l'espace de noms du système global) :
namespace MyAttributes;
use Attribute;
#[Attribute]
class Example
{
public function __construct(string $message, int $number)
{
...
}
}
Il est possible de restreindre les éléments de langage pour lesquels l'utilisation de l'attribut sera autorisée. Par exemple, uniquement pour les classes et les propriétés :
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_PROPERTY)]
class Example
{
...
}
Les drapeaux TARGET_CLASS
, TARGET_FUNCTION
,
TARGET_METHOD
, TARGET_PROPERTY
,
TARGET_CLASS_CONSTANT
, TARGET_PARAMETER
et
TARGET_ALL
par défaut sont disponibles.
Mais attention, la vérification de l'utilisation correcte ou incorrecte ne
se produit étonnamment que lorsque la méthode newInstance()
est
appelée. Le compilateur lui-même n'effectue pas cette vérification.
Le futur avec les attributs
Grâce aux attributs et aux nouveaux
types, les commentaires de la documentation PHP, pour la première fois de
leur histoire, deviendront réellement des commentaires documentaires. PhpStorm
est déjà livré avec des attributs
personnalisés, qui peuvent remplacer, par exemple, l'annotation
@deprecated
. Et l'on peut supposer que cet attribut sera un jour en
PHP par défaut. De même, d'autres annotations seront remplacées, comme
@throws
etc.
Bien que Nette utilise les annotations depuis sa toute première version pour indiquer les paramètres et les composants persistants, elles n'ont pas été utilisées plus massivement car elles n'étaient pas une construction du langage natif, les éditeurs ne les suggéraient donc pas et il était facile de faire une erreur. Bien que ce problème ait déjà été résolu par des plugins d'éditeur, la méthode réellement native, qui est apportée par les attributs, ouvre de toutes nouvelles possibilités.
À propos, les attributs ont obtenu une exception dans la norme de codage
Nette, qui exige que le nom de la classe, en plus de la spécificité (par
exemple Product
, InvalidValue
), contienne également
une généralité (par exemple ProductPresenter
,
InvalidValueException
). Sinon, lorsqu'il est utilisé dans le code,
il ne serait pas clair ce que la classe représente exactement. Pour les
attributs, ce n'est pas souhaitable, c'est pourquoi la classe est appelée
Inject
au lieu de InjectAttribute
.
*Dans la dernière partie, nous verrons quelles nouvelles fonctions et classes sont apparues en PHP et nous présenterons le compilateur Just in Time.
Pour soumettre un commentaire, veuillez vous connecter