Réflexion en PHP

La réflexion est généralement définie comme la capacité d'un programme à s'auto-inspecter et à modifier sa logique au moment de l'exécution. En termes moins techniques, la réflexion consiste à demander à un objet de vous informer sur ses propriétés et méthodes et à modifier ses membres (même privés). Dans cette leçon, nous allons voir comment cela est accompli et quand cela pourrait s’avérer utile..


Un peu d'histoire

À l'aube de l'ère de la programmation, il y avait le langage d'assemblage. Un programme écrit en assembleur réside sur des registres physiques à l'intérieur de l'ordinateur. Sa composition, ses méthodes et ses valeurs peuvent être consultées à tout moment en lisant les registres. Encore plus, vous pouvez modifier le programme en cours d’exécution en modifiant simplement ces registres. Cela nécessitait une connaissance intime du programme en cours, mais il était intrinsèquement réflexif.

Comme avec tout jouet cool, utilisez la réflexion, mais n'en abusez pas.

Au fur et à mesure que les langages de programmation de niveau supérieur (comme C) sont apparus, cette réflectivité s'est estompée et a disparu. Il a ensuite été réintroduit avec la programmation orientée objet.

Aujourd'hui, la plupart des langages de programmation peuvent utiliser la réflexion. Les langages à typage statique, tels que Java, posent peu ou pas de problèmes de réflexion. Ce que je trouve intéressant, cependant, est que tout langage à typage dynamique (comme PHP ou Ruby) est fortement basé sur la réflexion. Sans le concept de réflexion, le typage de canard serait probablement impossible à mettre en œuvre. Lorsque vous envoyez un objet à un autre (un paramètre, par exemple), l'objet destinataire n'a aucun moyen de connaître la structure et le type de cet objet. Tout ce qu’il peut faire est d’utiliser la réflexion pour identifier les méthodes qui peuvent et ne peuvent pas être appelées sur l’objet reçu..


Un exemple simple

La réflexion est répandue en PHP. En fait, il existe plusieurs situations où vous pouvez l’utiliser sans même le savoir. Par exemple:

 // Nettuts.php require_once 'Editor.php'; class Nettuts function publishNextArticle () $ editor = new Editor ('John Doe'); $ editor-> setNextArticle ('135523'); $ editor-> publish (); 

Et:

 // Editor.php class Editor nom privé $ nom; public $ articleId; fonction __construct ($ name) $ this-> name = $ name;  fonction publique setNextArticle ($ articleId) $ this-> articleId = $ articleId;  fonction publique publish () // la logique de publication va ici return true; 

Dans ce code, nous appelons directement une variable initialisée localement avec un type connu. Création de l'éditeur dans publishNextArticle () il est évident que le éditeur $ la variable est de type Éditeur. Aucune réflexion n’est nécessaire ici, mais introduisons une nouvelle classe, appelée Directeur:

 // Manager.php require_once './Editor.php'; require_once './Nettuts.php'; class manager function doJobFor (DateTime $ date) if ((new DateTime ()) -> getTimestamp ()> $ date-> getTimestamp ()) $ editor = nouvel éditeur ('John Doe'); $ nettuts = new Nettuts (); $ nettuts-> publishNextArticle ($ editor); 

Ensuite, modifiez Nettuts, ainsi:

 // Nettuts.php class Nettuts function publishNextArticle ($ editor) $ editor-> setNextArticle ('135523'); $ editor-> publish (); 

À présent, Nettuts n'a absolument aucun rapport avec le Éditeur classe. Il n'inclut pas son fichier, il n'initialise pas sa classe et il ne sait même pas qu'il existe. Je pourrais passer un objet de tout type dans le publishNextArticle () méthode et le code fonctionnerait.


Comme vous pouvez le voir sur ce diagramme de classes, Nettuts a seulement une relation directe avec Directeur. Directeur le crée, et donc, Directeur dépend de Nettuts. Mais Nettuts n'a plus aucun rapport avec le Éditeur classe, et Éditeur est seulement lié à Directeur.

À l'exécution, Nettuts utilise un Éditeur objet, donc le <> et le point d'interrogation. Au moment de l'exécution, PHP inspecte l'objet reçu et vérifie qu'il implémente le setNextArticle () et publier() les méthodes.

Informations sur le membre de l'objet

Nous pouvons faire en sorte que PHP affiche les détails d'un objet. Créons un test PHPUnit pour nous aider à exercer facilement notre code:

 // ReflectionTest.php require_once '… /Editor.php'; require_once '… /Nettuts.php'; La classe ReflectionTest étend PHPUnit_Framework_TestCase function testItCanReflect () $ editor = new Editor ('John Doe'); $ tuts = new Nettuts (); $ tuts-> publishNextArticle ($ editor); 

Maintenant, ajoutez un var_dump () à Nettuts:

 // Nettuts.php class NetTuts function publishNextArticle ($ editor) $ editor-> setNextArticle ('135523'); $ editor-> publish (); var_dump (new ReflectionClass ($ editor)); 

Exécutez le test et observez la magie se dérouler dans la sortie:

PHPUnit 3.6.11 de Sebastian Bergmann… object (ReflectionClass) # 197 (1) ["name"] => string (6) "Editor" Temps: 0 secondes, Mémoire: 2,25 Mo OK (1 test, 0 assertions)

Notre classe de réflexion a un prénom propriété définie sur le type d'origine du éditeur $ variable: Éditeur, mais ce n'est pas beaucoup d'informations. Qu'en est-il de Éditeurles méthodes de?

 // Nettuts.php class Nettuts function publishNextArticle ($ editor) $ editor-> setNextArticle ('135523'); $ editor-> publish (); $ réflecteur = new ReflectionClass ($ editor); var_dump ($ reflector-> getMethods ()); 

Dans ce code, nous affectons l'instance de la classe de réflexion à la $ réflecteur variable afin que nous puissions maintenant déclencher ses méthodes. ReflectionClass expose un grand ensemble de méthodes que vous pouvez utiliser pour obtenir les informations d'un objet. Une de ces méthodes est getMethods (), qui retourne un tableau contenant les informations de chaque méthode.

 PHPUnit 3.6.11 de Sebastian Bergmann… array (3) [0] => & object (ReflectionMethod) # 196 (2) ["name"] => string (11) "__construct" ["class"] => string (6) "Éditeur" [1] => & objet (ReflectionMethod) # 195 (2) ["nom"] => chaîne (14) "setNextArticle" ["classe"] => chaîne (6) "Éditeur"  [2] => & object (ReflectionMethod) # 194 (2) ["name"] => string (7) "publish" ["class"] => string (6) "Editor" Durée: 0 secondes , Mémoire: 2.25Mb OK (1 test, 0 assertions)

Une autre méthode, getProperties (), récupère les propriétés (même les propriétés privées!) de l'objet:

 PHPUnit 3.6.11 de Sebastian Bergmann… array (2) [0] => & object (ReflectionProperty) # 196 (2) ["name"] => string (4) "name" ["class"] => string (6) "Éditeur" [1] => & objet (ReflectionProperty) # 195 (2) ["nom"] => chaîne (9) "articleId" ["classe"] => chaîne (6) "Éditeur"  Temps: 0 secondes, mémoire: 2,25 Mo OK (1 test, 0 assertions)

Les éléments des tableaux renvoyés de getMethod () et getProperties () sont de type Méthode de réflexion et ReflectionProperty, respectivement; ces objets sont très utiles:

 // Nettuts.php class Nettuts function publishNextArticle ($ editor) $ editor-> setNextArticle ('135523'); $ editor-> publish (); // premier appel à publish () $ reflector = new ReflectionClass ($ editor); $ publishMethod = $ reflector-> getMethod ('publish'); $ publishMethod-> invoke ($ editor); // deuxième appel à publish ()

Ici, nous utilisons getMethod () pour récupérer une méthode unique avec le nom de "publier"; le résultat est un Méthode de réflexion objet. Ensuite, nous appelons le invoquer() méthode, en passant le éditeur $ objet, afin d'exécuter l'éditeur publier() méthode une seconde fois.

Ce processus était simple dans notre cas, car nous avions déjà un Éditeur objet à passer à invoquer(). Nous pouvons avoir plusieurs Éditeur objets dans certaines circonstances, nous donnant le luxe de choisir quel objet utiliser. Dans d’autres circonstances, nous n’avons peut-être aucun objet avec lequel travailler, auquel cas nous aurions besoin de l’obtenir auprès de ReflectionClass.

Modifions Éditeurde publier() méthode pour démontrer le double appel:

 // Editor.php class Editor […] fonction publique publish () // la logique de publication va ici echo ("HERE \ n"); retourne vrai; 

Et la nouvelle sortie:

 PHPUnit 3.6.11 par Sebastian Bergmann… ICI ICI Temps: 0 secondes, mémoire: 2,25 Mo OK (1 test, 0 assertions)

Manipulation des données d'instance

Nous pouvons également modifier le code au moment de l'exécution. Qu'en est-il de la modification d'une variable privée sans paramètre de définition publique? Ajoutons une méthode à Éditeur qui récupère le nom de l'éditeur:

 // Editor.php class Editor nom privé $ nom; public $ articleId; fonction __construct ($ name) $ this-> name = $ name;  […] Function getEditorName () return $ this-> name; 

Cette nouvelle méthode s'appelle, getEditorName (), et renvoie simplement la valeur du privé $ name variable. le $ name variable est définie au moment de la création, et nous n’avons aucune méthode publique nous permettant de la changer. Mais nous pouvons accéder à cette variable en utilisant la réflexion. Vous pouvez d’abord essayer l’approche la plus évidente:

 // Nettuts.php class Nettuts function publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ réflecteur = new ReflectionClass ($ editor); $ editorName = $ reflector-> getProperty ('name'); $ editorName-> getValue ($ editor); 

Même si cela affiche la valeur à la var_dump () line, une erreur est générée lors de la tentative de récupération de la valeur avec réflexion:

PHPUnit 3.6.11 de Sebastian Bergmann. Estring (8) "John Doe" Durée: 0 secondes, Mémoire: 2.50 Mo Il y avait 1 erreur: 1) ReflectionTest :: testItCanReflect ReflectionException: Impossible d'accéder aux membres non publics Editor :: name […] / Reflection en PHP / Source / NetTuts.php: 13 […] / Reflection en PHP / Source / Tests / ReflectionTest.php: 13 / usr / bin / phpunit: 46 FAILURES! Tests: 1, Assertions: 0, Erreurs: 1.

Afin de résoudre ce problème, nous devons demander au ReflectionProperty object pour nous donner accès aux variables et méthodes privées:

// Nettuts.php class Nettuts function publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ réflecteur = new ReflectionClass ($ editor); $ editorName = $ reflector-> getProperty ('name'); $ editorName-> setAccessible (true); var_dump ($ editorName-> getValue ($ editor)); 

Appel setAccessible () et en passant vrai fait le tour:

PHPUnit 3.6.11 de Sebastian Bergmann… string (8) "John Doe" string (8) "John Doe" Durée: 0 secondes, Mémoire: 2,25 Mo OK (1 test, 0 assertions)

Comme vous pouvez le constater, nous avons réussi à lire une variable privée. La première ligne de sortie provient du propre objet getEditorName () méthode, et le second vient de la réflexion. Mais qu'en est-il de changer la valeur d'une variable privée? Utilisez le setValue () méthode:

// Nettuts.php class Nettuts function publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ réflecteur = new ReflectionClass ($ editor); $ editorName = $ reflector-> getProperty ('name'); $ editorName-> setAccessible (true); $ editorName-> setValue ($ editor, 'Mark Twain'); var_dump ($ editorName-> getValue ($ editor)); 

Et c'est tout. Ce code change "John Doe" en "Mark Twain".

PHPUnit 3.6.11 de Sebastian Bergmann… string (8) "John Doe" string (10) "Mark Twain" Durée: 0 secondes, Mémoire: 2,25 Mo OK (1 test, 0 assertions)

Utilisation de la réflexion indirecte

Certaines fonctionnalités intégrées de PHP utilisent indirectement la réflexion, l’une étant la call_user_func () une fonction.

Le rappel

le call_user_func () function accepte un tableau: le premier élément pointant sur un objet et le second sur le nom d'une méthode. Vous pouvez fournir un paramètre facultatif, qui est ensuite transmis à la méthode appelée. Par exemple:

 // Nettuts.php class Nettuts function publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ réflecteur = new ReflectionClass ($ editor); $ editorName = $ reflector-> getProperty ('name'); $ editorName-> setAccessible (true); $ editorName-> setValue ($ editor, 'Mark Twain'); var_dump ($ editorName-> getValue ($ editor)); var_dump (call_user_func (array ($ editor, 'getEditorName'))); 

La sortie suivante montre que le code récupère la valeur appropriée:

PHPUnit 3.6.11 de Sebastian Bergmann… chaîne (8) Chaîne "John Doe" chaîne (10) Chaîne "Mark Twain" chaîne (10) "Mark Twain" Durée: 0 secondes, Mémoire: 2,25 Mo OK (1 test, 0 assertions)

Utiliser la valeur d'une variable

Un autre exemple de réflexion indirecte consiste à appeler une méthode à l'aide de la valeur contenue dans une variable, par opposition à l'appel direct. Par exemple:

 // Nettuts.php class Nettuts function publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ réflecteur = new ReflectionClass ($ editor); $ editorName = $ reflector-> getProperty ('name'); $ editorName-> setAccessible (true); $ editorName-> setValue ($ editor, 'Mark Twain'); var_dump ($ editorName-> getValue ($ editor)); $ methodName = 'getEditorName'; var_dump ($ editor -> $ methodName ()); 

Ce code produit la même sortie que dans l'exemple précédent. PHP remplace simplement la variable par la chaîne qu'il représente et appelle la méthode. Cela fonctionne même lorsque vous voulez créer des objets en utilisant des variables pour les noms de classe.


Quand devrions-nous utiliser la réflexion?

Maintenant que nous avons mis les détails techniques derrière nous, quand devrions-nous tirer parti de la réflexion? Voici quelques scénarios:

  • Frappe dynamique est probablement impossible sans réflexion.
  • Programmation Orientée Aspect écoute les appels de méthode et place le code autour des méthodes, le tout réalisé avec la réflexion.
  • PHPUnit repose fortement sur la réflexion, comme le font les autres cadres moqueurs.
  • Cadres Web en général utiliser la réflexion à des fins différentes. Certains l'utilisent pour initialiser des modèles, construisant des objets pour des vues, etc. Laravel fait un usage intensif de la réflexion pour injecter des dépendances.
  • Métaprogrammation, comme notre dernier exemple, est une réflexion cachée.
  • Cadres d'analyse de code utiliser la réflexion pour comprendre votre code.

Dernières pensées

Comme avec n'importe quel jouet cool, utilisez la réflexion, mais n'en abusez pas. La réflexion est coûteuse lorsque vous inspectez de nombreux objets et peut compliquer l'architecture et la conception de votre projet. Je vous recommande de ne l'utiliser que si cela vous donne un avantage ou si vous n'avez aucune autre option viable.

Personnellement, je n’ai utilisé la réflexion que dans quelques cas, le plus souvent lors de l’utilisation de modules tiers dépourvus de documentation. Je me trouve fréquemment en utilisant un code similaire au dernier exemple. Il est facile d'appeler la méthode appropriée lorsque votre MVC répond avec une variable contenant les valeurs "add" ou "remove".

Merci d'avoir lu!