PHP反射机制实现自动依赖注入

思考并回答以下问题:

  • 依赖注入为什么又叫控制反转?
  • 只要知道类的名字就能实例化该类。依赖注入就是传类名做参数,然后保证有这个类就好了。怎么理解?
  • 手动和自动依赖注入的区别是什么?
  • 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
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
class A
{
protected $bObj;

/**
* 构造函数依赖注入
* 用于测试多级依赖注入 A依赖B,B依赖C
*
* @param B $b [使用依赖注入B]
*/
public function __construct(B $b)
{
$this->bObj = $b;
}

/**
* 方法调用依赖注入
*
* @param C $c [依赖注入C]
* @param string $param [这个是自己手动填写的参数]
* @return void
*/
public function aa(C $c, $param)
{
$c->cc();
echo "<br/>";

echo 'params:' . $param;
}

/**
* 验证依赖注入是否成功
* @return void
*/
public function aaa()
{
$this->bObj->bbc();
}
}

class B
{
protected $cObj;

/**
* 构造函数依赖注入
*
* @param C $c [使用依赖注入C]
*/
public function __construct(C $c)
{
$this->cObj = $c;
}

public function bb()
{
echo 'this is B->bb()';
}

public function bbc()
{
$this->cObj->cc();
}
}

class C
{
public function cc()
{
echo 'this is C->cc()';
}
}

测试构造函数的依赖注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 使用IOC来创建A类的实例,A的构造函数依赖B类,B的构造函数依赖C类。
$aObj = IOC::getInstance('A');
$aObj->aaa(); // 输出:this is C->cc() , 说明依赖注入成功。

// 打印$aObj
print_r($aObj);
// 打印结果,可以看出A中有B实例,B中有C实例,说明依赖注入成功。
/*
A Object
(
[bObj:protected] => B Object
(
[cObj:protected] => C Object
(
)

)
)
*/

测试方法依赖注入

1
2
3
4
5
6
7
8
9
$methodResult = IOC::make('A', 'aa', ['this is param a']);

print_r($methodResult);

// 输出结果,可以看出依赖注入成功。
/*
this is C->cc()
params:this is param a
*/

从上面两个例子可以看出我们创建对象或者调用方法时,根本就不用知道该类或该方法依赖了哪个类。使用反射机制可以轻松的为我们自动注入所需要的类。

源码

使用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
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
135
/**
* 工具类,使用该类来实现自动依赖注入。
*
*/
class IOC
{
/*
* 获得类的对象实例
*
* @param string $className [类名]
* @return object 实例对象
*/
public static function getInstance($className)
{
$paramArr = self::getMethodParams($className);

return (new ReflectionClass($className))->newInstanceArgs($paramArr);
/*
A Object
(
[bObj:protected] => B Object
(
[cObj:protected] => C Object
(
)

)
)
*/
}

/**
* 执行类的方法
*
* @param string $className [类名]
* @param string $methodName [方法名称]
* @param array $params [额外的参数]
* @return
*/
public static function make($className, $methodName, $params = [])
{
// 获取类的实例
$instance = self::getInstance($className);

// 获取该方法所需要依赖注入的参数
$paramArr = self::getMethodParams($className, $methodName);

return $instance->{$methodName}(...array_merge($paramArr, $params));
}

/**
* 获得类的方法参数,只获得有类型的参数
*
* @param string $className [类名]
* @param string $methodsName [构造函数]
* @return array [参数数组]
*/
protected static function getMethodParams($className, $methodsName = '__construct')
{
// 获得该类的反射类
$class = new ReflectionClass($className); // ReflectionClass Object ( [name] => A )

$paramArr = []; // 记录参数,和参数类型

// 判断该类是否有构造函数
if ($class->hasMethod($methodsName))
{
// 获得构造函数
$construct = $class->getMethod($methodsName); // ReflectionMethod Object ( [name] => __construct [class] => A )

// 判断构造函数是否有参数
$params = $construct->getParameters(); // Array ( [0] => ReflectionParameter Object ( [name] => b ) )

if (count($params) > 0)
{
// 判断参数类型
foreach ($params as $key => $param)
{
if ($paramClass = $param->getClass()) // ReflectionClass Object ( [name] => B )
{
// 获得参数类型名称
$paramClassName = $paramClass->getName(); // B

// 获得参数类型
// 递归
$args = self::getMethodParams($paramClassName);

// print_r($args);

/*
Array
(
)
Array
(
[0] => C Object
(
)
)
*/

$paramArr[] = (new ReflectionClass($paramClass->getName()))->newInstanceArgs($args); // $args这个参数以array形式传递到类的构造函数。
}
}
}
}

// print_r($paramArr);

/*
Array
(
)
Array
(
[0] => C Object
(
)

)
Array
(
[0] => B Object
(
[cObj:protected] => C Object
(
)

)
)
*/

return $paramArr;
}
}

laravel中利用反射实现依赖注入

在一个类中经常会依赖于其他的对象,先看一下经典的写法。

1
2
3
4
5
6
7
8
9
10
class Foo 
{
public $bar;

public function __construct() {
$this->bar = new Bar();
}
}

$foo = new Foo();

当类的依赖发生改变时,比如Bar这个类需要实例化参数时,而依赖于它的类有很多,总不能一个一个地去修改吧。

再看一下使用依赖注入怎么做、

1
2
3
4
5
6
7
8
9
10
11
12
class Foo 
{
public $bar;

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

$bar = new Bar();
$foo = new Foo($bar);

将Bar类在外部实例化好后,作为一个参数传入进Foo类,从而实现了控制反转,假如现在Bar类需要参数了,外部修改就好了,不必一个个地去修改依赖于它的类。

在laravel中,经常写出下面这种代码:

1
2
3
4
5
6
7
class SomeController 
{
public function index(Request $request)
{
dd($request->all());
}
}

只要在方法参数中申明Request $request,就可以直接使用$request对象了,非常地方便。

其实laravel在背后利用PHP的反射机制为我们做了$request = new Request这一步。反射是一种类的反省能力,可以导出类的详细信息包括属性、方法、甚至注释等等。

实现,看代码:

1
2
3
4
5
6
7
8
9
10
11
$method = new ReflectionMethod('SomeController', 'index');
$args = [];

foreach($method->getParameters() as $parameter)
{
if ($class = $parameter->getClass())
{
$args[] = new $class->name; //$request = new Request
}
}
$method->invokeArgs(new SomeController, $args);

通过ReflectionMethod获取类方法的参数,如果参数是其他的类,就实例化后作为参数使用ReflectionMethod::invokeArgs 传入到类方法中,原理就是这么简单。

通常使用new ReflectionClass(‘className’) 来反射类,ReflectionMethod来反射类方法。

总结

只要熟悉php的反射机制,依赖注入并不难实现,上面的代码为了方便理解,所以写的简单粗暴,在实际的项目中肯定不会这么简单,比如:会对注入的类和参数进行配置,比如会缓存实例化过的类,下次需要该类的实例时,可以直接使用,而不用在重新初始化,等等。不过相信原理了解了,其他的可以随着项目的需求自己去完善。

0%