【反射】PHP的反射机制【原创】

【反射】PHP的反射机制【原创】

摘要:主要是参考列旭松、陈文著的《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和插件开发中,使用反射很常见,但是反射的消耗也很大,在可以找到替代方案的情况下,不要滥用反射。

很多时候,善用反射能够保持代码的优雅和简洁,但反射也会破坏类的封装性,因为反射可以使本不应该暴露的方法或者属性被强制暴露了出来,这既是优点也是缺点。


posted @ 2017-07-14 21:45 Newman·Li 阅读( ...) 评论( ...) 编辑 收藏
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值