Reflection is generally defined as the ability of a program to examine itself and modify its logic while executing. In less technical terms, reflection is asking an object to tell you its properties and methods, and changing those members (even private members). In this course, we'll take a closer look at how to accomplish this, and when it might be useful.
In the early days of the programming era, assembly language appeared. Programs written in assembly language reside in physical registers inside the computer. Its composition, methods and values can be checked at any time by reading the registers. What's more, you can change your program by simply modifying these registers while it's running. It requires some deep knowledge of the running program, but it is reflective in nature.
As with any cool toy, use reflection, but don't abuse it.
With the advent of high-level programming languages (such as C), this reflexivity gradually disappeared. Later it was reintroduced through object-oriented programming.
Reflection is available in most programming languages these days. Staticly typed languages such as Java have few problems with reflection. However, I find it interesting that any dynamically typed language like PHP or Ruby is heavily based on reflection. Without the concept of reflection, duck typing would most likely not be possible. When you send an object to another object (such as a parameter), the receiving object has no way of knowing the object's structure and type. All it can do is use reflection to identify methods that can and cannot be called on the received object.
Reflection is common in PHP. In fact, there are several situations where you might be using it without knowing it. For example:
// Nettuts.php require_once 'Editor.php'; class Nettuts { function publishNextArticle() { $editor = new Editor('John Doe'); $editor->setNextArticle('135523'); $editor->publish(); } }
besides:
// 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; } }
In this code, we directly call a local initialized variable with a known type. When creating the editor in publishNextArticle()
, it can be clearly seen that the type of the $editor
variable is Editor
. There is no need for reflection here, but we introduce a new class named 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); } } }
Next, modify Nettuts
as follows:
// Nettuts.php class Nettuts { function publishNextArticle($editor) { $editor->setNextArticle('135523'); $editor->publish(); } }
Now, Nettuts
has absolutely nothing to do with the Editor
class. It doesn't contain its files, it doesn't initialize its classes, it doesn't even know it exists. I can pass any type of object into the publishNextArticle()
method and the code works.
As you can see from this class diagram, Nettuts
is only directly related to Manager
. Manager
creates it so Manager
depends on Nettuts
. But Nettuts
no longer has anything to do with the Editor
class, and Editor
is only relevant to Manager
.
At runtime, Nettuts
uses the Editor
object, hence the <setNextArticle()
and publish()
methods.
We can let PHP display the detailed information of the object. Let's create a PHPUnit test to help us easily test our code:
// 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); } }
Now, add var_dump()
to Nettuts
:
// Nettuts.php class NetTuts { function publishNextArticle($editor) { $editor->setNextArticle('135523'); $editor->publish(); var_dump(new ReflectionClass($editor)); } }
Run the test and watch the magic happen in the output:
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)
Our reflection class has a name
property set to the original type of the $editor
variable: Editor
, but that's not too much information. How about the Editor
method?
// Nettuts.php class Nettuts { function publishNextArticle($editor) { $editor->setNextArticle('135523'); $editor->publish(); $reflector = new ReflectionClass($editor); var_dump($reflector->getMethods()); } }
In this code, we assign an instance of the reflection class to the $reflector
variable so that we can now trigger its methods. ReflectionClass
Exposes a number of methods that can be used to obtain information about an object. One of these methods is getMethods()
, which returns an array containing information about each method.
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)
Another method, getProperties()
, retrieves the properties of the object (even private properties!):
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)
The elements in the arrays returned from getMethod()
and getProperties()
are of type ReflectionMethod
and ReflectionProperty
respectively; these objects very useful:
// 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() } }
Here, we use getMethod()
to retrieve a single method named "publish"; the result is a ReflectionMethod
object. We then call the invoke()
method, passing it the $editor
object to execute the editor's publish()
method again.
在我们的例子中,这个过程很简单,因为我们已经有一个 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 使用包含“添加”或“删除”值的变量进行响应时,调用正确的方法很容易。
感谢您的阅读!
The above is the detailed content of Reflection mechanism in PHP. For more information, please follow other related articles on the PHP Chinese website!