下面由Laravel框架教學專欄給大家詳解Laravel—IOC容器,希望對需要的朋友有幫助!
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 的這個步驟,透過依賴注入的方式同樣的能獲取到支付的實例.
依賴我們知道了是什麼意思,那依賴注入又是什麼意思呢,我們把上面的程式碼拓展一下
//支付类接口 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 框架實現則是自動注入.
在介紹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 容器.
接下來介紹一下Laravel 的IOC服務容器概念. 在laravel框架中,服務容器是整個laravel的核心,它提供了整個系統功能及服務的配置, 調用. 容器按照字面上的理解就是裝東西的東西,比如冰箱, 當我們需要冰箱裡面的東西的時候直接從裡面拿就行了. 服務容器也可以這樣理解, 當程式開始運作的時候,我們把我們需要的一些服務放到或註冊到(bind)到容器裡面,當我需要的時候直接取出來(make)就行了. 上面提到的bind和make 就是註冊和取出的兩個動作.
好了,说了这么多,下面要上一段容器的代码了. 下面这段代码不是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 [ "App\Http\Controllers\Pay" => array:2 [ "concrete" => Closure {#355 class: "App\Http\Controllers\Container" this:Container{[#354](http://127.0.0.4/ioc#sf-dump-254248394-ref2354) …} parameters: array:1 [ "$c" => [] ] use: array:2 [ "$abstract" => "App\Http\Controllers\Pay" "$concrete" => "App\Http\Controllers\Alipay" ] file: "C:\project\test\app\Http\Controllers\IOCController.php" line: "119 to 122" } "shared" => false ] "tryToPayMyBill" => array:2 [ "concrete" => Closure {#359 class: "App\Http\Controllers\Container" this:Container{[#354](http://127.0.0.4/ioc#sf-dump-254248394-ref2354) …} parameters: array:1 [ "$c" => [] ] use: array:2 [ "$abstract" => "tryToPayMyBill" "$concrete" => "\App\Http\Controllers\PayBill" ] file: "C:\project\test\app\Http\Controllers\IOCController.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: "App\Http\Controllers\PayBill" parameters: array:1 [ "$payMethod" => ReflectionParameter {#371 +name: "payMethod" position: 0 typeHint: "App\Http\Controllers\Pay" } ] extra: array:3 [ "file" => "C:\project\test\app\Http\Controllers\IOCController.php" "line" => "83 to 86" "isUserDefined" => true ] modifiers: "public" } //$dependencies = $constructor->getParameters(); array:1 [ 0 => ReflectionParameter {#370 +name: "payMethod" position: 0 typeHint: "App\Http\Controllers\Pay" } ]
接着,我们知道了有’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 框架关键技术解析》这本书,写的还是不错的.
以上是詳解Laravel—IOC容器的詳細內容。更多資訊請關注PHP中文網其他相關文章!