如何理解laravel 的 IOC 容器

1.依賴

IOC( inversion of controller )叫作控制反轉模式,也能夠稱爲(dependency injection ) 依賴注入模式。要理解依賴注入的概念咱們先理解下什麼依賴php

 

 1 //支付寶支付
 2 class Alipay {
 3       public function __construct(){}
 4 
 5       public function pay()
 6       {
 7           echo 'pay bill by alipay';
 8       }
 9 }
10 //微信支付
11 class Wechatpay {
12       public function __construct(){}
13 
14       public function pay()
15       {
16           echo 'pay bill by wechatpay';
17       }
18 }
19 //銀聯支付
20 class Unionpay{
21       public function __construct(){}
22 
23       public function pay()
24       {
25           echo 'pay bill by unionpay';
26       }
27 }
28 
29 //支付帳單
30 class PayBill {
31 
32       private $payMethod33 
34       public function __construct( )
35       {
36           $this->payMethod= new Alipay ();
37       }
38 
39       public function  payMyBill()
40       {
41            $this->payMethod->pay();
42       }
43 }
44 
45 
46 $pb = new PayBill ();
47 $pb->payMyBill();

 

經過上面的代碼咱們知道,當咱們建立一個class PayBill 的實例的時候, PayBill的構造函數裏面有{ $this->payMethod= new Alipay (); }, 也就是實例化了一個class Alipay . 這個時候依賴就產生了, 這裏能夠理解爲當我想用支付寶支付的時候, 那我首先要獲取到一個支付寶的實例,或者理解爲獲取支付寶的功能支持. 當用咱們完 new 關鍵字的時候, 依賴其實已經解決了,由於咱們獲取了Alipay 的實例.laravel

其實在我知道ioc概念以前,個人代碼中大部分都是這種模式 ~ _ ~ . 這種有什麼問題呢, 簡單來講, 好比當我想用的不是支付寶而是微信的時候怎麼辦, 你能作的就是修改Payment 的構造函數的代碼,實例化一個微信支付Wechatpay.c#

若是咱們的程序不是很大的時候可能還感受不出什麼,可是當你的代碼很是複雜,龐大的時候,若是咱們的需求常常改變,那麼修改代碼就變的很是麻煩了。因此ioc 的思想就是不要在 class Payment 裏面用new 的方式去實例化解決依賴, 並且轉爲由外部來負責,簡單一點就是內部沒有new 的這個步驟,經過依賴注入的方式一樣的能獲取到支付的實例.數組

 

2.依賴注入

依賴咱們知道了是什麼意思,那依賴注入又是什麼意思呢,咱們把上面的代碼拓展一下微信

 1 //支付類接口
 2 interface Pay
 3 {
 4     public function pay();
 5 }
 6 
 7 
 8 //支付寶支付
 9 class Alipay implements Pay {
10       public function __construct(){}
11 
12       public function pay()
13       {
14           echo 'pay bill by alipay';
15       }
16 }
17 //微信支付
18 class Wechatpay implements Pay  {
19       public function __construct(){}
20 
21       public function pay()
22       {
23           echo 'pay bill by wechatpay';
24       }
25 }
26 //銀聯支付
27 class Unionpay implements Pay  {
28       public function __construct(){}
29 
30       public function pay()
31       {
32           echo 'pay bill by unionpay';
33       }
34 }
35 
36 //付款
37 class PayBill {
38 
39       private $payMethod;
40 
41       public function __construct( Pay $payMethod)
42       {
43           $this->payMethod= $payMethod;
44       }
45 
46       public function  payMyBill()
47       {
48            $this->payMethod->pay();
49       }
50 }
51 
52 //生成依賴
53 $payMethod =  new Alipay();
54 //注入依賴
55 $pb = new PayBill( $payMethod );
56 $pb->payMyBill();

上面的代碼中,跟以前的比較的話,咱們加入一個Pay 接口, 而後全部的支付方式都繼承了這個接口而且實現了pay 這個功能. 可能你們會問爲何要用接口,這個咱們稍後會講到.閉包

當咱們實例化PayBill的以前, 咱們首先是實例化了一個Alipay,這個步驟就是生成了依賴了,而後咱們須要把這個依賴注入到PayBill 的實例當中,經過代碼咱們能夠看到 { $pb = new PayBill( payMethod ); }, 咱們是經過了構造函數把這個依賴注入了PayBill 裏面. 這樣一來 $pb 這個PayBill 的實例就有了支付寶支付的能力了.app

把class Alipay 的實例經過constructor注入的方式去實例化一個 class PayBill. 在這裏咱們的注入是手動注入, 不是自動的. 而Laravel 框架實現則是自動注入.框架

3.反射

在介紹IOC 的容器以前咱們先來理解下反射的概念(reflection),由於IOC 容器也是要經過反射來實現的.從網上抄了一段來解釋反射是什麼意思函數

"反射它指在PHP運行狀態中,擴展分析PHP程序,導出或提取出關於類、方法、屬性、參數等的詳細信息,包括註釋。這種動態獲取的信息以及動態調用對象的方法的功能稱爲反射API。反射是操縱面向對象範型中元模型的API,其功能十分強大,可幫助咱們構建複雜,可擴展的應用。其用途如:自動加載插件,自動生成文檔,甚至可用來擴充PHP語言"微信支付

舉個簡單的例子

 1 class B{
 2 
 3 }
 4 
 5 
 6 class A {
 7 
 8     public function __construct(B $args)
 9     {
10     }
11 
12     public function dosomething()
13     {
14         echo 'Hello world';
15     }
16 }
17 
18 //創建class A 的反射
19 $reflection = new ReflectionClass('A');
20 
21 $b = new B();
22 
23 //獲取class A 的實例
24 $instance = $reflection ->newInstanceArgs( [ $b ]);
25 
26 $instance->dosomething(); //輸出 ‘Hellow World’
27 
28 $constructor = $reflection->getConstructor();//獲取class A 的構造函數
29 
30 $dependencies = $constructor->getParameters();//獲取class A 的依賴類
31 
32 dump($constructor);
33 
34 dump($dependencies);
35 dump 的獲得的$constructor$dependencies 結果以下
36 //constructor
37 ReflectionMethod {#351 
38         +name: "__construct" 
39         +class: "A" 
40         parameters: array:1 [] 
41         extra: array:3 [] 
42         modifiers: "public"
43 }
44 
45 //$dependencies
46 array:1 [
47         0 => ReflectionParameter {#352 
48          +name: "args"
49           position: 0
50           typeHint: "B"
51       }
52 ]

經過上面的代碼咱們能夠獲取到 class A 的構造函數,還有構造函數依賴的類,這個地方咱們依賴一個名字爲 'args' 的量,並且經過TypeHint能夠知道他是類型爲 Class B; 反射機制可讓我去解析一個類,能過獲取一個類裏面的屬性,方法 ,構造函數, 構造函數須要的參數。 有個了這個才能實現Laravel 的IOC 容器.

4.IOC容器

接下來介紹一下Laravel 的IOC服務容器概念. 在laravel框架中, 服務容器是整個laravel的核心,它提供了整個系統功能及服務的配置, 調用. 容器按照字面上的理解就是裝東西的東西,好比冰箱, 當咱們須要冰箱裏面的東西的時候直接從裏面拿就好了. 服務容器也能夠這樣理解, 當程序開始運行的時候,咱們把咱們須要的一些服務放到或者註冊到(bind)到容器裏面,當我須要的時候直接取出來(make)就好了. 上面提到的 bind 和 make 就是註冊 和 取出的 兩個動做.

5. IOC 容器代碼

好了,說了這麼多,下面要上一段容器的代碼了. 下面這段代碼不是laravel 的源碼, 而是來自一本書《laravel 框架關鍵技術解析》. 這段代碼很好的還原了laravel 的服務容器的核心思想. 代碼有點長, 小夥伴們要耐心看. 固然小夥伴徹底能夠試着運行一下這段代碼,而後調試一下,這樣會更有助於理解.

 1 <?php 
 2 
 3 //容器類裝實例或提供實例的回調函數
 4 class Container {
 5 
 6     //用於裝提供實例的回調函數,真正的容器還會裝實例等其餘內容
 7     //從而實現單例等高級功能
 8     protected $bindings = [];
 9 
10     //綁定接口和生成相應實例的回調函數
11     public function bind($abstract, $concrete=null, $shared=false) {
12         
13         //若是提供的參數不是回調函數,則產生默認的回調函數
14         if(!$concrete instanceof Closure) {
15             $concrete = $this->getClosure($abstract, $concrete);
16         }
17         
18         $this->bindings[$abstract] = compact('concrete', 'shared');
19     }
20 
21     //默認生成實例的回調函數
22     protected function getClosure($abstract, $concrete) {
23         
24         return function($c) use ($abstract, $concrete) {
25             $method = ($abstract == $concrete) ? 'build' : 'make';
26             return $c->$method($concrete);
27         };
28         
29     }
30 
31     public function make($abstract) {
32         $concrete = $this->getConcrete($abstract);
33 
34         if($this->isBuildable($concrete, $abstract)) {
35             $object = $this->build($concrete);
36         } else {
37             $object = $this->make($concrete);
38         }
39         
40         return $object;
41     }
42 
43     protected function isBuildable($concrete, $abstract) {
44         return $concrete === $abstract || $concrete instanceof Closure;
45     }
46 
47     //獲取綁定的回調函數
48     protected function getConcrete($abstract) {
49         if(!isset($this->bindings[$abstract])) {
50             return $abstract;
51         }
52 
53         return $this->bindings[$abstract]['concrete'];
54     }
55 
56     //實例化對象
57     public function build($concrete) {
58 
59         if($concrete instanceof Closure) {
60             return $concrete($this);
61         }
62 
63         $reflector = new ReflectionClass($concrete);
64         if(!$reflector->isInstantiable()) {
65             echo $message = "Target [$concrete] is not instantiable";
66         }
67 
68         $constructor = $reflector->getConstructor();
69         if(is_null($constructor)) {
70             return new $concrete;
71         }
72 
73         $dependencies = $constructor->getParameters();
74         $instances = $this->getDependencies($dependencies);
75 
76         return $reflector->newInstanceArgs($instances);
77     }
78 
79     //解決經過反射機制實例化對象時的依賴
80     protected function getDependencies($parameters) {
81         $dependencies = [];
82         foreach($parameters as $parameter) {
83             $dependency = $parameter->getClass();
84             if(is_null($dependency)) {
85                 $dependencies[] = NULL;
86             } else {
87                 $dependencies[] = $this->resolveClass($parameter);
88             }
89         }
90 
91         return (array)$dependencies;
92     }
93 
94     protected function resolveClass(ReflectionParameter $parameter) {
95         return $this->make($parameter->getClass()->name);
96     }
97 
98 }

上面的代碼就生成了一個容器,下面是如何使用容器

1 $app = new Container();
2 $app->bind("Pay", "Alipay");//Pay 爲接口, Alipay 是 class Alipay
3 $app->bind("tryToPayMyBill", "PayBill"); //tryToPayMyBill能夠當作是Class PayBill 的服務別名
4 
5 //經過字符解析,或獲得了Class PayBill 的實例
6 $paybill = $app->make("tryToPayMyBill"); 
7 
8 //由於以前已經把Pay 接口綁定爲了 Alipay,因此調用pay 方法的話會顯示 'pay bill by alipay '
9 $paybill->payMyBill(); 

當咱們實例化一個Container獲得 $app 後, 咱們就能夠向其中填充東西了

$app->bind("Pay", "Alipay");
$app->bind("tryToPayMyBill", "PayBill");  

當執行完這兩行綁定碼後, $app 裏面的屬性 $bindings 就已經有了array 值,是啥樣的呢,咱們來看下

 1 array:2 [
 2  "App\Http\Controllers\Pay" => array:2 [
 3      "concrete" => Closure {#355 
 4        class: "App\Http\Controllers\Container" 
 5        this:Container{[#354](http://127.0.0.4/ioc#sf-dump-254248394-ref2354) …} 
 6        parameters: array:1 [
 7          "$c" => []
 8        ] 
 9        use: array:2 [
10          "$abstract" => "App\Http\Controllers\Pay"
11         "$concrete" => "App\Http\Controllers\Alipay"
12        ] 
13        file: "C:\project\test\app\Http\Controllers\IOCController.php" line:       "119 to 122"
14    } 
15    "shared" => false 
16  ]
17 
18 "tryToPayMyBill" => array:2 [
19      "concrete" => Closure {#359 
20          class: "App\Http\Controllers\Container" 
21          this:Container{[#354](http://127.0.0.4/ioc#sf-dump-254248394-ref2354) …} 
22          parameters: array:1 [
23                "$c" => []
24          ] 
25          use: array:2 [
26                "$abstract" => "tryToPayMyBill" 
27                "$concrete" => "\App\Http\Controllers\PayBill"
28          ] 
29          file: "C:\project\test\app\Http\Controllers\IOCController.php" line: "119 to 122"
30    } 
31      "shared" => false 
32  ]
33 ]

當執行 $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 存在依賴

 1 //$constructor = $reflector->getConstructor();
 2 ReflectionMethod {#374 
 3     +name: "__construct" 
 4     +class: "App\Http\Controllers\PayBill" 
 5     parameters: array:1 [
 6           "$payMethod" => ReflectionParameter {#371 
 7               +name: "payMethod" 
 8               position: 0 typeHint: "App\Http\Controllers\Pay"
 9           }
10     ]
11      extra: array:3 [
12           "file" => "C:\project\test\app\Http\Controllers\IOCController.php"
13           "line" => "83 to 86" 
14           "isUserDefined" => true 
15       ] 
16     modifiers: "public"
17 }
18 
19 
20 //$dependencies = $constructor->getParameters();
21 array:1 [
22     0 => ReflectionParameter {#370 
23         +name: "payMethod" 
24         position: 0 
25         typeHint: "App\Http\Controllers\Pay"
26         }
27 ]

接着,咱們知道了有'Pay'這個依賴以後呢,咱們要作的就是解決這個依賴,經過 getDependencies($parameters), 和 resolveClass(ReflectionParameter $parameter) ,還有以前的綁定$app->bind("Pay", "Alipay"); 在build 一次的時候,經過 return new $concrete;到這裏咱們獲得了這個Alipay 的實例

1 if(is_null($constructor)) {
2             return new $concrete;
3         }

到這裏咱們總算結局了這個依賴, 這個依賴的結果就是實例化了一個 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 這個方法,咱們就可以經過綁定正常的使用. 這樣咱們的程序就很是容易被拓展,由於之後可能會出現成百上千種的支付方式.

相關文章
相關標籤/搜索