laravel服務容器(IOC過程詳解)
對於laravel的服務容器不是很理解看了《Laravel框架關鍵技術解析》和網上的一些資料後對於服務容器有了一些自己的理解,在這裡分享給大家,過程比較曲折,看了比較長的時間,包括回撥函式,use用法,反射函式等需要查相關的資料,稍後我也做一些更新。
簡化版的IoC容器類,使用bind()函式進行服務繫結,使用make()函式來進行解析,最後在容器內由build()函式建立並返回例項。下面是具體如何使用。
IOC依賴注入過程:
1、make 容器已經存在介面和例項的關係, 獲取回撥函式;('Go_To_School', 'Bicycle' )和('studentaaaa', 'Student')
2、執行make('studentaaaa'),回撥函式存在,執行回撥函式;(回撥函式開始例項化【在初始bind的時候】物件Student);
3、例項化過程:利用反射先找到Student的建構函式的引數:trafficTool;
再通過反射機制例項化物件時的依賴,找到trafficTool的介面:Go_To_School;找到Go_To_School去找相關的make,對Go_To_School進行例項化;
【重複2的過程:Go_To_School回撥函式存在,執行回撥函式;(回撥函式開始例項化【在初始bind的時候】物件Bicycle);
利用反射先找到Bicycle,因該函式不需要建構函式,所以直接new一個物件來返回】;
4、利用反射機制函式例項化物件:object(Student)[7]private 'trafficTool' => object(Bicycle)[9]
5、容器初始化完成;
程式碼如下:
<?php //設計公共介面 interface Go_To_School { public function go(); } //實現交通工具類 class Foot implements Go_To_School { public function go() { echo 'walt to school'; } } class Car implements Go_To_School { public function go() { echo 'drive to school'; } } class Bicycle implements Go_To_School { public function go() { echo 'ride to school;'; } } //設計學生類,實現去學校時要依賴的交通工具例項 class Student { private $trafficTool; public function __construct(Go_To_School $trafficTool) { $this->trafficTool = $trafficTool; } public function go_to_school(){ $this->trafficTool->go(); } } class Container { //用於裝提供例項的回撥函式,真正的容器還會裝例項等其他內容 //從而實現單例等高階功能 public $bindings = []; //繫結介面和生成相應例項的回撥函式 public function bind($abstract, $concrete=null, $shared=false) { //如果提供的引數不是回撥函式,則產生預設的回撥函式 即concrete變為回撥函式,不是一個字串 if(!$concrete instanceof Closure) { $concrete = $this->getClosure($abstract, $concrete); } $this->bindings[$abstract] = compact('concrete', 'shared'); } //預設生成例項的回撥函式 protected function getClosure($abstract, $concrete) { //使用bing函式的時候不執行該函式;僅僅把$abstract, $concrete 放入$container物件中; return function($container) use ($abstract, $concrete) { $method = ($abstract == $concrete) ? 'build' : 'make'; echo "回撥函式".$abstract; var_dump( $concrete); //利用回撥函式來例項化 return $container->$method($concrete); }; } //解決介面和要例項化類之間的依賴關係 public function make($abstract) { $concrete = $this->getConcrete($abstract); echo "make<br>"; var_dump($concrete); if($this->isBuildable($concrete, $abstract)) { echo "build<br>"; $object = $this->build($concrete); } else { echo "make2<br>"; $object = $this->make($concrete); } echo "make end"; return $object; } protected function isBuildable($concrete, $abstract) { return $concrete === $abstract || $concrete instanceof Closure; } //獲取繫結的回撥函式 protected function getConcrete($abstract) { var_dump($this->bindings);echo $abstract; //如果是介面或者別名(Go_To_School,studentaaaa)返回回撥函式來執行;如果是例項類,則返回例項類字串 if(!isset($this->bindings[$abstract])) { echo "stringabstract"; return $abstract; } return $this->bindings[$abstract]['concrete']; } //例項化物件 public function build($concrete) { if($concrete instanceof Closure) { echo "instanceof"; //執行回撥函式 return $concrete($this); } echo "build detail<br>"; $reflector = new ReflectionClass($concrete); //檢查類是否可例項化 if(!$reflector->isInstantiable()) { echo $message = "Target [$concrete] is not instantiable"; } // 獲取類的建構函式 $constructor = $reflector->getConstructor(); if(is_null($constructor)) { return new $concrete; } $dependencies = $constructor->getParameters(); //public 'name' => string 'trafficTool' $instances = $this->getDependencies($dependencies); var_dump($reflector->newInstanceArgs($instances)); //object(Student)[7]private 'trafficTool' => object(Bicycle)[9] return $reflector->newInstanceArgs($instances); } //解決通過反射機制例項化物件時的依賴 protected function getDependencies($parameters) { $dependencies = []; foreach($parameters as $parameter) { // public 'name' => string 'Go_To_School' $dependency = $parameter->getClass(); if(is_null($dependency)) { $dependencies[] = NULL; } else { $dependencies[] = $this->resolveClass($parameter); } } return (array)$dependencies; } protected function resolveClass(ReflectionParameter $parameter) { var_dump($parameter->getClass()->name); //Go_To_School return $this->make($parameter->getClass()->name); } } //例項化IOC容器 $ioc = new Container(); //填充容器 $ioc->bind('Go_To_School', 'Bicycle'); //第一個引數'Go_To_School'是介面, //第二個引數'Car'是交通工具類 $ioc->bind('studentaaaa', 'Student'); //第一個引數'student'可以理解為服務別名,用make() //例項化的時候直接使用別名即可,第二個引數'Student'是學生類 //通過容器實現依賴注入,完成類的例項化 //執行回撥函式 $student = $ioc->make('studentaaaa'); $student->go_to_school();
現在,我們不僅解除了學生類與交通工具類的依賴關係,而且容器類沒有和他們產生任何依賴。我們通過註冊、繫結(bind)的方式向容器中新增一段可以被執行的回撥(可以是匿名函式、非匿名函式、類的方法)作為建立一個類的例項的方法,只有在真正的建立(make)操作被呼叫執行時,才會觸發。這樣一種方式,使得我們更容易在建立一個例項的同時解決其依賴關係,並且更加靈活。當有新的需求,只需另外繫結一個回撥即可。例如現在我們想步行去學校,只要再繫結$ioc->bind('Go_To_School', 'Foot');就可以了。用何種方式去學校,我們可以自由的選擇。
通過上述例子可以看到IOC容器最核心的功能,解決依賴注入的根本問題。在實現過程中,沒有用new關鍵字來例項化物件,不需要人來關注元件之間的依賴關係,只要在容器填充過程中理順介面和實現類之間的關係及實現類與依賴介面之間的關係就可以流水線式的完成實現類的例項化過程。