Refleksi secara amnya ditakrifkan sebagai kebolehan program untuk memeriksa dirinya sendiri dan mengubah suai logiknya semasa melaksanakan. Dalam istilah yang kurang teknikal, refleksi meminta objek untuk memberitahu anda sifat dan kaedahnya, dan menukar ahli tersebut (walaupun ahli persendirian). Dalam kursus ini, kita akan melihat dengan lebih dekat cara untuk mencapai ini, dan bila ia mungkin berguna.
Pada masa awal era pengaturcaraan, bahasa himpunan muncul. Program yang ditulis dalam bahasa himpunan berada dalam daftar fizikal di dalam komputer. Komposisi, kaedah dan nilainya boleh disemak pada bila-bila masa dengan membaca daftar. Lebih-lebih lagi, anda boleh menukar program anda dengan hanya mengubah suai daftar ini semasa ia sedang berjalan. Ia memerlukan sedikit pengetahuan mendalam tentang program yang sedang berjalan, tetapi ia bersifat reflektif.
Seperti mana-mana mainan yang keren, gunakan refleksi, tetapi jangan menyalahgunakannya.
Dengan kemunculan bahasa pengaturcaraan peringkat tinggi seperti C, reflekstiviti ini beransur-ansur hilang. Kemudian ia diperkenalkan semula melalui pengaturcaraan berorientasikan objek.
Refleksi tersedia dalam kebanyakan bahasa pengaturcaraan hari ini. Bahasa yang ditaip secara statik seperti Java mempunyai sedikit masalah dengan refleksi. Walau bagaimanapun, saya mendapati menarik bahawa mana-mana bahasa yang ditaip secara dinamik seperti PHP atau Ruby banyak berdasarkan refleksi. Tanpa konsep refleksi, menaip itik kemungkinan besar tidak dapat dilakukan. Apabila anda menghantar objek ke objek lain (seperti parameter), objek penerima tidak mempunyai cara untuk mengetahui struktur dan jenis objek. Apa yang boleh dilakukan ialah menggunakan refleksi untuk mengenal pasti kaedah yang boleh dan tidak boleh dipanggil pada objek yang diterima.
Refleksi sangat biasa dalam PHP. Malah, terdapat beberapa situasi di mana anda mungkin menggunakannya tanpa mengetahuinya. Contohnya:
// Nettuts.php require_once 'Editor.php'; class Nettuts { function publishNextArticle() { $editor = new Editor('John Doe'); $editor->setNextArticle('135523'); $editor->publish(); } }
Juga:
// 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; } }
Dalam kod ini, kami memanggil terus pembolehubah dimulakan setempat dengan jenis yang diketahui. Apabila mencipta editor dalam publishNextArticle()
, dapat dilihat dengan jelas bahawa jenis pembolehubah $editor
ialah Editor
. Tidak ada keperluan untuk refleksi di sini, tetapi kami memperkenalkan kelas baharu bernama Manager
: 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()
方法中,代码就可以工作。
从这个类图中可以看到,Nettuts
只与Manager
有直接关系。 Manager
创建它,因此 Manager
依赖于 Nettuts
。但是 Nettuts
不再与 Editor
类有任何关系,并且 Editor
仅与 Manager
相关。
在运行时,Nettuts
使用 Editor
对象,因此有 <setNextArticle()
和 publish()
方法。
我们可以让 PHP 显示对象的详细信息。让我们创建一个 PHPUnit 测试来帮助我们轻松地测试我们的代码:
// 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
:
// Nettuts.php class NetTuts { function publishNextArticle($editor) { $editor->setNextArticle('135523'); $editor->publish(); var_dump(new ReflectionClass($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)
我们的反射类有一个 name
属性,设置为 $editor
变量的原始类型:Editor
,但这并不是太多信息。 Editor
的方法怎么样?
// Nettuts.php class Nettuts { function publishNextArticle($editor) { $editor->setNextArticle('135523'); $editor->publish(); $reflector = new ReflectionClass($editor); var_dump($reflector->getMethods()); } }
在此代码中,我们将反射类的实例分配给 $reflector
变量,以便我们现在可以触发其方法。 ReflectionClass
公开了大量可用于获取对象信息的方法。其中一个方法是 getMethods()
,它返回一个包含每个方法信息的数组。
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)
另一个方法,getProperties()
,检索对象的属性(甚至是私有属性!):
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()
和 getProperties()
返回的数组中的元素分别为 ReflectionMethod
和 ReflectionProperty
类型;这些对象非常有用:
// 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() } }
这里,我们使用 getMethod()
来检索名称为“publish”的单个方法;其结果是 ReflectionMethod
对象。然后,我们调用 invoke()
方法,并向其传递 $editor
对象,以便再次执行编辑器的 publish()
// Editor.php class Editor { [ ... ] public function publish() { // publish logic goes here echo ("HERE\n"); return true; } }
Nettuts
seperti berikut: #🎜🎜#
PHPUnit 3.6.11 by Sebastian Bergmann. .HERE HERE Time: 0 seconds, Memory: 2.25Mb OK (1 test, 0 assertions)
Nettuts
langsung tiada kaitan dengan kelas Editor
. Ia tidak mengandungi failnya, ia tidak memulakan kelasnya, ia tidak tahu ia wujud. Saya boleh menghantar sebarang jenis objek ke dalam kaedah publishNextArticle()
dan kod itu akan berfungsi. #🎜🎜#
#🎜🎜##🎜🎜#
#🎜🎜#Seperti yang anda boleh lihat dari rajah kelas ini, Nettuts
hanya berkaitan secara langsung dengan Manager
. Manager
menciptanya, jadi Manager
bergantung pada Nettuts
. Tetapi Nettuts
tidak lagi mempunyai kaitan dengan kelas Editor
dan Editor
hanya berkaitan dengan Manager
. #🎜🎜#
#🎜🎜#Pada masa jalan, Nettuts
menggunakan objek Editor
, oleh itu <setNextArticle()
dan publish()
. #🎜🎜#
// Editor.php class Editor { private $name; public $articleId; function __construct($name) { $this->name = $name; } [ ... ] function getEditorName() { return $this->name; } }
var_dump()
pada Nettuts
:#🎜🎜#
// Nettuts.php class Nettuts { function publishNextArticle($editor) { var_dump($editor->getEditorName()); $reflector = new ReflectionClass($editor); $editorName = $reflector->getProperty('name'); $editorName->getValue($editor); } }
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.
name
, yang ditetapkan kepada jenis asal pembolehubah $editor
: Editor
, tetapi ini tidak terlalu Maklumat lanjut. Bagaimana pula dengan kaedah Editor
? #🎜🎜#
// 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)); } }
$reflector
supaya kami kini boleh mencetuskan kaedahnya. ReflectionClass
mendedahkan beberapa kaedah yang boleh digunakan untuk mendapatkan maklumat tentang objek. Salah satu kaedah ini ialah getMethods()
, yang mengembalikan tatasusunan yang mengandungi maklumat tentang setiap kaedah. #🎜🎜#
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)
getProperties()
, mendapatkan semula sifat objek (malah sifat peribadi!): #🎜🎜#
// 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)); } }
getMethod()
dan getProperties()
ialah ReflectionMethod
dan ReflectionProperty
masing-masing > Jenis objek ini sangat berguna: #🎜🎜#
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)
getMethod()
untuk mendapatkan satu kaedah bernama "publish"; hasilnya ialah objek ReflectionMethod
. Kami kemudian memanggil kaedah invoke()
, memberikannya objek $editor
untuk melaksanakan kaedah publish()
editor sekali lagi. #🎜🎜#
在我们的例子中,这个过程很简单,因为我们已经有一个 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 使用包含“添加”或“删除”值的变量进行响应时,调用正确的方法很容易。
感谢您的阅读!
Atas ialah kandungan terperinci Mekanisme refleksi dalam PHP. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!