리플렉션은 일반적으로 프로그램이 실행 중에 자체를 검사하고 논리를 수정하는 기능으로 정의됩니다. 덜 기술적인 용어로 말하면, 리플렉션은 객체에 속성과 메서드를 알려달라고 요청하고 해당 멤버(비공개 멤버도 포함)를 변경하는 것입니다. 이 과정에서는 이를 수행하는 방법과 이것이 언제 유용할 수 있는지 자세히 살펴보겠습니다.
프로그래밍 시대 초에 어셈블리 언어가 등장했습니다. 어셈블리 언어로 작성된 프로그램은 컴퓨터 내부의 물리적 레지스터에 상주합니다. 그 구성, 방법, 값은 레지스터를 읽어 언제든지 확인할 수 있습니다. 더욱이, 프로그램이 실행되는 동안 간단히 이러한 레지스터를 수정하여 프로그램을 변경할 수 있습니다. 실행 중인 프로그램에 대한 깊은 지식이 필요하지만 본질적으로 반영적입니다.
다른 멋진 장난감과 마찬가지로 반사를 사용하되 남용하지 마세요.
C와 같은 고급 프로그래밍 언어의 출현으로 이러한 재귀성은 점차 사라졌습니다. 나중에 객체 지향 프로그래밍을 통해 다시 도입되었습니다.
Reflection은 요즘 대부분의 프로그래밍 언어에서 사용할 수 있습니다. Java와 같은 정적으로 유형이 지정된 언어는 리플렉션에 문제가 거의 없습니다. 그러나 PHP나 Ruby와 같은 동적 유형 언어가 리플렉션에 크게 기반을 두고 있다는 점이 흥미롭습니다. 반사라는 개념이 없다면 오리 타이핑은 불가능할 것입니다. 개체를 다른 개체(예: 매개 변수)로 보낼 때 수신 개체는 개체의 구조와 유형을 알 수 없습니다. 할 수 있는 일은 리플렉션을 사용하여 수신된 개체에 대해 호출할 수 있는 메서드와 호출할 수 없는 메서드를 식별하는 것뿐입니다.
반사(Reflection)는 PHP에서 일반적입니다. 실제로, 자신도 모르게 이 기능을 사용하고 있는 상황이 여러 가지 있습니다. 예:
으아악또한:
으아악이 코드에서는 알려진 유형으로 초기화된 로컬 변수를 직접 호출합니다. publishNextArticle()
中创建编辑器,可以明显看出 $editor
变量的类型为 Editor
。这里不需要反射,但是我们引入一个新类,名为Manager
에서:
다음으로 Nettuts
를 다음과 같이 수정하세요.
이제 Nettuts
与 Editor
类完全没有关系。它不包含它的文件,它不初始化它的类,它甚至不知道它的存在。我可以将任何类型的对象传递到 publishNextArticle()
는 Editor
클래스와 전혀 관련이 없습니다. 파일을 포함하지 않고, 클래스를 초기화하지 않으며, 클래스가 존재하는지조차 모릅니다. 모든 유형의 개체를 publishNextArticle()
메서드에 전달할 수 있으며 코드가 작동합니다.
이 클래스 다이어그램에서 볼 수 있듯이 Nettuts
只与Manager
有直接关系。 Manager
创建它,因此 Manager
依赖于 Nettuts
。但是 Nettuts
不再与 Editor
类有任何关系,并且 Editor
仅与 Manager
는 Manager
에만 직접적으로 관련되어 있습니다. Manager
가 생성하므로 Manager
는
더 이상 Editor
클래스와 관련이 없으며 Editor
는 Manager
에만 관련됩니다. 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()
返回的数组中的元素分别为 ReflectionMethod
和 ReflectionProperty
으아악
getMethod()
및 에서 반환된 배열의 요소는 각각 ReflectionMethod
및 ReflectionProperty
유형입니다. 이러한 개체는 매우 유용합니다.
으아악
getMethod()
来检索名称为“publish”的单个方法;其结果是 ReflectionMethod
对象。然后,我们调用 invoke()
方法,并向其传递 $editor
对象,以便再次执行编辑器的 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 중국어 웹사이트의 기타 관련 기사를 참조하세요!