Rumah  >  Artikel  >  pembangunan bahagian belakang  >  Mekanisme refleksi dalam PHP

Mekanisme refleksi dalam PHP

WBOY
WBOYasal
2023-08-31 13:57:051218semak imbas

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.


Sedikit sejarah

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.


Contoh mudah

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

}

现在,NettutsEditor 类完全没有关系。它不包含它的文件,它不初始化它的类,它甚至不知道它的存在。我可以将任何类型的对象传递到 publishNextArticle() 方法中,代码就可以工作。

Mekanisme refleksi dalam PHP

从这个类图中可以看到,Nettuts只与Manager有直接关系。 Manager 创建它,因此 Manager 依赖于 Nettuts。但是 Nettuts 不再与 Editor 类有任何关系,并且 Editor 仅与 Manager 相关。

在运行时,Nettuts 使用 Editor 对象,因此有 > 和问号。在运行时,PHP 检查接收到的对象并验证它是否实现了 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() 返回的数组中的元素分别为 ReflectionMethodReflectionProperty 类型;这些对象非常有用:

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

}

Seterusnya, ubah suai Nettuts seperti berikut: #🎜🎜#
PHPUnit 3.6.11 by Sebastian Bergmann.

.HERE
HERE

Time: 0 seconds, Memory: 2.25Mb

OK (1 test, 0 assertions)
#🎜🎜#Kini, 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. #🎜🎜# #🎜🎜#Mekanisme refleksi dalam PHP#🎜🎜# #🎜🎜#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 > dan tanda soal. Pada masa jalan, PHP memeriksa objek yang diterima dan mengesahkan sama ada ia melaksanakan kaedah setNextArticle() dan publish(). #🎜🎜#

Maklumat ahli objek

#🎜🎜#Kita boleh membenarkan PHP memaparkan butiran objek. Mari buat ujian PHPUnit untuk membantu kami menguji kod kami dengan mudah: #🎜🎜#
// Editor.php

class Editor {

	private $name;
	public $articleId;

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

	[ ... ]

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

}
#🎜🎜#Sekarang, tambahkan 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);

	}

}
#🎜🎜#Jalankan ujian dan saksikan keajaiban berlaku dalam output: #🎜🎜#
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.
#🎜🎜#Kelas refleksi kami mempunyai atribut 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));
	}

}
#🎜🎜#Dalam kod ini, kami menetapkan contoh kelas refleksi kepada pembolehubah $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)
#🎜🎜#Kaedah lain, 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));
	}

}
#🎜🎜#Elemen dalam tatasusunan yang dikembalikan daripada 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)
#🎜🎜#Di sini, kami menggunakan 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 获取一个对象。

我们来修改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 使用包含“添加”或“删除”值的变量进行响应时,调用正确的方法很容易。

感谢您的阅读!

Atas ialah kandungan terperinci Mekanisme refleksi dalam PHP. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn