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..
À 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..
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 <setNextArticle ()
et publier()
les méthodes.
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 Éditeur
les 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 Éditeur
de 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)
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)
Certaines fonctionnalités intégrées de PHP utilisent indirectement la réflexion, l’une étant la call_user_func ()
une fonction.
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)
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.
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:
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!