如何理解laravel 的 IOC 容器

學(xué)習(xí)laravel快小一年了熏兄,到現(xiàn)在才去研究laravel 的核心 '容器 IOC' 這些概念. 寫項目的時候有大概看看關(guān)于IOC 文章鸽疾, 但是沒有深入理解贰军,只是有個概念,趕著寫代碼强法, 雖然代碼也寫的很菜 · - ·

這幾天花了點時間研究了一下laravel 的核心 ‘服務(wù)容器’ 然后理解了一下關(guān)于IOC 的概念. 不敢說百分百掌握了,但是比之前是有一定加深. 所以決定把自己理解的分享一下, 把自己的第一次博文獻(xiàn)給laravel. 理解不到位的還請各位大牛多多指正.

1.依賴

IOC( inversion of controller )叫做控制反轉(zhuǎn)模式膏萧,也可以稱為(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';
      }
}
//銀聯(lián)支付
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();

通過上面的代碼我們知道,當(dāng)我們創(chuàng)建一個class PayBill 的實例的時候, PayBill的構(gòu)造函數(shù)里面有{ $this->payMethod= new Alipay (); }, 也就是實例化了一個class Alipay . 這個時候依賴就產(chǎn)生了, 這里可以理解為當(dāng)我想用支付寶支付的時候, 那我首先要獲取到一個支付寶的實例,或者理解為獲取支付寶的功能支持. 當(dāng)用我們完 new 關(guān)鍵字的時候, 依賴其實已經(jīng)解決了榛泛,因為我們獲取了Alipay 的實例.

其實在我知道ioc概念之前蝌蹂,我的代碼中大部分都是這種模式 ~ _ ~ . 這種有什么問題呢, 簡單來說曹锨, 比如當(dāng)我想用的不是支付寶而是微信的時候怎么辦孤个, 你能做的就是修改Payment 的構(gòu)造函數(shù)的代碼,實例化一個微信支付Wechatpay.

如果我們的程序不是很大的時候可能還感覺不出什么,但是當(dāng)你的代碼非常復(fù)雜艘希,龐大的時候硼身,如果我們的需求經(jīng)常改變硅急,那么修改代碼就變的非常麻煩了覆享。所以ioc 的思想就是不要在 class Payment 里面用new 的方式去實例化解決依賴, 而且轉(zhuǎn)為由外部來負(fù)責(zé)营袜,簡單一點就是內(nèi)部沒有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';
      }
}
//銀聯(lián)支付
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 接口跪另, 然后所有的支付方式都繼承了這個接口并且實現(xiàn)了pay 這個功能. 可能大家會問為什么要用接口拧抖,這個我們稍后會講到.

當(dāng)我們實例化PayBill的之前, 我們首先是實例化了一個Alipay免绿,這個步驟就是生成了依賴了唧席,然后我們需要把這個依賴注入到PayBill 的實例當(dāng)中,通過代碼我們可以看到 { $pb = new PayBill( payMethod ); }, 我們是通過了構(gòu)造函數(shù)把這個依賴注入了PayBill 里面. 這樣一來 $pb 這個PayBill 的實例就有了支付寶支付的能力了.

把class Alipay 的實例通過constructor注入的方式去實例化一個 class PayBill. 在這里我們的注入是手動注入, 不是自動的. 而Laravel 框架實現(xiàn)則是自動注入.

3.反射

在介紹IOC 的容器之前我們先來理解下反射的概念(reflection),因為IOC 容器也是要通過反射來實現(xiàn)的.從網(wǎng)上抄了一段來解釋反射是什么意思

"反射它指在PHP運行狀態(tài)中淌哟,擴(kuò)展分析PHP程序迹卢,導(dǎo)出或提取出關(guān)于類、方法徒仓、屬性腐碱、參數(shù)等的詳細(xì)信息,包括注釋掉弛。這種動態(tài)獲取的信息以及動態(tài)調(diào)用對象的方法的功能稱為反射API症见。反射是操縱面向?qū)ο蠓缎椭性P偷腁PI,其功能十分強(qiáng)大狰晚,可幫助我們構(gòu)建復(fù)雜筒饰,可擴(kuò)展的應(yīng)用。其用途如:自動加載插件壁晒,自動生成文檔瓷们,甚至可用來擴(kuò)充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 的構(gòu)造函數(shù)

$dependencies = $constructor->getParameters();//獲取class A 的依賴類

dump($constructor);

dump($dependencies);

dump 的得到的$constructor 和 $dependencies 結(jié)果如下

//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 的構(gòu)造函數(shù),還有構(gòu)造函數(shù)依賴的類秒咐,這個地方我們依賴一個名字為 'args' 的量谬晕,而且通過TypeHint可以知道他是類型為 Class B; 反射機(jī)制可以讓我去解析一個類,能過獲取一個類里面的屬性携取,方法 攒钳,構(gòu)造函數(shù), 構(gòu)造函數(shù)需要的參數(shù)雷滋。 有個了這個才能實現(xiàn)Laravel 的IOC 容器.

4.IOC容器

接下來介紹一下Laravel 的IOC服務(wù)容器概念. 在laravel框架中不撑, 服務(wù)容器是整個laravel的核心,它提供了整個系統(tǒng)功能及服務(wù)的配置, 調(diào)用. 容器按照字面上的理解就是裝東西的東西晤斩,比如冰箱焕檬, 當(dāng)我們需要冰箱里面的東西的時候直接從里面拿就行了. 服務(wù)容器也可以這樣理解, 當(dāng)程序開始運行的時候澳泵,我們把我們需要的一些服務(wù)放到或者注冊到(bind)到容器里面实愚,當(dāng)我需要的時候直接取出來(make)就行了. 上面提到的 bind 和 make 就是注冊 和 取出的 兩個動作.

5. IOC 容器代碼

好了,說了這么多兔辅,下面要上一段容器的代碼了. 下面這段代碼不是laravel 的源碼腊敲, 而是來自一本書《laravel 框架關(guān)鍵技術(shù)解析》. 這段代碼很好的還原了laravel 的服務(wù)容器的核心思想. 代碼有點長, 小伙伴們要耐心看. 當(dāng)然小伙伴完全可以試著運行一下這段代碼维苔,然后調(diào)試一下碰辅,這樣會更有助于理解.

<?php 

//容器類裝實例或提供實例的回調(diào)函數(shù)
class Container {

    //用于裝提供實例的回調(diào)函數(shù),真正的容器還會裝實例等其他內(nèi)容
    //從而實現(xiàn)單例等高級功能
    protected $bindings = [];

    //綁定接口和生成相應(yīng)實例的回調(diào)函數(shù)
    public function bind($abstract, $concrete=null, $shared=false) {
        
        //如果提供的參數(shù)不是回調(diào)函數(shù)介时,則產(chǎn)生默認(rèn)的回調(diào)函數(shù)
        if(!$concrete instanceof Closure) {
            $concrete = $this->getClosure($abstract, $concrete);
        }
        
        $this->bindings[$abstract] = compact('concrete', 'shared');
    }

    //默認(rèn)生成實例的回調(diào)函數(shù)
    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;
    }

    //獲取綁定的回調(diào)函數(shù)
    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);
    }

    //解決通過反射機(jī)制實例化對象時的依賴
    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可以當(dāng)做是Class PayBill 的服務(wù)別名

//通過字符解析忍法,或得到了Class PayBill 的實例
$paybill = $app->make("tryToPayMyBill"); 

//因為之前已經(jīng)把Pay 接口綁定為了 Alipay,所以調(diào)用pay 方法的話會顯示 'pay bill by alipay '
$paybill->payMyBill(); 

當(dāng)我們實例化一個Container得到 $app 后榕吼, 我們就可以向其中填充東西了

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

當(dāng)執(zhí)行完這兩行綁定碼后, $app 里面的屬性 $bindings 就已經(jīng)有了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 
 ]
]

當(dāng)執(zhí)行 $paybill = $app->make("tryToPayMyBill"); 的時候, 程序就會用make方法通過閉包函數(shù)的回調(diào)開始解析了.

解析'tryToPayBill' 這個字符串地技, 程序通過閉包函數(shù) 和build方法會得到 'PayBill' 這個字符串朗鸠,該字符串保存在$concrete 上. 這個是第一步. 然后程序還會以類似于遞歸方式 將$concrete 傳入 build() 方法. 這個時候build里面就獲取了$concrete = 'PayBill'. 這個時候反射就派上了用場, 大家有沒有發(fā)現(xiàn)顽素,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;
        }

到這里我們總算結(jié)局了這個依賴, 這個依賴的結(jié)果就是實例化了一個 Alipay. 到這里還沒結(jié)束

        $instances = $this->getDependencies($dependencies);
       
       

上面的$instances 數(shù)組只有一個element 那就是 Alipay 實例

  array:1 [0 =>Alipay
      {#380}
 ]

最終通過 newInstanceArgs() 方法抑淫, 我們獲取到了 PayBill 的實例绷落。

 return $reflector->newInstanceArgs($instances);

到這里整個流程就結(jié)束了, 我們通過 bind 方式綁定了一些依賴關(guān)系始苇, 然后通過make 方法 獲取到到我們想要的實例. 在make中有牽扯到了閉包函數(shù)砌烁,反射等概念.

好了,當(dāng)我們把容器的概念理解了之后催式,我們就可以理解下為什么要用接口這個問題了. 如果說我不想用支付寶支付函喉,我要用微信支付怎么辦,too easy.

$app->bind("Pay", "Wechatpay");
$app->bind("tryToPayMyBill", "PayBill");
$paybill = $app->make("tryToPayMyBill"); 
$paybill->payMyBill();

是不是很簡單呢荣月, 只要把綁定從'Alipay' 改成 'Wechatpay' 就行了管呵,其他的都不用改. 這就是為什么我們要用接口. 只要你的支付方式繼承了pay 這個接口,并且實現(xiàn)pay 這個方法喉童,我們就能夠通過綁定正常的使用. 這樣我們的程序就非常容易被拓展撇寞,因為以后可能會出現(xiàn)成百上千種的支付方式.

好了顿天,到這里不知道小伙伴有沒有理解呢堂氯,我建議大家可以試著運行下這些代碼, 這樣理解起來會更快.同時推薦大家去看看 《laravel 框架關(guān)鍵技術(shù)解析》這本書,寫的還是不錯的.


給自己打個廣告: http://www.gbhchina.com/jacky
轉(zhuǎn)載請注明:作者[哎喲我的巴扎黑]

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市牌废,隨后出現(xiàn)的幾起案子咽白,更是在濱河造成了極大的恐慌,老刑警劉巖鸟缕,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晶框,死亡現(xiàn)場離奇詭異排抬,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)授段,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進(jìn)店門蹲蒲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人侵贵,你說我怎么就攤上這事届搁。” “怎么了窍育?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵卡睦,是天一觀的道長。 經(jīng)常有香客問我漱抓,道長表锻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任乞娄,我火速辦了婚禮瞬逊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘仪或。我一直安慰自己码耐,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布溶其。 她就那樣靜靜地躺著骚腥,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瓶逃。 梳的紋絲不亂的頭發(fā)上束铭,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天,我揣著相機(jī)與錄音厢绝,去河邊找鬼契沫。 笑死,一個胖子當(dāng)著我的面吹牛昔汉,可吹牛的內(nèi)容都是我干的懈万。 我是一名探鬼主播,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼靶病,長吁一口氣:“原來是場噩夢啊……” “哼会通!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起娄周,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤涕侈,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后煤辨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體裳涛,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡木张,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了端三。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片舷礼。...
    茶點故事閱讀 38,646評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖郊闯,靈堂內(nèi)的尸體忽然破棺而出且轨,到底是詐尸還是另有隱情,我是刑警寧澤虚婿,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布旋奢,位于F島的核電站,受9級特大地震影響然痊,放射性物質(zhì)發(fā)生泄漏至朗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一剧浸、第九天 我趴在偏房一處隱蔽的房頂上張望锹引。 院中可真熱鬧,春花似錦唆香、人聲如沸嫌变。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽腾啥。三九已至,卻和暖如春冯吓,著一層夾襖步出監(jiān)牢的瞬間倘待,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工组贺, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留凸舵,地道東北人。 一個月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓失尖,卻偏偏與公主長得像啊奄,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子掀潮,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,514評論 2 348

推薦閱讀更多精彩內(nèi)容