>백엔드 개발 >PHP 튜토리얼 >PHP의 반사 메커니즘

PHP의 반사 메커니즘

WBOY
WBOY원래의
2023-08-31 13:57:051258검색

리플렉션은 일반적으로 프로그램이 실행 중에 자체를 검사하고 논리를 수정하는 기능으로 정의됩니다. 덜 기술적인 용어로 말하면, 리플렉션은 객체에 속성과 메서드를 알려달라고 요청하고 해당 멤버(비공개 멤버도 포함)를 변경하는 것입니다. 이 과정에서는 이를 수행하는 방법과 이것이 언제 유용할 수 있는지 자세히 살펴보겠습니다.


약간의 역사

프로그래밍 시대 초에 어셈블리 언어가 등장했습니다. 어셈블리 언어로 작성된 프로그램은 컴퓨터 내부의 물리적 레지스터에 상주합니다. 그 구성, 방법, 값은 레지스터를 읽어 언제든지 확인할 수 있습니다. 더욱이, 프로그램이 실행되는 동안 간단히 이러한 레지스터를 수정하여 프로그램을 변경할 수 있습니다. 실행 중인 프로그램에 대한 깊은 지식이 필요하지만 본질적으로 반영적입니다.

다른 멋진 장난감과 마찬가지로 반사를 사용하되 남용하지 마세요.

C와 같은 고급 프로그래밍 언어의 출현으로 이러한 재귀성은 점차 사라졌습니다. 나중에 객체 지향 프로그래밍을 통해 다시 도입되었습니다.

Reflection은 요즘 대부분의 프로그래밍 언어에서 사용할 수 있습니다. Java와 같은 정적으로 유형이 지정된 언어는 리플렉션에 문제가 거의 없습니다. 그러나 PHP나 Ruby와 같은 동적 유형 언어가 리플렉션에 크게 기반을 두고 있다는 점이 흥미롭습니다. 반사라는 개념이 없다면 오리 타이핑은 불가능할 것입니다. 개체를 다른 개체(예: 매개 변수)로 보낼 때 수신 개체는 개체의 구조와 유형을 알 수 없습니다. 할 수 있는 일은 리플렉션을 사용하여 수신된 개체에 대해 호출할 수 있는 메서드와 호출할 수 없는 메서드를 식별하는 것뿐입니다.


간단한 예

반사(Reflection)는 PHP에서 일반적입니다. 실제로, 자신도 모르게 이 기능을 사용하고 있는 상황이 여러 가지 있습니다. 예:

으아악

또한:

으아악

이 코드에서는 알려진 유형으로 초기화된 로컬 변수를 직접 호출합니다. publishNextArticle() 中创建编辑器,可以明显看出 $editor 变量的类型为 Editor。这里不需要反射,但是我们引入一个新类,名为Manager에서:

으아악

다음으로 Nettuts를 다음과 같이 수정하세요.

으아악

이제 NettutsEditor 类完全没有关系。它不包含它的文件,它不初始化它的类,它甚至不知道它的存在。我可以将任何类型的对象传递到 publishNextArticle()Editor 클래스와 전혀 관련이 없습니다. 파일을 포함하지 않고, 클래스를 초기화하지 않으며, 클래스가 존재하는지조차 모릅니다. 모든 유형의 개체를 publishNextArticle() 메서드에 전달할 수 있으며 코드가 작동합니다.

PHP의 반사 메커니즘

이 클래스 다이어그램에서 볼 수 있듯이 Nettuts只与Manager有直接关系。 Manager 创建它,因此 Manager 依赖于 Nettuts。但是 Nettuts 不再与 Editor 类有任何关系,并且 Editor 仅与 ManagerManager에만 직접적으로 관련되어 있습니다. Manager가 생성하므로 Manager

에 의존합니다. 하지만

더 이상 Editor 클래스와 관련이 없으며 EditorManager에만 관련됩니다. Nettuts 使用 Editor 对象,因此有 > 和问号。在运行时,PHP 检查接收到的对象并验证它是否实现了 setNextArticle()publish()

런타임에

Editor 개체를 사용하므로 >와 물음표가 표시됩니다. 런타임 시 PHP는 수신된 객체를 검사하고 setNextArticle()publish() 메서드를 구현하는지 확인합니다.

객체 회원정보

PHP에서 객체의 세부정보를 표시하도록 할 수 있습니다. 코드를 쉽게 테스트하는 데 도움이 되는 PHPUnit 테스트를 만들어 보겠습니다. var_dump() 添加到 Nettuts 으아악

이제

:

var_dump()를 추가하세요. 으아악

테스트를 실행하고 출력에서 ​​마법 같은 일이 일어나는지 확인하세요. name 属性,设置为 $editor 变量的原始类型:Editor,但这并不是太多信息。 Editor 으아악

저희 반성수업에는

메서드가 있는 게 어때요? $reflector 变量,以便我们现在可以触发其方法。 ReflectionClass 公开了大量可用于获取对象信息的方法。其中一个方法是 getMethods() 으아악

이 코드에서는 각 메서드에 대한 정보가 포함된 배열을 반환하는 리플렉션 클래스의 인스턴스를

에 할당합니다. getProperties() 으아악

또 다른 메소드인

는 객체의 속성(비공개 속성도 포함)을 검색합니다. getMethod()getProperties() 返回的数组中的元素分别为 ReflectionMethodReflectionProperty 으아악

getMethod()

에서 반환된 배열의 요소는 각각 ReflectionMethodReflectionProperty 유형입니다. 이러한 개체는 매우 유용합니다. 으아악 getMethod() 来检索名称为“publish”的单个方法;其结果是 ReflectionMethod 对象。然后,我们调用 invoke() 方法,并向其传递 $editor 对象,以便再次执行编辑器的 publish()여기서는

메소드를 사용합니다. 🎜

在我们的例子中,这个过程很简单,因为我们已经有一个 Editor 对象传递给 invoke()。在某些情况下,我们可能有多个 Editor 对象,这使我们可以自由选择使用哪个对象。在其他情况下,我们可能没有可以使用的对象,在这种情况下,我们需要从 ReflectionClass 获取一个对象。

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

// 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 只是用它所代表的字符串替换该变量并调用该方法。当您想通过使用类名变量来创建对象时,它甚至可以工作。


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

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

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

最终想法

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

就我个人而言,我只在少数情况下使用过反射,最常见的是在使用缺乏文档的第三方模块时。我发现自己经常使用与上一个示例类似的代码。当您的 MVC 使用包含“添加”或“删除”值的变量进行响应时,调用正确的方法很容易。

感谢您的阅读!

위 내용은 PHP의 반사 메커니즘의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.