什么是依赖注入?其实这个特性在这个框架各个地方都有体现,实现代码的解耦。就是A类里面的需要依赖到B类,依赖注入的话A类中只依赖接口,而在A类实例化的时候注入实现接口的B类,大大降低了代码的耦合。

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
interface Notify
{
public function send();
}
class SMS implements Notify
{
public function send()
{
echo 'send SMS';
}
}
class Voice implements Notify
{
public function send()
{
echo 'send Voice';
}
}
class User
{
public function sendNotify(Notify $notify)
{
$notify->send();
}
}
$user = new User;
$user->sendNotify(new SMS);

这里我简化了日常的业务逻辑。我们要对一个用户发送通知,可以用短信发送也可以通过语音发送等等。如果不用依赖注入,我们直接在User类你们new一个SMS或者Voice类。当然也是没问题的。但是突然有一天,老板说改成Email对用户发送通知,好了 我们User类要改,具体调用user类的地方也要改。当然实际的业务逻辑更复杂。在日积月累的情况下代码就变得更不好维护了。依赖注入让我们的业务中调用的是接口,而不是具体的类,实现了代码的解耦。这样子,就算老板让我们改成用邮件对用户发送通知,我们只要新建个实现了Notify接口的Email类,然后在具体调用的时候传这个Email类过去就行了。

Laravel中依赖注入的实现

在用Laravel框架的时候,我们在控制器只要把类注入进去,就能直接调用,貌似没有看到具体在哪里new的。那是因为Laraver的控制器要求你都要继承它的BaseController。框架在运行的时候直接从容器中解析你注入进去的类给你实例化了。什么是容器,就是一个智能并高级的工厂。Laravel框架在启动的时候会把你配置文件的类实例化到容器内。容器可以通过闭包、PHP的反射类ReflectionClass来解析出具体的对象。所以我们就看不到具体在使用Laravel的时候,看它一直在注入类,却看不到它具体是在哪里new的。

容器类(Container)的原理

Larvel的容器类在/vendor/laravel/framework/src/Illuminate/Container/Container。我们打开可以看到一个非常长的类。咋一看不知道干嘛的,看不懂,一开始我也是。所以我简化了下容器,自己写了个容器类。

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
class Container
{
protected $bindings = [];
public function bind($abstract, $concrete = null, $shared = false)
{
if(! $concrete instanceof Closure){
$concrete = $this->getClosure($abstract,$concrete);
}
$this->bindings[$abstract] = compact('concrete', 'shared');
}
public function getClosure($abstract, $concrete)
{
return function($c, $parameters = []) use ($abstract, $concrete)
{
$method = ($abstract == $concrete) ? 'build' : 'make';
return $c->$method($concrete,$parameters);
};
}
public function make($abstract,$parameters = [])
{
$concrete = $this->getConcrete($abstract);
if ($this->isBuildable($concrete, $abstract)) {
$object = $this->build($concrete,$parameters);
}
else{
$object = $this->make($concrete,$parameters);
}
return $object;
}
protected function isBuildable($concrete, $abstract)
{
return $concrete === $abstract || $concrete instanceof Closure;
}
protected function getConcrete($abstract)
{
if(! isset($this->bindings[$abstract]))
{
return $abstract;
}
return $this->bindings[$abstract]['concrete'];
}
public function build($concrete,$parameters=[])
{
if($concrete instanceof Closure){
return $concrete($this,$parameters);
}
$reflector = new ReflectionClass($concrete);
if(! $reflector->isInstantiable()) {
return $message= "Target [$concrete] is not instantiable.";
}
$constructor = $reflector->getConstructor();
if (is_null($constructor)) {
return new $concrete;
}
$dependencies = $constructor->getParameters();
$instances = $this->getDependencies($dependencies,$parameters);
return $reflector->newInstanceArgs($instances);
}
protected function getDependencies($parameters,array $primitives = [])
{
$dependencies = [];
foreach ($parameters as $parameter)
{
$dependency = $parameter->getClass();
if(array_key_exists($parameter->name,$primitives)){
$dependencies[] = $primitives[$parameter->name];
}
else if(is_null($dependency)){
$dependencies[] = NULL;
}
else{
$dependencies[] = $this->resolveClass($parameter);
}
}
return (array) $dependencies;
}
protected function resolveClass(ReflectionParameter $parameter)
{
return $this->make($parameter->getClass()->name);
}
}
interface Notify
{
public function send();
}
class SMS implements Notify
{
public function send()
{
echo 'SMS send';
}
}
class Mail implements Notify
{
public function send()
{
echo 'Mail send';
}
}
class Push implements Notify
{
public function send()
{
echo 'Push send';
}
}
class Message
{
protected $notifyTool;
protected $config;
public function __construct(Notify $notify,$parameters = [])
{
$this->notifyTool = $notify;
$this->config = $parameters;
}
public function sendMessage()
{
$this->notifyTool->send();
}
}
$app = new Container();
$app->bind('Notify','Sms');
$app->bind('message','Message');
$msg = $app->make('message',['parameters'=>'额外参数']);
$msg->sendMessage();

容器的实现过程主要依赖php的反射类,一个参数传过来的时候,通过反射可以知道这个类是否可以被实例化,方法存不存在。你看,这样的话我们是不是基本上没有new了。其实不是没new了,而是通过反射,只要写好这样一个通用的容器,new的过程还是有的,只不过都在容器里面了。我写的这个容器还是要通过手动绑定参数来实现,Laravel的服务容器当然更高级,Laravel容器的核心代码基本都在我这里体现了。Laravel的容器能根据类的依赖需求,自动在已经注册、绑定的一堆实例中找到符合需求的,自动注入。

到这里,依赖注入的实现基本上也讲完了。Laravel还有个Facade(门面),其实我感觉这个没多大用,但是有些人在刚接触Laravel的时候有困惑,我稍微带过一下。门面其实把容器里面绑定好的类再套一层,然后用php的魔术方法__callStatic()来实现像是静态的调用不是静态的各个方法。

更多关于容器IOC的文章

http://laravelacademy.org/post/769.html

https://segmentfault.com/a/1190000002411255