Laravel服务容器实现原理

前言

通过实现laravel 框架功能,以便深入理解laravel框架的先进思想。

什么是服务容器

服务容器是用来管理类依赖与运行依赖注入的工具。Laravel框架中就是使用服务容器来实现 控制反转依赖注入

什么是控制反转(IoC)和依赖注入(DI)

控制反转(IoC) 就是说把创建对象的控制权进行转移,以前创建对象的主动权和创建时机是由自己把控的,而现在这种权力转移到第三方,也就是 Laravel 中的容器。

依赖注入(DI)则是帮助容器实现在运行中动态的为对象提供提依赖的资源。

概念容易不太容易让人理解,举个栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//我们构建一个人的类和一个狗的类
class People
{
public $dog = null;

public function __construct()
{
$this->dog = new Dog();
}

public function putDog(){
return $this->dog->dogCall();
}

}

class Dog{
public function dogCall(){
return '汪汪汪';
}
}

这个人在遛狗,突然遇到了死对头,他于是放狗咬人

1
2
$people = new People();
$people->putDog();

在这个操作中,people类要执行putDog()这个方法,需要依赖Dog类,一般我们像上面一样,在people中利用构造函数来添加这个Dog依赖。如果使用控制反转 依赖注入则是这个样子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class People
{
public $dog = null;

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

public function putDog(){
return $this->dog->dogCall();
}

}

People类通过构造参数声明自己需要的 依赖类,由容器自动注入。这样就实现了程序的有效解耦,好处在这就不多说了。

Laravel容器依赖注入的实现

实现原理需要了解的知识点:

闭包(匿名函数):
匿名函数(Anonymous functions),也叫闭包函数(closures),允许 临时创建一个没有指定名称的函数

反射:PHP 5 以上版本具有完整的反射 API,添加了对类、接口、函数、方法和扩展进行反向工程的能力。 此外,反射 API 提供了方法来取出函数、类和方法中的文档注释

理解了闭包和反射的基本用法我们来看Laravel中是怎么实现容器的,下面代码是我对laravel框架容器部分代码的简化核心版:
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
136
137
138
139
140
141
class Containerc
{
/**
* 容器绑定,用来装提供的实例或者 提供实例的回调函数
* @var array
*/
public $building = [];

/**
* 注册一个绑定到容器
*/
public function bind($abstract, $concrete = null, $shared = false)
{
if(is_null($concrete)){
$concrete = $abstract;
}

if(!$concrete instanceOf Closure){
$concrete = $this->getClosure($abstract, $concrete);
}

$this->building[$abstract] = compact("concrete", "shared");
}

//注册一个共享的绑定 单例
public function singleton($abstract, $concrete, $shared = true){
$this->bind($abstract, $concrete, $shared);
}

/**
* 默认生成实例的回调闭包
*
* @param $abstract
* @param $concrete
* @return Closure
*/
public function getClosure($abstract, $concrete)
{
return function($c) use($abstract, $concrete){
$method = ($abstract == $concrete)? 'build' : 'make';

return $c->$method($concrete);
};
}

/**
* 生成实例
*/
public function make($abstract)
{
$concrete = $this->getConcrete($abstract);

if($this->isBuildable($concrete, $abstract)){
$object = $this->build($concrete);
}else{
$object = $this->make($concrete);
}

return $object;
}

/**
* 获取绑定的回调函数
*/
public function getConcrete($abstract)
{
if(! isset($this->building[$abstract])){
return $abstract;
}

return $this->building[$abstract]['concrete'];
}

/**
* 判断 是否 可以创建服务实体
*/
public function isBuildable($concrete, $abstract)
{
return $concrete === $abstract || $concrete instanceof Closure;
}

/**
* 根据实例具体名称实例具体对象
*/
public function build($concrete)
{
if($concrete instanceof Closure){
return $concrete($this);
}

//创建反射对象
$reflector = new ReflectionClass($concrete);

if( ! $reflector->isInstantiable()){
//抛出异常
throw new \Exception('无法实例化');
}

$constructor = $reflector->getConstructor();
if(is_null($constructor)){
return new $concrete;
}

$dependencies = $constructor->getParameters();
$instance = $this->getDependencies($dependencies);

return $reflector->newInstanceArgs($instance);

}

//通过反射解决参数依赖
public function getDependencies(array $dependencies)
{
$results = [];
foreach( $dependencies as $dependency ){
$results[] = is_null($dependency->getClass())
?$this->resolvedNonClass($dependency)
:$this->resolvedClass($dependency);
}

return $results;
}

//解决一个没有类型提示依赖
public function resolvedNonClass(ReflectionParameter $parameter)
{
if($parameter->isDefaultValueAvailable()){
return $parameter->getDefaultValue();
}
throw new \Exception('出错');

}

//通过容器解决依赖
public function resolvedClass(ReflectionParameter $parameter)
{
return $this->make($parameter->getClass()->name);

}

}

容器的工作流程

接着上面遛狗的例子:

1
2
3
4
5
6
7
8
9
10
//实例化容器类
$app = new Container();
//向容器中填充Dog
$app->bind('Dog','App\Dog');
//填充People
$app->bind('People', 'App\People');
//通过容器实现依赖注入,完成类的实例化;
$people = $app->make('People');
//调用方法
echo $people->putDog();

上面示例中我们先实例化容器类,然后使用bind()方法 绑定接口和 生成相应的实例的闭包函数。然后使用make() 函数生成实例对象,在make()中会调用 isBuildable($concrete, $abstract) 来判断 给定的服务实体($concrete参数)是否可以创建,可以创建 就会调用 build($concrete) 函数 ,build($concrete) 函数会判断传的参数是 是 闭包 还是 具体类名 ,如果是闭包则直接运行,如果是具体类名的话,则通过反射获取该类的构造函数所需的依赖,完成实例化。

重点理解 下面这几个函数中 反射的用法,应该就很好理解了

1
2
3
4
build($concrete)
getDependencies(array $dependencies)
resolvedNonClass(ReflectionParameter $parameter)
resolvedClass(ReflectionParameter $parameter)

最后

IoC 理解起来是有点难度,可能文中描述让你感觉不是很清楚,可以将文中代码 在php中用debug观察 运行状态。
理解了容器的具体实现原理,再去看Laravel中的相关实现,就会感觉豁然开朗。