摘要:主要是參考列旭鬆、陳文著的《PHP核心技術與最佳實踐》的1.5節。
1.1 定義
反射,直觀理解就是根據到達地找到出發地和來源。比如說,給你一個光禿禿的對象,可以僅僅通過這個對象就能知道它所屬的類以及擁有的方法。
反射,指在PHP運行狀態中,擴展分析PHP程序,導出或提取出關於類、方法、屬性、參數等的詳細信息,包括註釋。這種動態獲取信息以及動態調用對象方法的功能稱爲反射API。
1.2 獲取對象屬性和方法
getMethods和getProperties分別用來獲取對象的所有方法和所有屬性,返回對象數組,然後通過getName來獲取具體的方法和屬性即可。但都是必須先通過反射獲取類的原型, 即使用$reflect
= new ReflectionObject($student);來獲取對象的原型。
下面是使用反射API來獲取對象的屬性和方法:
1
<?php
2
/**
3
* 使用反射API獲取對象的屬性和方法
4
* Created by PhpStorm.
5
* User: Administrator
6
* Date: 2017/7/10
7
* Time: 15:22
8
*/
9
10
class Person3
11
{
12
public $name;
13
public $gender;
14
15
public function say()
16
{
17
echo $this->name . " is " . $this->gender . "\r\n";
18
}
19
20
public function __set($name, $value)
21
{
22
echo "Setting $name to $value \r\n";
23
$this->$name = $value;
24
}
25
26
public function __get($name)
27
{
28
if (!isset($this->$name)) {
29
echo '未設置';
30
$this->$name = '正在爲你設置默認值';
31
}
32
return $this->$name;
33
}
34
}
35
36
$student = new Person3();
37
$student->name = 'Tom';
38
$student->gender = 'male';
39
$student->age = 24;
40
$student->say();
41
42
$reflect = new ReflectionObject($student);
43
44
// 獲取對象屬性列表
45
$props = $reflect->getProperties();
46
echo "\r\n對象的屬性有:\r\n";
47
foreach ($props as $prop) {
48
echo $prop->getName() . "\n";
49
}
50
print_r($props);
51
52
// 獲取對象方法列表
53
$methods = $reflect->getMethods();
54
echo "\r\n對象的方法有:\r\n";
55
foreach ($methods as $method) {
56
echo $method->getName() . "\n";
57
}
58
print_r($methods);
59
60
echo "------------------分隔線-----------------\r\n";
61
/**
62
* 也可以不用反射 API,而使用class函數來獲取對象屬性的關聯數組以及更多的信息,但是使用反射 API可以獲得更多的信息
63
*/
64
// 返回對象屬性的關聯數組
65
echo "對象屬性的關聯數組:\r\n";
66
print_r(get_object_vars($student));
67
68
// 返回對象屬性列表所屬的類
69
echo "對象屬性列表所屬的類:\r\n";
70
print_r(get_class($student));
71
72
// 類屬性
73
echo "類屬性:\r\n";
74
print_r(get_class_vars(get_class($student)));
75
76
// 返回由類的方法名組成的數組
77
echo "類的方法名組成的數組:\r\n";
78
print_r(get_class_methods(get_class($student)));
運行:
Setting age to 24
Tom is male
對象的屬性有:
name
gender
age
Array
(
[0] => ReflectionProperty Object
(
[name] => name
[class] => Person3
)
[1] => ReflectionProperty Object
(
[name] => gender
[class] => Person3
)
[2] => ReflectionProperty Object
(
[name] => age
[class] => Person3
)
)
對象的方法有:
say
__set
__get
Array
(
[0] => ReflectionMethod Object
(
[name] => say
[class] => Person3
)
[1] => ReflectionMethod Object
(
[name] => __set
[class] => Person3
)
[2] => ReflectionMethod Object
(
[name] => __get
[class] => Person3
)
)
------------------分隔線-----------------
對象屬性的關聯數組:
Array
(
[name] => Tom
[gender] => male
[age] => 24
)
對象屬性列表所屬的類:
Person3類屬性:
Array
(
[name] =>
[gender] =>
)
類的方法名組成的數組:
Array
(
[0] => say
[1] => __set
[2] => __get
)
1.3 還原類的原型
既然上面已經可以使用反射來獲取對象的屬性和方法了,那麼再進一步,獲取方法和屬性的訪問權限,那麼就可以根據對象來獲取類的原型了:
1
<?php
2
/**
3
* 使用反射 API 來還原類的原型
4
* Created by PhpStorm.
5
* User: Administrator
6
* Date: 2017/7/13
7
* Time: 17:42
8
*/
9
10
class Person4
11
{
12
public $name;
13
private $gender;
14
15
public function say()
16
{
17
echo $this->name . " is " . $this->gender . "\r\n";
18
}
19
20
public function __set($name, $value)
21
{
22
echo "Setting $name to $value \r\n";
23
$this->$name = $value;
24
}
25
26
public function __get($name)
27
{
28
if (!isset($this->$name)) {
29
echo '未設置';
30
$this->$name = '正在爲你設置默認值';
31
}
32
return $this->$name;
33
}
34
35
protected function run($method)
36
{
37
echo $this->name . ' runned with' . $method;
38
}
39
}
40
41
// 反射獲取類的原型
42
$obj = new ReflectionClass('Person4');
43
44
// 獲取類的名稱
45
$class_name = $obj->getName();
46
47
$methods = $properties = [];
48
49
// 獲取類的所有屬性
50
foreach ($obj->getProperties() as $value) {
51
$properties[$value->getName()] = $value;
52
}
53
54
// 獲取類的所有方法
55
foreach ($obj->getMethods() as $value) {
56
$methods[$value->getName()] = $value;
57
}
58
59
echo "class {$class_name}\r\n{\n";
60
61
// 如果類有屬性的話則進行排序
62
is_array($properties) && ksort($properties);
63
64
// 輸出類的屬性
65
foreach ($properties as $key => $value) {
66
echo "\t";
67
echo $value->isPublic() ? 'public' : '' , $value->isPrivate() ? 'private' : '' , $value->isProtected() ? 'protected' : '' , $value->isStatic() ? 'static' : '';
68
echo "\t{$key}\n";
69
}
70
71
echo "\n";
72
73
// 對類的方法進行排序
74
if (is_array($methods)) {
75
ksort($methods);
76
}
77
78
// 輸出類的方法
79
foreach ($methods as $key => $value) {
80
echo "\t";
81
echo $value->isPublic() ? 'public' : '' , $value->isPrivate() ? 'private' : '' , $value->isProtected() ? 'protected' : '' , $value->isStatic() ? 'static' : '';
82
echo " function {$key}() {}\n";
83
}
84
85
echo "}\n";
86
87
/**
88
運行:
89
class Person4
90
{
91
private gender
92
public name
93
94
public function __get() {}
95
public function __set() {}
96
protected function run() {}
97
public function say() {}
98
}
99
*/
PHP手冊中關於反射API的有很多,可以說,反射完整的描述了一個類或者對象的原型。反射不僅可以用於類和對象,還可以用於函數、擴展模塊、異常等。
1.4 反射的invoke方法
invoke方法是個很實用的方法,用來執行一個反射的方法:
<?php
/**
* 使用反射API的invoke方法來執行反射的方法
* Created by PhpStorm.
* User: Administrator
* Date: 2017/7/13
* Time: 18:23
*/
class HelloWorld
{
public function sayHelloTo($name) {
return 'Hello ' . $name;
}
}
// 獲取反射的方法
$method = new ReflectionMethod('HelloWorld', 'sayHelloTo');
/** 上面的代碼和下面註釋的代碼作用是一樣的 */
//$reflectionClass = new ReflectionClass('HelloWorld');
//$method = $reflectionClass->getMethod('sayHelloTo');
// 執行一個反射的方法
echo $method->invoke(new HelloWorld(), 'Mike'); // Hello Mike
其中的:
1
$method = new ReflectionMethod('HelloWorld', 'sayHelloTo');
作用相當於:
$reflectionClass = new ReflectionClass('HelloWorld');
$method = $reflectionClass->getMethod('sayHelloTo');
1.5 動態代理
使用反射的invoke方法,可以實現簡單的動態代理:
1
<?php
2
/**
3
* 使用反射API實現簡單的動態代理
4
* Created by PhpStorm.
5
* User: Administrator
6
* Date: 2017/7/14
7
* Time: 9:33
8
*/
9
10
class MySql
11
{
12
public function connect($db)
13
{
14
echo "已經連接到數據庫${db[0]}\r\n";
15
}
16
}
17
18
/**
19
* Class SqlProxy
20
* SqlProxy類實現了根據動態傳入參數,代替實際的類MySql類的運行,並且在方法運行前後進行攔截,並且可以動態改變類中的方法和屬性,這就是簡單的動態代理
21
*/
22
class SqlProxy
23
{
24
private $target;
25
26
public function __construct($tar)
27
{
28
$this->target[] = new $tar();
29
}
30
31
public function __call($name, $arguments)
32
{
33
foreach ($this->target as $obj) {
34
$method = new ReflectionMethod($obj, $name);
35
if ($method) {
36
if ($method->isPublic() && !$method->isAbstract()) {
37
echo "方法前攔截記錄 Log\r\n";
38
$method->invoke($obj, $arguments);
39
echo "方法後攔截\r\n";
40
}
41
}
42
}
43
}
44
}
45
46
$obj = new SqlProxy('MySql');
47
$obj->connect('member');
48
$obj->connect('lottery');
49
運行:
方法前攔截記錄 Log
已經連接到數據庫member
方法後攔截
方法前攔截記錄 Log
已經連接到數據庫lottery
方法後攔截
這裏簡單說明一下,真正的操作類是MySql類,但SqlProxy類實現了根據動態傳入參數,代替實際的類MySql類的運行,並且在方法運行前後進行攔截,並且可以動態改變類中的方法和屬性,這就是簡單的動態代理。
1.6 反射的作用
反射的用處:
- 用於文檔生成,因此可以用它對文件裏的類進行掃描,逐個生成描述文檔。
- 用來做hook實現插件功能
- 動態代理
在平常開發中,用到反射的地方很有限,主要有兩個地方,一個是對對象進行調試,另一個是獲取類的信息。而在MVC和插件開發中,使用反射很常見,但是反射的消耗也很大,在可以找到替代方案的情況下,不要濫用反射。
很多時候,善用反射能夠保持代碼的優雅和簡潔,但反射也會破壞類的封裝性,因爲反射可以使本不應該暴露的方法或者屬性被強制暴露了出來,這既是優點也是缺點。