下面由laravel框架教程欄目給大家詳解laravel—ioc容器,希望對需要的朋友有所幫助!
1.依賴
IOC( inversion of controller )叫做控制反轉模式,也可以稱為(dependency injection ) 依賴注入模式。要理解依賴注入的概念我們先理解下什么依賴
//支付寶支付 class?Alipay?{ ??????public?function?__construct(){} ??????public?function?pay() ??????{ ??????????echo?'pay?bill?by?alipay'; ??????} } //微信支付 class?Wechatpay?{ ??????public?function?__construct(){} ??????public?function?pay() ??????{ ??????????echo?'pay?bill?by?wechatpay'; ??????} } //銀聯支付 class?Unionpay{ ??????public?function?__construct(){} ??????public?function?pay() ??????{ ??????????echo?'pay?bill?by?unionpay'; ??????} } //支付賬單 class?PayBill?{ ??????private?$payMethod; ??????public?function?__construct(?) ??????{ ??????????$this->payMethod=?new?Alipay?(); ??????} ??????public?function??payMyBill() ??????{ ???????????$this->payMethod->pay(); ??????} } $pb?=?new?PayBill?(); $pb->payMyBill();
通過上面的代碼我們知道,當我們創建一個class PayBill 的實例的時候, PayBill的構造函數里面有{ $this->payMethod= new Alipay (); }, 也就是實例化了一個class Alipay . 這個時候依賴就產生了, 這里可以理解為當我想用支付寶支付的時候, 那我首先要獲取到一個支付寶的實例,或者理解為獲取支付寶的功能支持. 當用我們完 new 關鍵字的時候, 依賴其實已經解決了,因為我們獲取了Alipay 的實例.
其實在我知道ioc概念之前,我的代碼中大部分都是這種模式 ~ _ ~ . 這種有什么問題呢, 簡單來說, 比如當我想用的不是支付寶而是微信的時候怎么辦, 你能做的就是修改Payment 的構造函數的代碼,實例化一個微信支付Wechatpay.
如果我們的程序不是很大的時候可能還感覺不出什么,但是當你的代碼非常復雜,龐大的時候,如果我們的需求經常改變,那么修改代碼就變的非常麻煩了。所以ioc 的思想就是不要在 class Payment 里面用new 的方式去實例化解決依賴, 而且轉為由外部來負責,簡單一點就是內部沒有new 的這個步驟,通過依賴注入的方式同樣的能獲取到支付的實例.
2.依賴注入
依賴我們知道了是什么意思,那依賴注入又是什么意思呢,我們把上面的代碼拓展一下
//支付類接口 interface?Pay { ????public?function?pay(); } //支付寶支付 class?Alipay?implements?Pay?{ ??????public?function?__construct(){} ??????public?function?pay() ??????{ ??????????echo?'pay?bill?by?alipay'; ??????} } //微信支付 class?Wechatpay?implements?Pay??{ ??????public?function?__construct(){} ??????public?function?pay() ??????{ ??????????echo?'pay?bill?by?wechatpay'; ??????} } //銀聯支付 class?Unionpay?implements?Pay??{ ??????public?function?__construct(){} ??????public?function?pay() ??????{ ??????????echo?'pay?bill?by?unionpay'; ??????} } //付款 class?PayBill?{ ??????private?$payMethod; ??????public?function?__construct(?Pay?$payMethod) ??????{ ??????????$this->payMethod=?$payMethod; ??????} ??????public?function??payMyBill() ??????{ ???????????$this->payMethod->pay(); ??????} } //生成依賴 $payMethod?=??new?Alipay(); //注入依賴 $pb?=?new?PayBill(?$payMethod?); $pb->payMyBill();
上面的代碼中,跟之前的比較的話,我們加入一個Pay 接口, 然后所有的支付方式都繼承了這個接口并且實現了pay 這個功能. 可能大家會問為什么要用接口,這個我們稍后會講到.
當我們實例化PayBill的之前, 我們首先是實例化了一個Alipay,這個步驟就是生成了依賴了,然后我們需要把這個依賴注入到PayBill 的實例當中,通過代碼我們可以看到 { $pb = new PayBill( payMethod ); }, 我們是通過了構造函數把這個依賴注入了PayBill 里面. 這樣一來 $pb 這個PayBill 的實例就有了支付寶支付的能力了.
把class Alipay 的實例通過constructor注入的方式去實例化一個 class PayBill. 在這里我們的注入是手動注入, 不是自動的. 而Laravel 框架實現則是自動注入.
3.反射
在介紹IOC 的容器之前我們先來理解下反射的概念(Reflection),因為IOC 容器也是要通過反射來實現的.從網上抄了一段來解釋反射是什么意思
“反射它指在php運行狀態中,擴展分析PHP程序,導出或提取出關于類、方法、屬性、參數等的詳細信息,包括注釋。這種動態獲取的信息以及動態調用對象的方法的功能稱為反射API。反射是操縱面向對象范型中元模型的API,其功能十分強大,可幫助我們構建復雜,可擴展的應用。其用途如:自動加載插件,自動生成文檔,甚至可用來擴充PHP語言”
舉個簡單的例子
class?B{ } class?A?{ ????public?function?__construct(B?$args) ????{ ????} ????public?function?dosomething() ????{ ????????echo?'Hello?world'; ????} } //建立class?A?的反射 $reflection?=?new?ReflectionClass('A'); $b?=?new?B(); //獲取class?A?的實例 $instance?=?$reflection?->newInstanceArgs(?[?$b?]); $instance->dosomething();?//輸出?‘Hellow?World’ $constructor?=?$reflection->getConstructor();//獲取class?A?的構造函數 $dependencies?=?$constructor->getParameters();//獲取class?A?的依賴類 dump($constructor); dump($dependencies);
dump 的得到的$constructor 和 $dependencies 結果如下
//constructor ReflectionMethod?{#351? ????????+name:?"__construct"? ????????+class:?"A"? ????????parameters:?Array:1?[]? ????????extra:?array:3?[]? ????????modifiers:?"public" } //$dependencies array:1?[ ????????0?=>?ReflectionParameter?{#352? ?????????+name:?"args" ??????????position:?0 ??????????typeHint:?"B" ??????} ]
通過上面的代碼我們可以獲取到 class A 的構造函數,還有構造函數依賴的類,這個地方我們依賴一個名字為 ‘args’ 的量,而且通過TypeHint可以知道他是類型為 Class B; 反射機制可以讓我去解析一個類,能過獲取一個類里面的屬性,方法 ,構造函數, 構造函數需要的參數。 有個了這個才能實現Laravel 的IOC 容器.
4.IOC容器
接下來介紹一下Laravel 的IOC服務容器概念. 在laravel框架中, 服務容器是整個laravel的核心,它提供了整個系統功能及服務的配置, 調用. 容器按照字面上的理解就是裝東西的東西,比如冰箱, 當我們需要冰箱里面的東西的時候直接從里面拿就行了. 服務容器也可以這樣理解, 當程序開始運行的時候,我們把我們需要的一些服務放到或者注冊到(bind)到容器里面,當我需要的時候直接取出來(make)就行了. 上面提到的 bind 和 make 就是注冊 和 取出的 兩個動作.
5. IOC 容器代碼
好了,說了這么多,下面要上一段容器的代碼了. 下面這段代碼不是laravel 的源碼, 而是來自一本書《laravel 框架關鍵技術解析》. 這段代碼很好的還原了laravel 的服務容器的核心思想. 代碼有點長, 小伙伴們要耐心看. 當然小伙伴完全可以試著運行一下這段代碼,然后調試一下,這樣會更有助于理解.
<?php //容器類裝實例或提供實例的回調函數 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'); ????} ????//默認生成實例的回調函數 ????protected?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; ????} ????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)?{ ????????if($concrete?instanceof?Closure)?{ ????????????return?$concrete($this); ????????} ????????$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(); ????????$instances?=?$this->getDependencies($dependencies); ????????return?$reflector->newInstanceArgs($instances); ????} ????//解決通過反射機制實例化對象時的依賴 ????protected?function?getDependencies($parameters)?{ ????????$dependencies?=?[]; ????????foreach($parameters?as?$parameter)?{ ????????????$dependency?=?$parameter->getClass(); ????????????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); ????} }
上面的代碼就生成了一個容器,下面是如何使用容器
$app?=?new?Container(); $app->bind("Pay",?"Alipay");//Pay?為接口,?Alipay?是?class?Alipay $app->bind("tryToPayMyBill",?"PayBill");?//tryToPayMyBill可以當做是Class?PayBill?的服務別名 //通過字符解析,或得到了Class?PayBill?的實例 $paybill?=?$app->make("tryToPayMyBill");? //因為之前已經把Pay?接口綁定為了?Alipay,所以調用pay?方法的話會顯示?'pay?bill?by?alipay?' $paybill->payMyBill();
當我們實例化一個Container得到 $app 后, 我們就可以向其中填充東西了
$app->bind("Pay",?"Alipay"); $app->bind("tryToPayMyBill",?"PayBill");
當執行完這兩行綁定碼后, $app 里面的屬性 $bindings 就已經有了array 值,是啥樣的呢,我們來看下
array:2?[ ?"AppHttpControllersPay"?=>?array:2?[ ?????"concrete"?=>?Closure?{#355? ???????class:?"AppHttpControllersContainer"? ???????this:Container{[#354](http://127.0.0.4/ioc#sf-dump-254248394-ref2354)?…}? ???????parameters:?array:1?[ ?????????"$c"?=>?[] ???????]? ???????use:?array:2?[ ?????????"$abstract"?=>?"AppHttpControllersPay" ????????"$concrete"?=>?"AppHttpControllersAlipay" ???????]? ???????file:?"C:projecttestappHttpControllersIOCController.php"?line:???????"119?to?122" ???}? ???"shared"?=>?false? ?] "tryToPayMyBill"?=>?array:2?[ ?????"concrete"?=>?Closure?{#359? ?????????class:?"AppHttpControllersContainer"? ?????????this:Container{[#354](http://127.0.0.4/ioc#sf-dump-254248394-ref2354)?…}? ?????????parameters:?array:1?[ ???????????????"$c"?=>?[] ?????????]? ?????????use:?array:2?[ ???????????????"$abstract"?=>?"tryToPayMyBill"? ???????????????"$concrete"?=>?"AppHttpControllersPayBill" ?????????]? ?????????file:?"C:projecttestappHttpControllersIOCController.php"?line:?"119?to?122" ???}? ?????"shared"?=>?false? ?] ]
當執行 $paybill = $app->make(“tryToPayMyBill”); 的時候, 程序就會用make方法通過閉包函數的回調開始解析了.
解析’tryToPayBill’ 這個字符串, 程序通過閉包函數 和build方法會得到 ‘PayBill’ 這個字符串,該字符串保存在$concrete 上. 這個是第一步. 然后程序還會以類似于遞歸方式 將$concrete 傳入 build() 方法. 這個時候build里面就獲取了$concrete = ‘PayBill’. 這個時候反射就派上了用場, 大家有沒有發現,PayBill 不就是 class PayBill 嗎? 然后在通過反射的方法ReflectionClass(‘PayBill’) 獲取PayBill 的實例. 之后通過getConstructor(),和getParameters() 等方法知道了 Class PayBill 和 接口Pay 存在依賴
//$constructor?=?$reflector->getConstructor(); ReflectionMethod?{#374? ????+name:?"__construct"? ????+class:?"AppHttpControllersPayBill"? ????parameters:?array:1?[ ??????????"$payMethod"?=>?ReflectionParameter?{#371? ??????????????+name:?"payMethod"? ??????????????position:?0?typeHint:?"AppHttpControllersPay" ??????????} ????] ?????extra:?array:3?[ ??????????"file"?=>?"C:projecttestappHttpControllersIOCController.php" ??????????"line"?=>?"83?to?86"? ??????????"isUserDefined"?=>?true? ??????]? ????modifiers:?"public" } //$dependencies?=?$constructor->getParameters(); array:1?[ ????0?=>?ReflectionParameter?{#370? ????????+name:?"payMethod"? ????????position:?0? ????????typeHint:?"AppHttpControllersPay" ????????} ]
接著,我們知道了有’Pay’這個依賴之后呢,我們要做的就是解決這個依賴,通過 getDependencies($parameters), 和 resolveClass(ReflectionParameter $parameter) ,還有之前的綁定$app->bind(“Pay”, “Alipay”); 在build 一次的時候,通過 return new $concrete;到這里我們得到了這個Alipay 的實例
????????if(is_null($constructor))?{ ????????????return?new?$concrete; ????????}
到這里我們總算結局了這個依賴, 這個依賴的結果就是實例化了一個 Alipay. 到這里還沒結束
????????$instances?=?$this->getDependencies($dependencies);
上面的$instances 數組只有一個element 那就是 Alipay 實例
??array:1?[0?=>Alipay ??????{#380} ?]
最終通過 newInstanceArgs() 方法, 我們獲取到了 PayBill 的實例。
?return?$reflector->newInstanceArgs($instances);
到這里整個流程就結束了, 我們通過 bind 方式綁定了一些依賴關系, 然后通過make 方法 獲取到到我們想要的實例. 在make中有牽扯到了閉包函數,反射等概念.
好了,當我們把容器的概念理解了之后,我們就可以理解下為什么要用接口這個問題了. 如果說我不想用支付寶支付,我要用微信支付怎么辦,too easy.
$app->bind("Pay",?"Wechatpay"); $app->bind("tryToPayMyBill",?"PayBill"); $paybill?=?$app->make("tryToPayMyBill");? $paybill->payMyBill();
是不是很簡單呢, 只要把綁定從’Alipay’ 改成 ‘Wechatpay’ 就行了,其他的都不用改. 這就是為什么我們要用接口. 只要你的支付方式繼承了pay 這個接口,并且實現pay 這個方法,我們就能夠通過綁定正常的使用. 這樣我們的程序就非常容易被拓展,因為以后可能會出現成百上千種的支付方式.
好了,到這里不知道小伙伴有沒有理解呢,我建議大家可以試著運行下這些代碼, 這樣理解起來會更快.同時推薦大家去看看 《laravel 框架關鍵技術解析》這本書,寫的還是不錯的.