Maison >développement back-end >tutoriel php >Mécanisme de réflexion en PHP
La réflexion est généralement définie comme la capacité d'un programme à s'inspecter et à modifier sa logique lors de son exécution. En termes moins techniques, la réflexion consiste à demander à un objet de vous indiquer ses propriétés et ses méthodes, et à modifier ces membres (même les membres privés). Dans ce cours, nous examinerons de plus près comment y parvenir et quand cela peut être utile.
Au début de l'ère de la programmation, le langage assembleur est apparu. Les programmes écrits en langage assembleur résident dans des registres physiques à l’intérieur de l’ordinateur. Sa composition, ses modalités et ses valeurs peuvent être vérifiées à tout moment par la lecture des registres. De plus, vous pouvez changer votre programme en modifiant simplement ces registres pendant son exécution. Cela nécessite une connaissance approfondie du programme en cours d’exécution, mais il est de nature réfléchie.
Comme pour tout jouet cool, utilisez la réflexion, mais n'en abusez pas.
Avec l'avènement des langages de programmation de haut niveau comme le C, cette réflexivité a progressivement disparu. Plus tard, il a été réintroduit grâce à la programmation orientée objet.
Reflection est disponible dans la plupart des langages de programmation de nos jours. Les langages typés statiquement tels que Java ont peu de problèmes de réflexion. Cependant, je trouve intéressant que tout langage typé dynamiquement comme PHP ou Ruby soit fortement basé sur la réflexion. Sans le concept de réflexion, la saisie au canard ne serait probablement pas possible. Lorsque vous envoyez un objet à un autre objet (comme un paramètre), l'objet récepteur n'a aucun moyen de connaître la structure et le type de l'objet. Tout ce qu'il peut faire, c'est 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 courante en PHP. En fait, il existe plusieurs situations dans lesquelles vous pourriez l’utiliser sans le savoir. Par exemple :
// Nettuts.php require_once 'Editor.php'; class Nettuts { function publishNextArticle() { $editor = new Editor('John Doe'); $editor->setNextArticle('135523'); $editor->publish(); } }
Aussi :
// Editor.php class Editor { private $name; public $articleId; function __construct($name) { $this->name = $name; } public function setNextArticle($articleId) { $this->articleId = $articleId; } public function publish() { // publish logic goes here return true; } }
Dans ce code, nous appelons directement une variable locale initialisée de type connu. en publishNextArticle()
中创建编辑器,可以明显看出 $editor
变量的类型为 Editor
。这里不需要反射,但是我们引入一个新类,名为Manager
:
// Manager.php require_once './Editor.php'; require_once './Nettuts.php'; class Manager { function doJobFor(DateTime $date) { if ((new DateTime())->getTimestamp() > $date->getTimestamp()) { $editor = new Editor('John Doe'); $nettuts = new Nettuts(); $nettuts->publishNextArticle($editor); } } }
Ensuite, modifiez Nettuts
comme suit :
// Nettuts.php class Nettuts { function publishNextArticle($editor) { $editor->setNextArticle('135523'); $editor->publish(); } }
Maintenant, Nettuts
与 Editor
类完全没有关系。它不包含它的文件,它不初始化它的类,它甚至不知道它的存在。我可以将任何类型的对象传递到 publishNextArticle()
n'a absolument rien à voir avec la classe Editor
. Il ne contient pas ses fichiers, il n'initialise pas ses classes, il ne sait même pas qu'il existe. Je peux passer n'importe quel type d'objet dans la méthode publishNextArticle()
et le code fonctionnera.
Comme vous pouvez le voir sur ce diagramme de classes, Nettuts
只与Manager
有直接关系。 Manager
创建它,因此 Manager
依赖于 Nettuts
。但是 Nettuts
不再与 Editor
类有任何关系,并且 Editor
仅与 Manager
n'est que directement lié au Manager
. Manager
le crée, donc Manager
dépend de
n'a plus rien à voir avec la classe Editor
, et Editor
n'est lié qu'à Manager
. Nettuts
使用 Editor
对象,因此有 > 和问号。在运行时,PHP 检查接收到的对象并验证它是否实现了 setNextArticle()
和 publish()
Editor
, d'où les > et le point d'interrogation. Au moment de l'exécution, PHP examine l'objet reçu et vérifie s'il implémente les méthodes setNextArticle()
et publish()
. Informations sur les membres de l'objet
Nous pouvons laisser PHP afficher les détails de l'objet. Créons un test PHPUnit pour nous aider à tester facilement notre code : var_dump()
添加到 Nettuts
// ReflectionTest.php require_once '../Editor.php'; require_once '../Nettuts.php'; class ReflectionTest extends PHPUnit_Framework_TestCase { function testItCanReflect() { $editor = new Editor('John Doe'); $tuts = new Nettuts(); $tuts->publishNextArticle($editor); } }Maintenant, ajoutez
var_dump()
à :
// Nettuts.php class NetTuts { function publishNextArticle($editor) { $editor->setNextArticle('135523'); $editor->publish(); var_dump(new ReflectionClass($editor)); } }
Exécutez le test et regardez la magie opérer dans le résultat : name
属性,设置为 $editor
变量的原始类型:Editor
,但这并不是太多信息。 Editor
PHPUnit 3.6.11 by Sebastian Bergmann. .object(ReflectionClass)#197 (1) { ["name"]=> string(6) "Editor" } Time: 0 seconds, Memory: 2.25Mb OK (1 test, 0 assertions)Que diriez-vous que notre cours de réflexion ait une méthode
? $reflector
变量,以便我们现在可以触发其方法。 ReflectionClass
公开了大量可用于获取对象信息的方法。其中一个方法是 getMethods()
// Nettuts.php class Nettuts { function publishNextArticle($editor) { $editor->setNextArticle('135523'); $editor->publish(); $reflector = new ReflectionClass($editor); var_dump($reflector->getMethods()); } }Dans ce code, nous attribuons une instance de la classe de réflexion à
qui renvoie un tableau contenant des informations pour chaque méthode. getProperties()
PHPUnit 3.6.11 by Sebastian Bergmann. .array(3) { [0]=> &object(ReflectionMethod)#196 (2) { ["name"]=> string(11) "__construct" ["class"]=> string(6) "Editor" } [1]=> &object(ReflectionMethod)#195 (2) { ["name"]=> string(14) "setNextArticle" ["class"]=> string(6) "Editor" } [2]=> &object(ReflectionMethod)#194 (2) { ["name"]=> string(7) "publish" ["class"]=> string(6) "Editor" } } Time: 0 seconds, Memory: 2.25Mb OK (1 test, 0 assertions)Une autre méthode,
, récupère les propriétés d'un objet (même les propriétés privées !) : getMethod()
和 getProperties()
返回的数组中的元素分别为 ReflectionMethod
和 ReflectionProperty
PHPUnit 3.6.11 by Sebastian Bergmann. .array(2) { [0]=> &object(ReflectionProperty)#196 (2) { ["name"]=> string(4) "name" ["class"]=> string(6) "Editor" } [1]=> &object(ReflectionProperty)#195 (2) { ["name"]=> string(9) "articleId" ["class"]=> string(6) "Editor" } } Time: 0 seconds, Memory: 2.25Mb OK (1 test, 0 assertions)Les éléments des tableaux renvoyés par
getMethod()
et sont respectivement de type ReflectionMethod
et ReflectionProperty
. Ces objets sont très utiles : getMethod()
来检索名称为“publish”的单个方法;其结果是 ReflectionMethod
对象。然后,我们调用 invoke()
方法,并向其传递 $editor
对象,以便再次执行编辑器的 publish()
// Nettuts.php class Nettuts { function publishNextArticle($editor) { $editor->setNextArticle('135523'); $editor->publish(); // first call to publish() $reflector = new ReflectionClass($editor); $publishMethod = $reflector->getMethod('publish'); $publishMethod->invoke($editor); // second call to publish() } }Ici, nous utilisons la méthode 🎜. 🎜
在我们的例子中,这个过程很简单,因为我们已经有一个 Editor
对象传递给 invoke()
。在某些情况下,我们可能有多个 Editor
对象,这使我们可以自由选择使用哪个对象。在其他情况下,我们可能没有可以使用的对象,在这种情况下,我们需要从 ReflectionClass
获取一个对象。
我们来修改Editor
的publish()
方法来演示双重调用:
// Editor.php class Editor { [ ... ] public function publish() { // publish logic goes here echo ("HERE\n"); return true; } }
新的输出:
PHPUnit 3.6.11 by Sebastian Bergmann. .HERE HERE Time: 0 seconds, Memory: 2.25Mb OK (1 test, 0 assertions)
我们还可以在执行时修改代码。修改没有公共设置器的私有变量怎么样?让我们向 Editor
添加一个方法来检索编辑器的名称:
// Editor.php class Editor { private $name; public $articleId; function __construct($name) { $this->name = $name; } [ ... ] function getEditorName() { return $this->name; } }
这个新方法被称为 getEditorName()
,并且仅返回私有 $name
变量的值。 $name
变量是在创建时设置的,我们没有公共方法可以让我们更改它。但我们可以使用反射来访问这个变量。您可能首先尝试更明显的方法:
// Nettuts.php class Nettuts { function publishNextArticle($editor) { var_dump($editor->getEditorName()); $reflector = new ReflectionClass($editor); $editorName = $reflector->getProperty('name'); $editorName->getValue($editor); } }
尽管这会在 var_dump()
行输出值,但在尝试通过反射检索该值时会引发错误:
PHPUnit 3.6.11 by Sebastian Bergmann. Estring(8) "John Doe" Time: 0 seconds, Memory: 2.50Mb There was 1 error: 1) ReflectionTest::testItCanReflect ReflectionException: Cannot access non-public member Editor::name [...]/Reflection in PHP/Source/NetTuts.php:13 [...]/Reflection in PHP/Source/Tests/ReflectionTest.php:13 /usr/bin/phpunit:46 FAILURES! Tests: 1, Assertions: 0, Errors: 1.
为了解决这个问题,我们需要请求 ReflectionProperty
对象授予我们访问私有变量和方法的权限:
// Nettuts.php class Nettuts { function publishNextArticle($editor) { var_dump($editor->getEditorName()); $reflector = new ReflectionClass($editor); $editorName = $reflector->getProperty('name'); $editorName->setAccessible(true); var_dump($editorName->getValue($editor)); } }
调用 setAccessible()
并传递 true
可以解决问题:
PHPUnit 3.6.11 by Sebastian Bergmann. .string(8) "John Doe" string(8) "John Doe" Time: 0 seconds, Memory: 2.25Mb OK (1 test, 0 assertions)
如您所见,我们已成功读取私有变量。第一行输出来自对象自己的 getEditorName()
方法,第二行来自反射。但是改变私有变量的值又如何呢?使用 setValue()
方法:
// Nettuts.php class Nettuts { function publishNextArticle($editor) { var_dump($editor->getEditorName()); $reflector = new ReflectionClass($editor); $editorName = $reflector->getProperty('name'); $editorName->setAccessible(true); $editorName->setValue($editor, 'Mark Twain'); var_dump($editorName->getValue($editor)); } }
就是这样。此代码将“John Doe”更改为“Mark Twain”。
PHPUnit 3.6.11 by Sebastian Bergmann. .string(8) "John Doe" string(10) "Mark Twain" Time: 0 seconds, Memory: 2.25Mb OK (1 test, 0 assertions)
PHP 的一些内置功能间接使用反射,其中一个是 call_user_func()
函数。
call_user_func()
函数接受一个数组:第一个元素指向对象,第二个元素指向方法的名称。您可以提供一个可选参数,然后将其传递给被调用的方法。例如:
// Nettuts.php class Nettuts { function publishNextArticle($editor) { var_dump($editor->getEditorName()); $reflector = 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'))); } }
以下输出表明代码检索了正确的值:
PHPUnit 3.6.11 by Sebastian Bergmann. .string(8) "John Doe" string(10) "Mark Twain" string(10) "Mark Twain" Time: 0 seconds, Memory: 2.25Mb OK (1 test, 0 assertions)
间接反射的另一个示例是通过变量中包含的值来调用方法,而不是直接调用它。例如:
// Nettuts.php class Nettuts { function publishNextArticle($editor) { var_dump($editor->getEditorName()); $reflector = 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()); } }
此代码产生与前面示例相同的输出。 PHP 只是用它所代表的字符串替换该变量并调用该方法。当您想通过使用类名变量来创建对象时,它甚至可以工作。
现在我们已经把技术细节抛在脑后了,我们什么时候应该利用反射呢?以下是一些场景:
与任何很酷的玩具一样,使用反射,但不要滥用它。当您检查许多对象时,反射的成本很高,并且有可能使项目的架构和设计变得复杂。我建议您仅在它确实为您带来优势或没有其他可行选择时才使用它。
就我个人而言,我只在少数情况下使用过反射,最常见的是在使用缺乏文档的第三方模块时。我发现自己经常使用与上一个示例类似的代码。当您的 MVC 使用包含“添加”或“删除”值的变量进行响应时,调用正确的方法很容易。
感谢您的阅读!
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!