什么是依赖注入?其实这个特性在这个框架各个地方都有体现,实现代码的解耦。就是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