php反射

PHP反射

反射就是让你拥有剖析类、函数的能力

反射并不会对你实现业务有任何影响

但是你如果想写出结构优雅的程序,想写出维护性和扩展性都很高的程序

学习反射是 必不可少 的。

目录

PHP内置了一组反射类来实现类、方法以及参数的解析

常用的有:

  • ReflectionClass 解析类
  • ReflectionProperty 类的属性的相关信息
  • ReflectionMethod 类方法的有关信息
  • ReflectionParameter 取回了函数或方法参数的相关信息

这些类大多数都继承于 ReflectionFunctionAbstract 类,该类常用的方法有:

  • getFileName() :获取文件名称
  • getName() : 获取函数名称
  • getNamespaceName() :获取命名空间
  • getNumberOfParameters():获取参数数目
  • getNumberOfRequiredParameters() : 获取必须输入的参数个数
  • getParameters() : 获取参数,返回一个 ReflectionParameter 类,参数为空时返回空数组
  • getStaticVariables() :获取静态变量
  • inNamespace() :检查是否处于命名空间
  • isUserDefined() : 检查是否是用户定义

详情参考 : http://php.net/manual/zh/class.reflectionfunctionabstract.php

ReflectionClass

传入实例化的类或者类名都可以。

常用方法:

  • is 开头的函数用于判断
    • isInstantiable() :判断是否是类的实例,也就是是否可以实例化
  • get 开头的函数获取类相关信息
    • getConstructor() : 或许构造函数相关信息,返回 ReflectionMethod 对象,没有则返回空
    • getProerty($name) : 获取某一个属性值,返回 ReflectionProperty 对象【可用has先判断是否含有该属性值】
    • getProperties() :获取属性列表,返回一组 ReflectionProperty 对象
    • getMethod($method_name) :获取某一个方法,返回 ReflectionMethod 对象
    • getMethods():获取方法列表,返回一组 ReflectionMethod 对象
  • new 创建一个新的实例
    • newInstanceArgs() : 从给定的参数实例化一个类
ReflectionParameter

常用方法:

  • isDefaultValueAvailable() :该参数是否拥有默认值
  • getDefaultValue() : 获取参数的默认值,参数没有默认值时该方法会抛出异常
  • getClass() : 获得参数类型提示

详情参考 : http://php.net/manual/zh/class.reflectionparameter.php

ReflectionMethod

传入类名和方法名

常用方法:

  • invoke() :传入对象和参数执行该方法

详情参考 :http://php.net/manual/zh/class.reflectionmethod.php

实例化类

传入类名和初始化参数,使用相关的反射类实现类的实例化,包括参数依赖注入。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class People 
{
protected $name;

protected $phone;

public function __construct(Phone $phone, $name = '小明')
{
$this->phone = $phone;
$this->name = $name;
}

public function has()
{
echo $this->name . '拥有' . $phone->name;
}
}

class Phone
{
protected $name;

public function __construct($phoneName = 'iphoneX')
{
$this->name = $phoneName;
}
}

$phone = new Phone('小米手机');
$people = new People($phone);
$people->has(); // 小明拥有小米手机

使用反射类相关知识封装方法 make() 用来实例化类,

1
2
3
$phone = new Phone('小米手机');
$people = new People($phone);
$people->has(); // 小明拥有小米手机

则上面实例化类的代码只需要一行代码就可以。

1
make('People', ['phoneName' => '小米手机','name' => '爱学习的小明']);

make() 方法代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/** 
* 执行类实例化
* 假如构造函数中有类型提示的参数会自动注入到类中
**/
function make($class, $vars = []) {
if (!class_exists($class)) {
echo '类不存在';
exit;
}
// 实例化反射类
$ref = new ReflectionClass($class);
// 判断类是否能够实例化,例如抽象类便不可实例化,不能单纯只判断类是否存在
if (!$ref->isInstantiable()) {
echo $class . '不可以实例化';
exit;
}
// 获取该类是否有构造函数,返回 ReflectionMethod 类
$construct = $ref->getConstructor();
if (is_null($construct)) {
// 如果没有构造函数直接实例化该类即可
return new $class;
}
// 获取该方法的参数,如果参数不为空则返回一个数组,数组里的元素是 ReflectionParamters 对象
// 如果该方法没有参数则直接返回 null
$parmeters = $construct->getParameters();
// injectionParameter() 方法是执行依赖注入
// 详细代码参考下文依赖注入相关
$resolveParams = is_null($parmeters) ? [] : injectionParameter($parmeters, $vars);
// 最后用处理过的参数去实例化该类并返回
// 处理过的参数已经帮我们实现 new Phone(’小米手机‘)
// 并将该对象存入处理完的参数

//return new $class(...$resolveParams); 两种实例化方式都可以
return $ref->newInstanceArgs($resolveParams);
}

依赖注入

injectionParameters() :解析参数,拼凑所需的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* $parmters 数组里的元素是 ReflectionParamters 对象
*
**/
function injectionParameter(array $parmeters, $vars = [])
{
$resolveParams = [];
foreach ($parmeters as $k => $v) {
// 获取参数名称,需要通过变量名定位是否有传入参数
$name = $v->getName();
// 判断 $value 是否有传值进来,有的话直接用传进来的值
if (isset($vars[$name])) {
$resolveParams[] = $vars[$name];
continue;
}
// 判断参数是否有默认值
$default = $v->isDefaultValueAvailable() ? $v->getDefaultValue() : null;
// 有的话优先使用默认值
if (!is_null($default)) {
$resolveParams[] = $default;
continue;
}
// 如果参数默认值为空则判断参数是否有类型提示,如果有实例化并注入参数中
if ($v->getClass()) {
$resolveParams[] = make($v->getClass()->getName(), $vars);
continue;
}
// 该参数不能为空,抛出异常,这里先暴力处理
echo $name . "不能为空";
exit;
}

return $resolveParams;
}

注入依赖最核心方法就是通过getClass 获取参数类型提示

ReflectionParamters->getClass() :获取参数类型提示

在该参数没有默认值且有设置类型提示时

递归去调用 make() 方法将类实例化出来后注入到参数里

调用类方法

action():传入实例化的类,对应的方法名之后执行该方法

1
2
3
4
5
6
7
8
9
10
11
12
13
function action($class, $method, $vars = [])
{
if (!method_exists($class, $method)) {
echo '方法不存在';
exit;
}
$ref = new ReflectionMethod($class, $method);
$parameters = $ref->getParameters();
$resolveParams = is_null($parameters) ? [] : injectionParameter($parameters, $vars);

// return $class->{$method}(...array_values($resolveParams));
return $ref->invoke($class, ...$resolveParams);
}

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
<?php
class Bag
{
public $name;

public function __construct($bagName = '书包')
{
$this->name = $bagName;
}
}

class Pencil
{
public $name;

public function __construct($pencilName = '铅笔')
{
$this->name = $pencilName;
}
}

class People
{
public $bag;

public $name;

public function __construct(Bag $bag, $peopleName = 'test')
{
$this->bag = $bag;
$this->name = $peopleName;
}

public function bag()
{
echo $this->name . ' has a ' . $this->bag->bag;
}

public function has(Pencil $pencil, $say = '')
{
echo "hello, I'm is {$this->name}." . "<br />";
echo "I has {$pencil->name}." . "<br />";
echo "I has {$this->bag->name}." . "<br />";
if ($say) {
echo $this->name . "say {$say}";
}
}
}

/**
* 执行类实例化
* 当执行类对应方法时再去执行方法的反射类
* 去注入方法所依赖的类
**/
function make($class, $vars = []) {
if (!class_exists($class)) {
echo '类不存在';
exit;
}
// 实例化反射类
$ref = new ReflectionClass($class);
// 判断类是否能够实例化
if (!$ref->isInstantiable()) {
echo $class . '不可以实例化';
exit;
}
// 获取该类是否有构造函数
$construct = $ref->getConstructor();
if (is_null($construct)) {
return new $class;
}
$parmeters = $construct->getParameters();
$resolveParams = is_null($parmeters) ? [] : injectionParameter($parmeters, $vars);
// 最后用处理过的参数去实例化该类并返回,$ref之前已经传入过要实例化的类名
// 因为我读的参数都由该类的反射类解析而来,所系顺序大小都一样
//return new $class(...$resolveParams);
return $ref->newInstanceArgs($resolveParams);
}

function action($class, $method, $vars = [])
{
if (!method_exists($class, $method)) {
echo '方法不存在';
exit;
}
$ref = new ReflectionMethod($class, $method);
$parameters = $ref->getParameters();
$resolveParams = is_null($parameters) ? [] : injectionParameter($parameters, $vars);

// return $class->{$method}(...array_values($resolveParams));
return $ref->invoke($class, ...$resolveParams);
}

function injectionParameter(array $parmeters, $vars = [])
{
$resolveParams = [];
foreach ($parmeters as $k => $v) {
// 获取参数名称,需要通过变量名定位是否有传入参数
$name = $v->getName();
// 判断 $value 是否有传值进来,有的话直接用传进来的值
if (isset($vars[$name])) {
$resolveParams[] = $vars[$name];
continue;
}
// 判断参数是否有默认值
$default = $v->isDefaultValueAvailable() ? $v->getDefaultValue() : null;
// 有的话优先使用默认值
if (!is_null($default)) {
$resolveParams[] = $default;
continue;
}
// 如果参数默认值为空则判断参数是否有类型提示,如果有实例化并注入参数中
if ($v->getClass()) {
$resolveParams[] = make($v->getClass()->getName(), $vars);
continue;
}
// 该参数不能为空,抛出异常,这里先暴力处理
echo $name . "不能为空";
exit;
}

return $resolveParams;
}

$people = make('People', [
'peopleName' => '小明',
'pencilName' => '2B铅笔',
'bagName' => '自定义书包',
]);
action($people, 'has');
// 输出
// hello, I'm is 小明.
// I has 铅笔.
// I has 自定义书包.

总结 :

make() : 实例化一个类,假如构造函数有依赖的时候实例化并将实例注入到类里

injectionParameter() :解析参数,当参数没有传值,也没有默认值,但是有类型提示的情况下。实例化该类实例并注入到参数中返回。

action():执行类里面的一个方法,假如方法参数里依赖于其他类的话将类实例化并注入到方法的参数中。

该示例模拟了框架常用的一个依赖注入的功能。

通过路由解析到实际执行的类和方法,然后执行该方法。

上面的示例就大概模仿了去执行路由对应的动作这个流程。

感谢您的阅读,本文由 Double-c 版权所有。如若转载,请注明出处:Double-c(https://double-c.github.io/2018/10/24/php-reflection/
Mac命令行终端下使用shadowsocks翻墙
Git 配置多用户