Mis à jour le lundi 8 janvier 2018
  • 30 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

Ce cours existe en livre papier.

Vous pouvez obtenir un certificat de réussite à l'issue de ce cours.

Vous pouvez être accompagné et mentoré par un professeur particulier par visioconférence sur ce cours.

J'ai tout compris !

L'API de réflexivité

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

En tant que développeur, vous savez que telle classe hérite de telle autre ou que tel attribut est protégé par exemple. Cependant, votre script PHP, lui ne le sait pas : comment feriez-vous pour afficher à l'écran par exemple que telle classe hérite de telle autre (dynamiquement bien entendu) ? Cela est pour vous impossible. Je vais cependant vous dévoiler la solution : pour y parvenir, on se sert de l'API de réflexivité qui va justement nous permettre d'obtenir des informations sur nos classes, attributs et méthodes.

Une fois que nous aurons vu tout cela, nous allons nous pencher sur un exemple d'utilisation. Pour cela, nous utiliserons une bibliothèque qui se sert de l'API de réflexivité pour exploiter les annotations, terme sans doute encore inconnu en programmation pour la plupart d'entre vous.

Obtenir des informations sur ses classes

Qui dit « Réflexivité » dit « Instanciation de classe ». Nous allons donc instancier une classe qui nous fournira des informations sur telle classe. Dans cette section, il s'agit de la classeReflectionClass. Quand nous l'instancierons, nous devrons spécifier le nom de la classe sur laquelle on veut obtenir des informations. Nous allons prendre pour exemple la classeMagiciende notre précédent TP.

Pour obtenir des informations la concernant, nous allons procéder comme suit :

<?php
$classeMagicien = new ReflectionClass('Magicien'); // Le nom de la classe doit être entre apostrophes ou guillemets.

Il est également possible d'obtenir des informations sur une classe grâce à un objet. Nous allons pour cela instancier la classeReflectionObjecten fournissant l'instance en guise d'argument. Cette classe hérite de toutes les méthodes deReflectionClass: elle ne réécrit que deux méthodes (dont le constructeur). La seconde méthode réécrite ne nous intéresse pas. Cette classe n'implémente pas de nouvelles méthodes.

Exemple d'utilisation très simple :

<?php
$magicien = new Magicien(['nom' => 'vyk12', 'type' => 'magicien']);
$classeMagicien = new ReflectionObject($magicien);

Informations propres à la classe

Les attributs

Pour savoir si la classe possède tel attribut, tournons-nous versReflectionClass::hasProperty($atrributeName). Cette méthode retourne vrai si l'attribut passé en paramètre existe et faux s'il n'existe pas. Exemple :

<?php
if ($classeMagicien->hasProperty('magie'))
{
  echo 'La classe Magicien possède un attribut $magie';
}
else
{
  echo 'La classe Magicien ne possède pas d\'attribut $magie';
}

Vous pouvez aussi récupérer cet attribut afin d'obtenir des informations le concernant comme nous venons de faire jusqu'à maintenant avec notre classe. Nous verrons cela plus tard, une sous-partie entière sera dédiée à ce sujet.

Les méthodes

Si vous voulez savoir si la classe implémente telle méthode, alors il va falloir regarder du côté deReflectionClass::hasMethod($methodName). Celle-ci retourne vrai si la méthode est implémentée ou faux si elle ne l'est pas. Exemple :

<?php
if ($classeMagicien->hasMethod('lancerUnSort'))
{
  echo 'La classe Magicien implémente une méthode lancerUnSort()';
}
else
{
  echo 'La classe Magicien n\'implémente pas de méthode lancerUnSort()';
}

Vous pouvez, comme pour les attributs, récupérer une méthode, et une sous-partie sera consacrée à la classe permettant d'obtenir des informations sur telle méthode.

Les constantes

Dans ce cas également, il est possible de savoir si telle classe possède telle constante. Ceci grâce à la méthodeReflectionClass::hasConstant($constName). Exemple :

<?php
if ($classePersonnage->hasConstant('NOUVEAU'))
{
  echo 'La classe Personnage possède une constante NOUVEAU';
}
else
{
  echo 'La classe Personnage ne possède pas de constante NOUVEAU';
}

Vous pouvez aussi récupérer la valeur de la constante grâce àReflectionClass::getConstant($constName):

<?php
if ($classePersonnage->hasConstant('NOUVEAU'))
{
  echo 'La classe Personnage possède une constante NOUVEAU (celle ci vaut ', $classePersonnage->getConstant('NOUVEAU'), ')';
}
else
{
  echo 'La classe Personnage ne possède pas de constante NOUVEAU';
}

Nous pouvons également retrouver la liste complète des constantes d'une classe sous forme de tableau grâce àReflectionClass::getConstants():

<?php
echo '<pre>', print_r($classePersonnage->getConstants(), true), '</pre>';

Les relations entre classes

L'héritage

Pour récupérer la classe parente de notre classe, nous allons regarder du côté deReflectionClass::getParentClass(). Cette méthode nous renvoie la classe parente s'il y en a une : la valeur de retour sera une instance de la classeReflectionClassqui représentera la classe parente ! Si la classe ne possède pas de parent, alors la valeur de retour serafalse.

Exemple :

<?php
$classeMagicien = new ReflectionClass('Magicien');

if ($parent = $classeMagicien->getParentClass())
{
  echo 'La classe Magicien a un parent : il s\'agit de la classe ', $parent->getName();
}
else
{
  echo 'La classe Magicien n\'a pas de parent';
}

Voici une belle occasion de vous présenter la méthodeReflectionClass::getName(). Cette méthode se contente de renvoyer le nom de la classe. Pour l'exemple avec le magicien, cela aurait été inutile puisque nous connaissions déjà le nom de la classe, mais ici nous ne le connaissons pas (quand je dis que nous ne le connaissons pas, cela signifie qu'il n'est pas déclaré explicitement dans les dernières lignes de code).

Dans le domaine de l'héritage, nous pouvons également citerReflectionClass::isSubclassOf($className). Cette méthode nous renvoie vrai si la classe spécifiée en paramètre est le parent de notre classe. Exemple :

<?php
if ($classeMagicien->isSubclassOf('Personnage'))
{
  echo 'La classe Magicien a pour parent la classe Personnage';
}
else
{
  echo 'La classe Magicien n\'a la classe Personnage pour parent';
}

Les deux prochaines méthodes que je vais vous présenter ne sont pas en rapport direct avec l'héritage mais sont cependant utilisées lorsque cette relation existe : il s'agit de savoir si la classe est abstraite ou finale. Nous avons pour cela les méthodesReflectionClass::isAbstract()etReflectionClass::isFinal(). Notre classePersonnageest abstraite, vérifions donc cela :

<?php
$classePersonnage = new ReflectionClass('Personnage');

// Est-elle abstraite ?
if ($classePersonnage->isAbstract())
{
  echo 'La classe Personnage est abstraite';
}
else
{
  echo 'La classe Personnage n\'est pas abstraite';
}

// Est-elle finale ?
if ($classePersonnage->isFinal())
{
  echo 'La classe Personnage est finale';
}
else
{
  echo 'La classe Personnage n\'est pas finale';
}

Dans le même genre,ReflectionClass::isInstantiable()permet également de savoir si notre classe est instanciable. Comme la classePersonnageest abstraite, elle ne peut pas l'être. Vérifions cela :

<?php
if ($classePersonnage->isInstantiable())
{
  echo 'La classe Personnage est instanciable';
}
else
{
  echo 'La classe personnage n\'est pas instanciable';
}

Bref, pas de grosse surprise. :)

Les interfaces

Voyons maintenant les méthodes en rapport avec les interfaces. Comme je vous l'ai déjà dit, une interface n'est autre qu'une classe entièrement abstraite : nous pouvons donc instancier la classeReflectionClassen spécifiant une interface en paramètre et vérifier si celle-ci est bien une interface grâce à la méthodeReflectionClass::isInterface().

<?php
$classeIMagicien = new ReflectionClass('iMagicien');

if ($classeIMagicien->isInterface())
{
   echo 'La classe iMagicien est une interface';
}
else
{
  echo 'La classe iMagicien n\'est pas une interface';
}

Vous pouvez aussi savoir si telle classe implémente telle interface grâce à la méthodeReflectionClass::implementsInterface($interfaceName). Exemple :

<?php
if ($classeMagicien->implementsInterface('iMagicien'))
{
  echo 'La classe Magicien implémente l\'interface iMagicien';
}
else
{
  echo 'La classe Magicien n\'implémente pas l\'interface iMagicien';
}

Il est aussi possible de récupérer toutes les interfaces implémentées, interfaces contenues dans un tableau. Pour cela, deux méthodes sont à votre disposition :ReflectionClass::getInterfaces()etReflectionClass::getInterfaceNames(). La première renvoie autant d'instances de la classeReflectionClassqu'il y a d'interfaces, chacune représentant une interface. La seconde méthode se contente uniquement de renvoyer un tableau contenant le nom de toutes les interfaces implémentées. Je pense qu'il est inutile de donner un exemple sur ce point-là. ;)

Obtenir des informations sur les attributs de ses classes

Nous avons assez parlé de la classe, intéressons-nous maintenant à ses attributs. La classe qui va nous permettre d'en savoir plus à leur sujet estReflectionProperty. Il y a deux moyens d'utiliser cette classe : l'instancier directement ou utiliser une méthode deReflectionClassqui nous renverra une instance deReflectionProperty.

Instanciation directe

L'appel du constructeur se fait en lui passant deux arguments. Le premier est le nom de la classe, et le second est le nom de l'attribut. Exemple :

<?php
$attributMagie = new ReflectionProperty('Magicien', 'magie');

Tout simplement. :)

Récupération d'attribut d'une classe

Récupérer un attribut

Pour récupérer un attribut d'une classe, nous aurons besoin de la méthodeReflectionClass::getProperty($attrName):

<?php
$classeMagicien = new ReflectionClass('Magicien');
$attributMagie = $classeMagicien->getProperty('magie');
Récupérer tous les attributs

Si vous souhaitez récupérer tous les attributs d'une classe, il va falloir se servir deReflectionClass::getProperties(). Le résultat retourné est un tableau contenant autant d'instances deReflectionPropertyque d'attributs.

<?php
$classePersonnage = new ReflectionClass('Personnage');
$attributsPersonnage = $classePersonnage->getProperties();

Après avoir vu comment récupérer un attribut, nous allons voir ce que l'on peut faire avec.

Le nom et la valeur des attributs

Afin de récupérer le nom de l'attribut, nous avons toujours la méthodeReflectionProperty::getName(). Pour obtenir la valeur de celui-ci, nous utiliserons la méthodeReflectionProperty::getValue($object). Nous devrons spécifier à cette dernière méthode l'instance dans laquelle nous voulons obtenir la valeur de l'attribut : chaque attribut est propre à chaque instance, ça n'aurait pas de sens de demander la valeur de l'attribut d'une classe.

Pour nous exercer, nous allons lister tous les attributs de la classeMagicien.

<?php
$classeMagicien = new ReflectionClass('Magicien');
$magicien = new Magicien(['nom' => 'vyk12', 'type' => 'magicien']);

foreach ($classeMagicien->getProperties() as $attribut)
{
  echo $attribut->getName(), ' => ', $attribut->getValue($magicien);
}

Il fonctionne pas ton code, j'ai une vieille erreur fatale qui me bloque tout...

En effet. Cette erreur fatale est levée car vous avez appelé la méthodeReflectionProperty::getValue()sur un attribut non public. Il faut donc rendre l'attribut accessible grâce àReflectionProperty::setAccessible($accessible), où$accessiblevaut vrai ou faux selon si vous voulez rendre l'attribut accessible ou non.

<?php
$classeMagicien = new ReflectionClass('Magicien');
$magicien = new Magicien(['nom' => 'vyk12', 'type' => 'magicien']);

foreach ($classeMagicien->getProperties() as $attribut)
{
  $attribut->setAccessible(true);
  echo $attribut->getName(), ' => ', $attribut->getValue($magicien);
}

Portée de l'attribut

Il est tout à fait possible de savoir si un attribut est privé, protégé ou public grâce aux méthodesReflectionProperty::isPrivate(),ReflectionProperty::isProtected()etReflectionProperty::isPublic().

<?php
$uneClasse = new ReflectionClass('MaClasse');

foreach ($uneClasse->getProperties() as $attribut)
{
  echo $attribut->getName(), ' => attribut ';
  
  if ($attribut->isPublic())
  {
    echo 'public';
  }
  elseif ($attribut->isProtected())
  {
    echo 'protégé';
  }
  else
  {
    echo 'privé';
  }
}

Il existe aussi une méthode permettant de savoir si l'attribut est statique ou non grâce àReflectionProperty::isStatic().

<?php
$uneClasse = new ReflectionClass('MaClasse');

foreach ($uneClasse->getProperties() as $attribut)
{
  echo $attribut->getName(), ' => attribut ';
  
  if ($attribut->isPublic())
  {
    echo 'public';
  }
  elseif ($attribut->isProtected())
  {
    echo 'protégé';
  }
  else
  {
    echo 'privé';
  }
  
  if ($attribut->isStatic())
  {
    echo ' (attribut statique)';
  }
}

Les attributs statiques

Le traitement d'attributs statiques diffère un peu dans le sens où ce n'est pas un attribut d'une instance mais un attribut de la classe. Ainsi, vous n'êtes pas obligés de spécifier d'instance lors de l'appel deReflectionProperty::getValue()car un attribut statique n'appartient à aucune instance.

<?php
class A
{
  public static $attr = 'Hello world !';
}

$classeA = new ReflectionClass('A');
echo $classeA->getProperty('attr')->getValue();

Au lieu d'utiliser cette façon de faire, vous pouvez directement appelerReflectionClass::getStaticPropertyValue($attr), où$attrest le nom de l'attribut. Dans le même genre, on peut citerReflectionClass::setStaticPropertyValue($attr, $value)$valueest la nouvelle valeur de l'attribut.

<?php
class A
{
  public static $attr = 'Hello world !';
}

$classeA = new ReflectionClass('A');
echo $classeA->getStaticPropertyValue('attr'); // Affiche Hello world !

$classeA->setStaticPropertyValue('attr', 'Bonjour le monde !');
echo $classeA->getStaticPropertyValue('attr'); // Affiche Bonjour le monde !

Vous avez aussi la possibilité d'obtenir tous les attributs statiques grâce àReflectionClass::getStaticProperties(). Le tableau retourné ne contient pas des instances deReflectionPropertymais uniquement les valeurs de chaque attribut.

<?php
class A
{
  public static $attr1 = 'Hello world !';
  public static $attr2 = 'Bonjour le monde !';
}

$classeA = new ReflectionClass('A');

foreach ($classeA->getStaticProperties() as $attr)
{
  echo $attr;
}

// À l'écran s'affichera Hello world ! Bonjour le monde !

Obtenir des informations sur les méthodes de ses classes

Voici la dernière classe faisant partie de l'API de réflexivité que je vais vous présenter : il s'agit deReflectionMethod. Comme vous l'aurez deviné, c'est grâce à celle-ci que l'on pourra obtenir des informations concernant telle ou telle méthode. Nous pourrons connaître la portée de la méthode (publique, protégée ou privée), si elle est statique ou non, abstraite ou finale, s'il s'agit du constructeur ou du destructeur et on pourra même l'appeler sur un objet.

Création d'une instance deReflectionMethod

Instanciation directe

Le constructeur deReflectionMethoddemande deux arguments : le nom de la classe et le nom de la méthode. Exemple :

<?php
class A
{
  public function hello($arg1, $arg2, $arg3 = 1, $arg4 = 'Hello world !')
  {
    echo 'Hello world !';
  }
}

$methode = new ReflectionMethod('A', 'hello');
Récupération d'une méthode d'une classe

La seconde façon de procéder est de récupérer la méthode de la classe grâce àReflectionClass::getMethod($name). Celle-ci renvoie une instance deReflectionMethodreprésentant la méthode.

<?php
class A
{
  public function hello($arg1, $arg2, $arg3 = 1, $arg4 = 'Hello world !')
  {
    echo 'Hello world !';
  }
}

$classeA = new ReflectionClass('A');
$methode = $classeA->getMethod('hello');

Publique, protégée ou privée ?

Comme pour les attributs, nous avons des méthodes pour le savoir : j'ai nomméReflectionMethod::isPublic(),ReflectionMethod::isProtected()etReflectionMethod::isPrivate(). Je ne vais pas m'étendre sur le sujet, vous savez déjà vous en servir !

<?php
$classeA = new ReflectionClass('A');
$methode = $classeA->getMethod('hello');

echo 'La méthode ', $methode->getName(), ' est ';

if ($methode->isPublic())
{
  echo 'publique';
}
elseif ($methode->isProtected())
{
  echo 'protégée';
}
else
{
  echo 'privée';
}

Je suis sûr que vous savez quelle méthode permet de savoir si elle est statique ou non. ;)

<?php
$classeA = new ReflectionClass('A');
$methode = $classeA->getMethod('hello');

echo 'La méthode ', $methode->getName(), ' est ';

if ($methode->isPublic())
{
  echo 'publique';
}
elseif ($methode->isProtected())
{
  echo 'protégée';
}
else
{
  echo 'privée';
}

if ($methode->isStatic())
{
  echo ' (en plus elle est statique)';
}

Abstraite ? Finale ?

Les méthodes permettant de savoir si une méthode est abstraite ou finale sont très simples à retenir : il s'agit deReflectionMethod::isAbstract()etReflectionMethod::isFinal().

<?php
$classeA = new ReflectionClass('A');
$methode = $classeA->getMethod('hello');

echo 'La méthode ', $methode->getName(), ' est ';

if ($methode->isAbstract())
{
  echo 'abstraite';
}
elseif ($methode->isFinal())
{
  echo 'finale';
}
else
{
  echo '« normale »';
}

Constructeur ? Destructeur ?

Dans le même genre,ReflectionMethod::isConstructor()etReflectionMethod::isDestructor()permettent de savoir si la méthode est le constructeur ou le destructeur de la classe.

<?php
$classeA = new ReflectionClass('A');
$methode = $classeA->getMethod('hello');

if ($methode->isConstructor())
{
  echo 'La méthode ', $methode->getName(), ' est le constructeur';
}
elseif ($methode->isDestructor())
{
  echo 'La méthode ', $methode->getName(), ' est le destructeur';
}

Appeler la méthode sur un objet

Pour réaliser ce genre de chose, nous allons avoir besoin deReflectionMethod::invoke($object, $args). Le premier argument est l'objet sur lequel on veut appeler la méthode. Viennent ensuite tous les arguments que vous voulez passer à la méthode : vous devrez donc passer autant d'arguments que la méthode appelée en exige. Prenons un exemple tout simple, vous comprendrez mieux :

<?php
class A
{
  public function hello($arg1, $arg2, $arg3 = 1, $arg4 = 'Hello world !')
  {
    var_dump($arg1, $arg2, $arg3, $arg4);
  }
}

$a = new A;
$hello = new ReflectionMethod('A', 'hello');

$hello->invoke($a, 'test', 'autre test'); // On ne va passer que deux arguments à notre méthode.

// A l'écran s'affichera donc :
// string(4) "test" string(10) "autre test" int(1) string(13) "Hello world !"

Une méthode semblable àReflectionMethod::invoke($object, $args)existe : il s'agit deReflectionMethod::invokeArgs($object, $args). La différence entre ces deux méthodes est que la seconde demandera les arguments listés dans un tableau au lieu de les lister en paramètres. L'équivalent du code précédent avecReflection::invokeArgs()serait donc le suivant :

<?php
class A
{
  public function hello($arg1, $arg2, $arg3 = 1, $arg4 = 'Hello world !')
  {
    var_dump($arg1, $arg2, $arg3, $arg4);
  }
}

$a = new A;
$hello = new ReflectionMethod('A', 'hello');

$hello->invokeArgs($a, ['test', 'autre test']); // Les deux arguments sont cette fois-ci contenus dans un tableau.

// Le résultat affiché est exactement le même.

Si vous n'avez pas accès à la méthode à cause de sa portée restreinte, vous pouvez la rendre accessible comme on l'a fait avec les attributs, grâce à la méthodeReflectionMethod::setAccessible($bool). Si$boolvauttrue, alors la méthode sera accessible, sinon elle ne le sera pas. Je me passe d'exemple, je suis sûr que vous trouverez tous seuls !

Utiliser des annotations

À présent, je vais vous montrer quelque chose d'intéressant qu'il est possible de faire grâce à la réflexivité : utiliser des annotations pour vos classes, méthodes et attributs, mais surtout y accéder durant l'exécution du script. Que sont les annotations ? Les annotations sont des méta-données relatives à la classe, méthode ou attribut, qui apportent des informations sur l'entité souhaitée. Elles sont insérées dans des commentaires utilisant le syntaxe doc block, comme ceci :

<?php
/**
 * @version 2.0
 */
class Personnage
{
  // ...
}

Les annotations s'insèrent à peu près de la même façon, mais la syntaxe est un peu différente. En effet, la syntaxe doit être précise pour qu'elle puisse être parsée par la bibliothèque que nous allons utiliser pour récupérer les données souhaitées.

Présentation d'addendum

Cette section aura pour but de présenter les annotations par le biais de la bibliothèque addendum qui parsera les codes pour en extraire les informations. Pour cela, commencez par télécharger addendum, et décompressez l'archive dans le dossier contenant votre projet.

Commençons par créer une classe sur laquelle nous allons travailler tout au long de cette partie, commePersonnage(à tout hasard). Avec addendum, toutes les annotations sont des classes héritant d'une classe de base :Annotation. Si nous voulons ajouter une annotation,Tablepar exemple, à notre classe pour spécifier à quelle table un objetPersonnagecorrespond, alors il faudra au préalable créer une classeTable.

<?php
class Table extends Annotation {}

À toute annotation correspond une valeur, valeur à spécifier lors de la déclaration de l'annotation :

<?php
/**
 * @Table("personnages")
 */
class Personnage
{

}

Nous venons donc de créer une annotation basique, mais concrètement, nous n'avons pas fait grand-chose. Nous allons maintenant voir comment récupérer cette annotation, et plus précisément la valeur qui lui est assignée, grâce à addendum.

À quoi servent-elles ces annotations ?

Les annotations sont surtout utilisées par les frameworks, comme PHPUnit (framework de tests unitaires) ou Zend Framework par exemple, ou bien les ORM tel que Doctrine, qui apportent ici des informations pour le mapping des classes. Vous n'aurez donc peut-être pas à utiliser les annotations dans vos scripts, mais il est important d'en avoir entendu parler si vous décidez d'utiliser des frameworks ou bibliothèques les utilisant.

Récupérer une annotation

Pour récupérer une annotation, il va d'abord falloir récupérer la classe via la bibliothèque en créant une instance deReflectionAnnotatedClass, comme nous l'avions fait en début de chapitre avecReflectionClass:

<?php
// On commence par inclure les fichiers nécessaires.
require 'addendum/annotations.php';
require 'MyAnnotations.php';
require 'Personnage.class.php';

$reflectedClass = new ReflectionAnnotatedClass('Personnage');

Maintenant que c'est fait, nous allons pouvoir récupérer l'annotation grâce à la méthodegetAnnotation. De manière générale, cette méthode retourne une instance deAnnotation. Dans notre cas, puisque nous voulons l'annotationTable, ce sera une instance deTablequi sera retournée. La valeur de l'annotation est contenue dans l'attributvalue, attribut public disponible dans toutes les classes filles deAnnotation:

<?php
// On commence par inclure les fichiers nécessaires.
require 'addendum/annotations.php';
require 'MyAnnotations.php';
require 'Personnage.class.php';

$reflectedClass = new ReflectionAnnotatedClass('Personnage');

echo 'La valeur de l\'annotation <strong>Table</strong> est <strong>', $reflectedClass->getAnnotation('Table')->value, '</strong>';

Il est aussi possible, pour une annotation, d'avoir un tableau comme valeur. Pour réaliser ceci, il faut mettre la valeur de l'annotation entre accolades et séparer les valeurs du tableau par des virgules :

<?php
/**
 * @Type({'brute', 'guerrier', 'magicien'})
 */
class Personnage
{

}

Si vous récupérez l'annotation, vous obtiendrez un tableau classique :

<?php
print_r($reflectedClass->getAnnotation('Type')->value); // Affiche le détail du tableau.

Vous pouvez aussi spécifier des clés pour les valeurs comme ceci :

<?php
/**
 * @Type({meilleur = 'magicien', 'moins bon' = 'brute', neutre = 'guerrier'})
 */
class Personnage
{

}

Enfin, pour finir avec les tableaux, je précise que vous pouvez en emboîter tant que vous voulez. Pour placer un tableau dans un autre, il suffit d'ouvrir une nouvelle paire d'accolades :

<?php
/**
 * @UneAnnotation({uneCle = 1337, {uneCle2 = true, uneCle3 = 'une valeur'}})
 */

Savoir si une classe possède telle annotation

Il est possible de savoir si une classe possède telle annotation grâce à la méthodehasAnnotation:

<?php
require 'addendum/annotations.php';
require 'MyAnnotations.php';
require 'Personnage.class.php';

$reflectedClass = new ReflectionAnnotatedClass('Personnage');

$ann = 'Table';
if ($reflectedClass->hasAnnotation($ann))
{
  echo 'La classe possède une annotation <strong>', $ann, '</strong> dont la valeur est <strong>', $reflectedClass->getAnnotation($ann)->value, '</strong><br />';
}

Une annotation à multiples valeurs

Il est possible pour une annotation de posséder plusieurs valeurs. Chacune de ces valeurs est stockée dans un attribut de la classe représentant l'annotation. Par défaut, une annotation ne contient qu'un attribut ($value) qui est la valeur de l'annotation.

Pour pouvoir assigner plusieurs valeurs à une annotation, il va donc falloir ajouter des attributs à notre classe. Commençons par ça :

<?php
class ClassInfos extends Annotation
{
  public $author;
  public $version;
}

Maintenant, tout se joue lors de la création de l'annotation. Pour assigner les valeurs souhaitées aux attributs, il suffit d'écrire ces valeurs précédées du nom de l'attribut. Exemple :

<?php
/**
 * @ClassInfos(author = "vyk12", version = "1.0")
 */
class Personnage
{

}

Pour accéder aux valeurs des attributs, il faut récupérer l'annotation, comme nous l'avons fait précédemment, et récupérer l'attribut.

<?php
$classInfos = $reflectedClass->getAnnotation('ClassInfos');

echo $classInfos->author;
echo $classInfos->version;

Le fait que les attributs soient publics peut poser quelques problèmes. En effet, de la sorte, nous ne pouvons pas être sûrs que les valeurs assignées soient correctes. Heureusement, la bibliothèque nous permet de pallier ce problème en réécrivant la méthodecheckConstraints()(déclarée dans sa classe mèreAnnotation) dans notre classe représentant l'annotation, appelée à chaque assignation de valeur, dans l'ordre dans lequel sont assignées les valeurs. Vous pouvez ainsi vérifier l'intégrité des données, et lancer une erreur si il y a un problème. Cette méthode prend un argument : la cible d'où provient l'annotation. Dans notre cas, l'annotation vient de notre classePersonnage, donc le paramètre sera une instance deReflectionAnnotatedClassreprésentantPersonnage. Vous verrez ensuite que cela peut être une méthode ou un attribut.

<?php
class ClassInfos extends Annotation
{
  public $author;
  public $version;
  
  public function checkConstraints($target)
  {
    if (!is_string($this->author))
    {
      throw new Exception('L\'auteur doit être une chaîne de caractères');
    }
    
    if (!is_numeric($this->version))
    {
      throw new Exception('Le numéro de version doit être un nombre valide');
    }
  }
}

Des annotations pour les attributs et méthodes

Jusqu'ici nous avons ajouté des annotations à une classe. Il est cependant possible d'en ajouter à des méthodes et attributs comme nous l'avons fait pour la classe :

<?php
/**
 * @Table("Personnages")
 * @ClassInfos(author = "vyk12", version = "1.0")
 */
class Personnage
{
  /**
   * @AttrInfos(description = 'Contient la force du personnage, de 0 à 100', type = 'int')
  */
  protected $force;
  
  /**
   * @ParamInfo(name = 'destination', description = 'La destination du personnage')
   * @ParamInfo(name = 'vitesse', description = 'La vitesse à laquelle se déplace le personnage')
   * @MethodInfos(description = 'Déplace le personnage à un autre endroit', return = true, returnDescription = 'Retourne true si le personnage peut se déplacer')
  */
  public function deplacer($destination, $vitesse)
  {
    // ...
  }
}

Pour récupérer une de ces annotations, il faut d'abord récupérer l'attribut ou la méthode. Nous allons pour cela se tourner versReflectionAnnotatedPropertyetReflectionAnnotatedMethod. Le constructeur de ces classes attend en premier paramètre le nom de la classe contenant l'élément et, en second, le nom de l'attribut ou de la méthode. Exemple :

<?php
$reflectedAttr = new ReflectionAnnotatedProperty('Personnage', 'force');
$reflectedMethod = new ReflectionAnnotatedMethod('Personnage', 'deplacer');

echo 'Infos concernant l\'attribut :';
var_dump($reflectedAttr->getAnnotation('AttrInfos'));

echo 'Infos concernant les paramètres de la méthode :';
var_dump($reflectedMethod->getAllAnnotations('ParamInfo'));

echo 'Infos concernant la méthode :';
var_dump($reflectedMethod->getAnnotation('MethodInfos'));

Notez ici l'utilisation deReflectionAnnotatedMethod::getAllAnnotations(). Cette méthode permet de récupérer toutes les annotations d'une entité correspondant au nom donné en argument. Si aucun nom n'est donné, alors toutes les annotations de l'entité seront retournées.

Contraindre une annotation à une cible précise

Grâce à une annotation un peu spéciale, vous avez la possibilité d'imposer un type de cible pour une annotation. En effet, jusqu'à maintenant, nos annotations pouvaient être utilisées aussi bien par des classes que par des attributs ou des méthodes. Dans le cas des annotationsClassInfos,AttrInfos,MethodInfosetParamInfos, cela présenterait un non-sens qu'elles puissent être utilisées par n'importe quel type d'élément.

Pour pallier ce problème, retournons à notre classeClassInfos. Pour dire à cette annotation qu'elle ne peut être utilisée que sur des classes, il faut utiliser l'annotation spéciale@Target:

<?php
/** @Target("class") */
class ClassInfos extends Annotation
{
  public $author;
  public $version;
}

À présent, essayez d'utiliser l'annotation@ClassInfossur un attribut ou une méthode et vous verrez qu'une erreur sera levée.
Cette annotation peut aussi prendre pour valeurproperty,methodounesty. Ce dernier type est un peu particulier et cette partie commençant déjà à devenir un peu imposante, j'ai décidé de ne pas en parler. Si cela vous intéresse, je ne peux que vous conseiller d'aller faire un tour sur la documentation d'addendum.

Je vous ai aussi volontairement caché une classe : la classeReflectionParameterqui vous permet d'obtenir des informations sur les paramètres de vos méthodes. Je vous laisse vous documenter à ce sujet, son utilisation est très simple. :)

En résumé

  • Il est possible d'obtenir des informations sur ses classes, attributs et méthodes, respectivement grâce àReflectionClass,ReflectionPropertyetReflectionMethod.

  • Utiliser des annotations sur ses classes permet, grâce à une bibliothèque telle qu'addendum, de récupérer dynamiquement leurs contenus.

  • L'utilisation d'annotations dans un but de configuration dynamique est utilisée par certains frameworks ou ORM tel que Doctrine par exemple, ce qui permet d'économiser un fichier de configuration en plaçant la description des tables et des colonnes directement dans des annotations.

Exemple de certificat de réussite
Exemple de certificat de réussite