Reflection mechanism in PHP

WBOY
Release: 2023-08-31 13:58:02
Original
1216 people have browsed it

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.


A little history

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.


A simple example

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();
	}

}
Copy after login

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;
	}

}
Copy after login

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);
		}
	}

}
Copy after login

Next, modify Nettuts as follows:

// Nettuts.php

class Nettuts {

	function publishNextArticle($editor) {
		$editor->setNextArticle('135523');
		$editor->publish();
	}

}
Copy after login

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.

Reflection mechanism in PHP

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 <> and the question mark. At runtime, PHP examines the received object and verifies that it implements the setNextArticle() and publish() methods.

Object member information

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);
	}

}
Copy after login

Now, add var_dump() to Nettuts:

// Nettuts.php

class NetTuts {

	function publishNextArticle($editor) {
		$editor->setNextArticle('135523');
		$editor->publish();
		var_dump(new ReflectionClass($editor));
	}

}
Copy after login

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)
Copy after login

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());
	}

}
Copy after login

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)
Copy after login

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)
Copy after login

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()
	}

}
Copy after login

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 获取一个对象。

我们来修改Editorpublish()方法来演示双重调用:

// Editor.php

class Editor {

	[ ... ]

	public function publish() {
		// publish logic goes here
		echo ("HERE\n");
		return true;
	}

}
Copy after login

新的输出:

PHPUnit 3.6.11 by Sebastian Bergmann.

.HERE
HERE

Time: 0 seconds, Memory: 2.25Mb

OK (1 test, 0 assertions)
Copy after login

操作实例数据

我们还可以在执行时修改代码。修改没有公共设置器的私有变量怎么样?让我们向 Editor 添加一个方法来检索编辑器的名称:

// Editor.php

class Editor {

	private $name;
	public $articleId;

	function __construct($name) {
		$this->name = $name;
	}

	[ ... ]

	function getEditorName() {
		return $this->name;
	}

}
Copy after login

这个新方法被称为 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);

	}

}
Copy after login

尽管这会在 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.
Copy after login

为了解决这个问题,我们需要请求 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));
	}

}
Copy after login

调用 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)
Copy after login

如您所见,我们已成功读取私有变量。第一行输出来自对象自己的 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));
	}

}
Copy after login

就是这样。此代码将“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)
Copy after login

间接反射的使用

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')));
	}

}
Copy after login

以下输出表明代码检索了正确的值:

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)
Copy after login

使用变量的值

间接反射的另一个示例是通过变量中包含的值来调用方法,而不是直接调用它。例如:

// 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());
	}

}
Copy after login

此代码产生与前面示例相同的输出。 PHP 只是用它所代表的字符串替换该变量并调用该方法。当您想通过使用类名变量来创建对象时,它甚至可以工作。


我们什么时候应该使用反射?

现在我们已经把技术细节抛在脑后了,我们什么时候应该利用反射呢?以下是一些场景:

  • 动态类型如果没有反射可能是不可能的。
  • 面向方面的编程侦听方法调用并将代码放置在方法周围,所有这些都通过反射完成。
  • PHPUnit 与其他模拟框架一样,严重依赖反射。
  • Web 框架通常将反射用于不同的目的。有些人用它来初始化模型、构建视图对象等等。 Laravel 大量使用反射来注入依赖项。
  • 元编程,就像我们的最后一个例子一样,是隐藏的反射。
  • 代码分析框架使用反射来理解您的代码。

最终想法

与任何很酷的玩具一样,使用反射,但不要滥用它。当您检查许多对象时,反射的成本很高,并且有可能使项目的架构和设计变得复杂。我建议您仅在它确实为您带来优势或没有其他可行选择时才使用它。

就我个人而言,我只在少数情况下使用过反射,最常见的是在使用缺乏文档的第三方模块时。我发现自己经常使用与上一个示例类似的代码。当您的 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!

Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
About us Disclaimer Sitemap
php.cn:Public welfare online PHP training,Help PHP learners grow quickly!