摘要:主要是参考列旭松、陈文著的《PHP核心技术与最佳实践》的1.5节。
1.1 定义
反射,直观理解就是根据到达地找到出发地和来源。比如说,给你一个光秃秃的对象,可以仅仅通过这个对象就能知道它所属的类以及拥有的方法。
反射,指在PHP运行状态中,扩展分析PHP程序,导出或提取出关于类、方法、属性、参数等的详细信息,包括注释。这种动态获取信息以及动态调用对象方法的功能称为反射API。
1.2 获取对象属性和方法
getMethods和getProperties分别用来获取对象的所有方法和所有属性,返回对象数组,然后通过getName来获取具体的方法和属性即可。但都是必须先通过反射获取类的原型, 即使用$reflect
= new ReflectionObject($student);来获取对象的原型。
下面是使用反射API来获取对象的属性和方法:
1
<?php2
/**3
* 使用反射API获取对象的属性和方法4
* Created by PhpStorm.5
* User: Administrator6
* Date: 2017/7/107
* Time: 15:228
*/9
10
class Person311
{12
public $name;13
public $gender;14
15
public function say()16
{17
echo $this->name . " is " . $this->gender . "
";18
}19
20
public function __set($name, $value)21
{22
echo "Setting $name to $value
";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 "
对象的属性有:
";47
foreach ($props as $prop) {48
echo $prop->getName() . "
";49
}50
print_r($props);51
52
// 获取对象方法列表53
$methods = $reflect->getMethods();54
echo "
对象的方法有:
";55
foreach ($methods as $method) {56
echo $method->getName() . "
";57
}58
print_r($methods);59
60
echo "------------------分隔线-----------------
";61
/**62
* 也可以不用反射 API,而使用class函数来获取对象属性的关联数组以及更多的信息,但是使用反射 API可以获得更多的信息63
*/64
// 返回对象属性的关联数组65
echo "对象属性的关联数组:
";66
print_r(get_object_vars($student));67
68
// 返回对象属性列表所属的类69
echo "对象属性列表所属的类:
";70
print_r(get_class($student));71
72
// 类属性73
echo "类属性:
";74
print_r(get_class_vars(get_class($student)));75
76
// 返回由类的方法名组成的数组77
echo "类的方法名组成的数组:
";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
<?php2
/**3
* 使用反射 API 来还原类的原型4
* Created by PhpStorm.5
* User: Administrator6
* Date: 2017/7/137
* Time: 17:428
*/9
10
class Person411
{12
public $name;13
private $gender;14
15
public function say()16
{17
echo $this->name . " is " . $this->gender . "
";18
}19
20
public function __set($name, $value)21
{22
echo "Setting $name to $value
";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}
{
";60
61
// 如果类有属性的话则进行排序62
is_array($properties) && ksort($properties);63
64
// 输出类的属性65
foreach ($properties as $key => $value) {66
echo " ";67
echo $value->isPublic() ? 'public' : '' , $value->isPrivate() ? 'private' : '' , $value->isProtected() ? 'protected' : '' , $value->isStatic() ? 'static' : '';68
echo " {$key}
";69
}70
71
echo "
";72
73
// 对类的方法进行排序74
if (is_array($methods)) {75
ksort($methods);76
}77
78
// 输出类的方法79
foreach ($methods as $key => $value) {80
echo " ";81
echo $value->isPublic() ? 'public' : '' , $value->isPrivate() ? 'private' : '' , $value->isProtected() ? 'protected' : '' , $value->isStatic() ? 'static' : '';82
echo " function {$key}() {}
";83
}84
85
echo "}
";86
87
/**88
运行:89
class Person490
{91
private gender92
public name93
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
<?php2
/**3
* 使用反射API实现简单的动态代理4
* Created by PhpStorm.5
* User: Administrator6
* Date: 2017/7/147
* Time: 9:338
*/9
10
class MySql11
{12
public function connect($db)13
{14
echo "已经连接到数据库${db[0]}
";15
}16
}17
18
/**19
* Class SqlProxy20
* SqlProxy类实现了根据动态传入参数,代替实际的类MySql类的运行,并且在方法运行前后进行拦截,并且可以动态改变类中的方法和属性,这就是简单的动态代理21
*/22
class SqlProxy23
{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
";38
$method->invoke($obj, $arguments);39
echo "方法后拦截
";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和插件开发中,使用反射很常见,但是反射的消耗也很大,在可以找到替代方案的情况下,不要滥用反射。
很多时候,善用反射能够保持代码的优雅和简洁,但反射也会破坏类的封装性,因为反射可以使本不应该暴露的方法或者属性被强制暴露了出来,这既是优点也是缺点。