反射通常被定义为程序在执行时检查自身并修改其逻辑的能力。用不太专业的术语来说,反射是要求一个对象告诉您它的属性和方法,并更改这些成员(甚至是私有成员)。在本课程中,我们将深入探讨如何实现这一点,以及它何时可能有用。
在编程时代的初期,出现了汇编语言。用汇编语言编写的程序驻留在计算机内部的物理寄存器中。通过读取寄存器可以随时检查其组成、方法和值。更重要的是,您可以在程序运行时通过简单地修改这些寄存器来更改程序。它需要对正在运行的程序有一些深入的了解,但它本质上是反思性的。
与任何很酷的玩具一样,使用反射,但不要滥用它。
随着高级编程语言(如 C)的出现,这种反射性逐渐消失。后来它通过面向对象编程重新引入。
如今,大多数编程语言都可以使用反射。静态类型语言(例如 Java)在反射方面几乎没有问题。然而,我发现有趣的是,任何动态类型语言(如 PHP 或 Ruby)都很大程度上基于反射。如果没有反射的概念,鸭子类型很可能无法实现。当您将一个对象发送到另一个对象(例如参数)时,接收对象无法知道该对象的结构和类型。它所能做的就是使用反射来识别可以在接收到的对象上调用和不能调用的方法。
反射在 PHP 中很普遍。事实上,有几种情况您可能会在不知情的情况下使用它。例如:
// Nettuts.php require_once 'Editor.php'; class Nettuts { function publishNextArticle() { $editor = new Editor('John Doe'); $editor->setNextArticle('135523'); $editor->publish(); } }
还有:
// 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; } }
在此代码中,我们直接调用具有已知类型的本地初始化变量。在 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); } } }
接下来,修改 Nettuts
,如下所示:
// Nettuts.php class Nettuts { function publishNextArticle($editor) { $editor->setNextArticle('135523'); $editor->publish(); } }
现在,Nettuts
与 Editor
类完全没有关系。它不包含它的文件,它不初始化它的类,它甚至不知道它的存在。我可以将任何类型的对象传递到 publishNextArticle()
与 Editor
类完全没有关系。它不包含它的文件,它不初始化它的类,它甚至不知道它的存在。我可以将任何类型的对象传递到 publishNextArticle()
方法中,代码就可以工作。
从这个类图中可以看到,Nettuts
只与Manager
有直接关系。 Manager
创建它,因此 Manager
依赖于 Nettuts
。但是 Nettuts
不再与 Editor
类有任何关系,并且 Editor
仅与 Manager
只与Manager
有直接关系。 Manager
创建它,因此 Manager
依赖于
不再与 Editor
类有任何关系,并且 Editor
仅与 Manager
相关。Nettuts
使用 Editor
对象,因此有 <setNextArticle()
和 publish()
Editor
对象,因此有 <setNextArticle()
和 publish()
方法。对象成员信息
我们可以让 PHP 显示对象的详细信息。让我们创建一个 PHPUnit 测试来帮助我们轻松地测试我们的代码: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); } }
var_dump()
添加到 :
// Nettuts.php class NetTuts { function publishNextArticle($editor) { $editor->setNextArticle('135523'); $editor->publish(); var_dump(new ReflectionClass($editor)); } }
运行测试,并观察输出中发生的奇迹: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)
的方法怎么样?$reflector
变量,以便我们现在可以触发其方法。 ReflectionClass
公开了大量可用于获取对象信息的方法。其中一个方法是 getMethods()
// Nettuts.php class Nettuts { function publishNextArticle($editor) { $editor->setNextArticle('135523'); $editor->publish(); $reflector = new ReflectionClass($editor); var_dump($reflector->getMethods()); } }
,它返回一个包含每个方法信息的数组。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)
,检索对象的属性(甚至是私有属性!):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)
getMethod()
和 返回的数组中的元素分别为 ReflectionMethod
和 ReflectionProperty
类型;这些对象非常有用: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() } }
在我们的例子中,这个过程很简单,因为我们已经有一个 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 使用包含“添加”或“删除”值的变量进行响应时,调用正确的方法很容易。
感谢您的阅读!
以上是PHP中的反射机制的详细内容。更多信息请关注PHP中文网其他相关文章!