Laravel IOC容器深度剖析

1兼都、基礎(chǔ)知識預(yù)備

1.1 PHP 反射詳解

什么是反射定续?直觀理解就是根據(jù)到達地找到出發(fā)地和來源捂人。比如蓝谨,一個光禿禿的對象,我們可以僅僅通過這個對象就能知道它所屬的類茂卦,擁有哪些方法何什。

PHP的反射是指在PHP運行狀態(tài)過程中,擴展分析PHP程序等龙,導(dǎo)出或提出關(guān)于類处渣、方法伶贰、屬性、參數(shù)等詳細信息罐栈,包括注釋黍衙。這種動態(tài)獲取信息以及動態(tài)調(diào)用對象方法的功能成為反射API

下面是一個PHP反射的一個簡單demo。
詳細可參考PHP docuement http://php.net/manual/zh/book.reflection.php

<?php
class Person{
    public $name;
    public $gender;
    private $age;

    public function __construct($name,$gender){
        $this->name = $name;
        $this->gender = $gender;
    }

    public function __set($key, $value){
        $this->$key = $value;
    }

    public function __get($key){
        if(!isset($this->$key)){
            return "$key not exists";
        }
        return $this->$key;
    }
}
//用ReflectionClass得到A的反射類對象荠诬,通過反射類對象可以得到類的各種屬性琅翻,
//包括類名空間,父類柑贞,類名等方椎,使用newInstanceArgs可以傳入構(gòu)造函數(shù)的參數(shù)創(chuàng)建一個新的類的實例
$reflect = new ReflectionClass("Person");
//注入?yún)?shù)
$student = $reflect->newInstanceArgs(["xiaoming", "male"]);
echo $student->name;
echo "\n";
echo $student->gender;
echo "\n";
echo $student->age;
echo "\n";
$student->age = 20;
echo $student->age;
echo "\n";

output:

xiaoming
male
age not exists
20

1.2 依賴

程序的執(zhí)行過程就是方法的調(diào)用過程,有方法調(diào)用,就必然會促使對象和對象之間產(chǎn)生依賴钧嘶,除非一個對象不參與程序的運行棠众,這樣的對象就像一座孤島,與其他對象沒有任何交互有决,但是這樣的對象也就沒有任何存在的價值了闸拿。因此,在我們的程序代碼中疮薇,任何一個對象必然會與其他一個甚至更多個對象產(chǎn)生依賴關(guān)系胸墙。

依賴產(chǎn)生的原因主要有:

  • 方法調(diào)用
  • 繼承(子類依賴于父類)
  • 傳遞參數(shù)(一個類作為參數(shù)傳遞給另外一個類的成員方法時)
  • 臨時變量引入

下面有兩段代碼我注。
第一段是直接在代碼中引入了Train類按咒,這時候就形成了依賴。但是假如旅客不想坐火車但骨,想坐飛機励七,只能修改StupidTraveller中的代碼。
第二段代碼使用依賴注入的方式解除了上面StupidTraveller對Train的依賴奔缠。代碼的可擴展性變強了很多掠抬,如果還有其他的交通工具比如Car,只需要將Car注入Traveller類中即可。此處的依賴注入是手動的校哎,Laravel的IOC容器是自動注入的两波。

<?php
class Train{
    public function go(){
        echo "move by train \n";
    }
}

class Airplane{
    public function go(){
        echo "move by airplane \n";
    }
}

class StupidTraveller{
    private $trafficTool;
    public function __construct(){
        $this->trafficTool = new Train();
    }

    public function travel(){
        $this->trafficTool->go();
    }
}
$mike = new StupidTraveller();
$mike->travel();

<?php
interface TrafficTool{
    public function go();
}

class Train implements TrafficTool{
    public function go(){
        echo "move by train \n";
    }
}

class Airplane implements TrafficTool{
    public function go(){
        echo "move by airplane \n";
    }
}

class Traveller{
    private $trafficTool;
    public function __construct(TrafficTool $tool){
        $this->trafficTool = $tool;
    }

    public function travel(){
        $this->trafficTool->go();
    }
}
//在這里進行了依賴注入
$mike = new Traveller(new Train());
$mike->travel();

2、IOC容器

2.1 IOC容器是什么闷哆?

在理解IOC之前腰奋,首先從1.2的例子中理解下什么是DI(Dependency Injection)。Traveller類在初始化的時候注入了TrafficTool類抱怔,這就是一個典型的依賴注入的例子劣坊。依賴注入有兩個好處,一個是解耦屈留,將依賴之間解耦局冰,顯然Traveller類要比StupidTraveller要優(yōu)雅很多测蘑;第二個是由于已經(jīng)解耦了,方便做單元測試康二,尤其是Mock測試碳胳。如果TrafficTool是一個數(shù)據(jù)庫的操作類(打個比方啊)沫勿,這個時候你做單元測試的時候可以直接Mock一個虛擬的數(shù)據(jù)庫類注入到Traveller中去來做單元測試固逗。

IOC(Inversion of Control) 控制反轉(zhuǎn)是一種面對對象編程的設(shè)計原則,用于降低代碼之間的耦合度藕帜。其基本思想是借助第三方實現(xiàn)具有依賴關(guān)系的對象之間的解耦烫罩。


混亂的依賴關(guān)系
IOC容器

軟件系統(tǒng)在沒有IOC之前,對象之間相互依賴洽故,那么對象A在初始化或者運行到某一個點的時候贝攒,自己必須主動去創(chuàng)建一個對象B,或者使用之間已經(jīng)創(chuàng)建好的對象B。無論是創(chuàng)建還是使用對象B,控制權(quán)都在自己手上时甚。

但是在有了IOC容器后隘弊,這種情況改變了,由于IOC的加入荒适,對象之間的直接聯(lián)系消失了梨熙。在對象A運行時需要對象B的時候,IOC會主動創(chuàng)建一個對象B注入到對象A需要的地方刀诬。這也是Laravel中實現(xiàn)的IOC容器的神奇之處咽扇,自動注入依賴。

2.2 一段比較經(jīng)典的IOC容器的代碼

下面這段代碼源于《laravel框架關(guān)鍵技術(shù)解析》

      <?php
// 容器類陕壹,將接口與實現(xiàn)綁定
class Container
{
    // 保存與接口綁定的閉包质欲,
    // 閉包必須能夠返回接口的實例。
    protected $bindings = [];

    // 為某個接口綁定一個實現(xiàn)糠馆,有兩種情況:
    //
    // 第一種是綁定接口的實現(xiàn)的類名嘶伟;
    // 第二種是綁定一個閉包,這個閉包應(yīng)該返回接口的實例又碌。
    // 不管哪種情況九昧,實例化操作都是調(diào)用 build() 方法完成。
    // 第二種情況毕匀,主要是為了能夠在實例化前后進行額外的操作铸鹰,
    // 實例化的邏輯就書寫在閉包中。
    public function bind($abstract, $concrete = null)
    {
        // 如果提供的參數(shù)不是閉包期揪,而是一個類掉奄,
        // 則構(gòu)建一個閉包,直接調(diào)用 build() 方法進行實例化
        if (! $concrete instanceof Closure) {
            // 調(diào)用閉包時,傳入的參數(shù)是容器本身姓建,即 $this
            $concrete = function ($c) use ($concrete) {
                return $c->build($concrete);
            };
        }
        $this->bindings[$abstract] = $concrete;
    }

    // 生成指定接口的實例
    public function make($abstract)
    {
        // 取出閉包
        $concrete = $this->bindings[$abstract];
        // 運行閉包诞仓,即可取得一個實例
        return $concrete($this);
    }

    public function build($class)
    {
        // 取得類的反射
        $reflector = new ReflectionClass($class);

        // 檢查類是否可實例化
        if (! $reflector->isInstantiable()) {
            // 如果不能,意味著接口不能正常工作速兔,報錯
            echo $message = "Target [$class] is not instantiable";
        }

        // 取得構(gòu)造函數(shù)的反射
        $constructor = $reflector->getConstructor();

        // 檢查是否有構(gòu)造函數(shù)
        if (is_null($constructor)) {
            // 如果沒有墅拭,就說明沒有依賴,直接實例化
            return new $class;
        }

        // 取得包含每個參數(shù)的反射的數(shù)組
        $parameters = $constructor->getParameters();
        // 返回一個真正的參數(shù)列表涣狗,那些被類型提示的參數(shù)已經(jīng)被注入相應(yīng)的實例
        $realParameters = $this->resolveDependencies($parameters);

        return $reflector->newInstanceArgs($realParameters);
    }

    protected function resolveDependencies($parameters)
    {
        $realParameters = [];
        foreach($parameters as $parameter) {
            // 如果一個參數(shù)被類型提示為類 foo谍婉,
            // 則這個方法將返回類 foo 的反射
            $dependency = $parameter->getClass();
            if(is_null($dependency)) {
                $realParameters[] = NULL;
            } else {
                $realParameters[] = $this->make($dependency->name);
            }
        }

        return (array)$realParameters;
    }
}

下面在用上面的IOC容器來實現(xiàn)先前的Traveller

<?php
require __DIR__ . '/Container.php';

interface  TrafficTool
{
    public function go();
}

class Train implements TrafficTool
{
    public function go()
    {
        // TODO: Implement go() method.
        echo "move by train";
    }
}

class Airplane implements TrafficTool
{
    public function go()
    {
        // TODO: Implement go() method.
        echo "move by airplane\n";
    }

}

class Destination
{

    public function mark()
    {
        echo "I am here";
    }

}

class Traveller
{
    protected $trafficTool;
    protected $destination;

    public function __construct(TrafficTool $tool, Destination $destination)
    {
        $this->trafficTool = $tool;
        $this->destination = $destination;
    }

    public function travel()
    {
        $this->trafficTool->go();
        $this->destination->mark();
    }
}


// 實例化容器
$app = new Container();
//綁定某一功能到ioc
//將Train綁定到TrafficTool,abstract 是TrafficTool, concrete是Train
$app->bind('TrafficTool', 'AirPlane');
$app->bind("Destination", "Destination");
$app->bind('travellerA', 'Traveller');
//實例化對象
$tra = $app->make('travellerA');
$tra->travel();

注意$tra = $app->make('travellerA');這里并沒有手動的注入依賴TrafficTool。這個依賴在container的resolveDependencies方法中獲取镀钓,最后在$reflector->newInstanceArgs($realParameters)這行代碼中間進行了注入穗熬。并且這一系列的注入都不是手動的,注入都是自動完成的丁溅。

2.3唤蔗、laravel中的IOC容器

在laravel初始化的地方index.php中有這樣一行代碼

/*
|--------------------------------------------------------------------------
| Turn On The Lights
|--------------------------------------------------------------------------
|
| We need to illuminate PHP development, so let us turn on the lights.
| This bootstraps the framework and gets it ready for use, then it
| will load up this application so that we can run it and send
| the responses back to the browser and delight our users.
|
*/

$app = require_once __DIR__.'/../bootstrap/app.php';

開燈了!?呱汀妓柜!這里的app就是IOC容器,里面已經(jīng)進行了一系列的綁定涯穷。具體的綁定如下棍掐。

<?php

$app = new Illuminate\Foundation\Application(
    realpath(__DIR__.'/../')
);


$app->singleton(
    Illuminate\Contracts\Http\Kernel::class,
    App\Http\Kernel::class
);

$app->singleton(
    Illuminate\Contracts\Console\Kernel::class,
    App\Console\Kernel::class
);

$app->singleton(
    Illuminate\Contracts\Debug\ExceptionHandler::class,
    App\Exceptions\Handler::class
);
return $app;

容器就是在vendor/laravel/framework/src/illuminate/Container/Container.php中實現(xiàn)的,具體的實現(xiàn)代碼要比上面的那個簡單版復(fù)雜很多拷况,但是思想是一樣的作煌。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蝠嘉,隨后出現(xiàn)的幾起案子最疆,更是在濱河造成了極大的恐慌杯巨,老刑警劉巖蚤告,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異服爷,居然都是意外死亡杜恰,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進店門仍源,熙熙樓的掌柜王于貴愁眉苦臉地迎上來心褐,“玉大人,你說我怎么就攤上這事笼踩《旱” “怎么了?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵嚎于,是天一觀的道長掘而。 經(jīng)常有香客問我挟冠,道長,這世上最難降的妖魔是什么袍睡? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任知染,我火速辦了婚禮,結(jié)果婚禮上斑胜,老公的妹妹穿的比我還像新娘控淡。我一直安慰自己,他們只是感情好止潘,可當(dāng)我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布掺炭。 她就那樣靜靜地躺著,像睡著了一般凭戴。 火紅的嫁衣襯著肌膚如雪竹伸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天簇宽,我揣著相機與錄音勋篓,去河邊找鬼。 笑死魏割,一個胖子當(dāng)著我的面吹牛譬嚣,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播钞它,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼拜银,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了遭垛?” 一聲冷哼從身側(cè)響起尼桶,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎锯仪,沒想到半個月后泵督,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡庶喜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年小腊,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片久窟。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡秩冈,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出斥扛,到底是詐尸還是另有隱情入问,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站芬失,受9級特大地震影響卷仑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜麸折,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一锡凝、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧垢啼,春花似錦窜锯、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至馁启,卻和暖如春驾孔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背惯疙。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工翠勉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人霉颠。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓对碌,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蒿偎。 傳聞我的和親對象是個殘疾皇子朽们,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,834評論 2 345

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

  • 土曾經(jīng)是地球上最廉價的資源。我們腳下曾經(jīng)到處都是土诉位。記得小時候我家的房子所有房間地都是土筑的骑脱。春天天干時,地上就起...
    湖邊人老劉閱讀 662評論 0 3
  • 每年的畢業(yè)季,也是人潮涌動的時候椿息,每個人似乎都開始了忙碌的步伐歹袁,有的人匆匆邁上了去北上廣的路,有的悄悄回到了家鄉(xiāng)所...
    況北痕閱讀 263評論 0 2
  • 在我的記憶里寝优, 井上村曾經(jīng)有三盤水碓。 咯嘣咯嘣的碾米聲枫耳, 曾經(jīng)是1300多號人的井上 特有的旋律乏矾, 令人陶醉。 ...
    李逢亭閱讀 587評論 0 1
  • 汪國真 總有些這樣的時候 正是為了愛 才悄悄躲開 躲開的是身影 躲不開的卻是那份 默默的情懷 月光下躑躅 睡夢里徘...
    小王子的狐貍先森閱讀 215評論 0 1