Laravel 服務(wù)容器實(shí)現(xiàn)原理

前言

通過實(shí)現(xiàn)laravel 框架功能宙攻,以便深入理解laravel框架的先進(jìn)思想助被。

什么是服務(wù)容器

服務(wù)容器是用來管理類依賴與運(yùn)行依賴注入的工具。Laravel框架中就是使用服務(wù)容器來實(shí)現(xiàn) ** 控制反轉(zhuǎn) ** 和 ** 依賴注入 **竟稳。

什么是控制反轉(zhuǎn)(IoC)和依賴注入(DI)

控制反轉(zhuǎn)(IoC) 就是說把創(chuàng)建對(duì)象的** 控制權(quán) **進(jìn)行轉(zhuǎn)移健蕊,以前創(chuàng)建對(duì)象的主動(dòng)權(quán)和創(chuàng)建時(shí)機(jī)是由自己把控的,而現(xiàn)在這種權(quán)力轉(zhuǎn)移到第三方罩缴,也就是 ** Laravel ** 中的容器蚊逢。

依賴注入(DI)則是幫助容器實(shí)現(xiàn)在運(yùn)行中動(dòng)態(tài)的為對(duì)象提供提依賴的資源层扶。

概念容易不太容易讓人理解,舉個(gè)栗子:

//我們構(gòu)建一個(gè)人的類和一個(gè)狗的類
 class People
{
    public $dog = null;

    public function __construct()
    {
        $this->dog = new Dog();
    }

    public function putDog(){
        return $this->dog->dogCall();
    }

}

class Dog{
    public function dogCall(){
        return '汪汪汪';
    }
}

這個(gè)人在遛狗烙荷,突然遇到了死對(duì)頭镜会,他于是放狗咬人

$people = new People();
$people->putDog();

在這個(gè)操作中,people類要執(zhí)行putDog()這個(gè)方法终抽,需要依賴Dog類戳表,一般我們像上面一樣,在people中利用構(gòu)造函數(shù)來添加這個(gè)Dog依賴昼伴。如果使用控制反轉(zhuǎn) 依賴注入則是這個(gè)樣子

class People
{
    public $dog = null;

    public function __construct(Dog $dog)
    {
        $this->dog = $dog;
    }

    public function putDog(){
        return $this->dog->dogCall();
    }

}

People類通過構(gòu)造參數(shù)聲明自己需要的 依賴類匾旭,由容器自動(dòng)注入。這樣就實(shí)現(xiàn)了程序的有效解耦圃郊,好處在這就不多說了价涝。

Laravel容器依賴注入的實(shí)現(xiàn)

實(shí)現(xiàn)原理需要了解的知識(shí)點(diǎn):

閉包(匿名函數(shù)):
匿名函數(shù)(Anonymous functions),也叫閉包函數(shù)(closures)持舆,允許 臨時(shí)創(chuàng)建一個(gè)沒有指定名稱的函數(shù)

反射:PHP 5 以上版本具有完整的反射 API色瘩,添加了對(duì)類、接口吏廉、函數(shù)泞遗、方法和擴(kuò)展進(jìn)行反向工程的能力。 此外席覆,反射 API 提供了方法來取出函數(shù)史辙、類和方法中的文檔注釋

理解了閉包和反射的基本用法我們來看Laravel中是怎么實(shí)現(xiàn)容器的,下面代碼是我對(duì)laravel框架容器部分代碼的簡(jiǎn)化核心版:
class Containerc
{
    /**
     *  容器綁定佩伤,用來裝提供的實(shí)例或者 提供實(shí)例的回調(diào)函數(shù)
     * @var array
     */
    public $building = [];

    /**
     * 注冊(cè)一個(gè)綁定到容器
     */
    public function bind($abstract, $concrete = null, $shared = false)
    {
        if(is_null($concrete)){
            $concrete = $abstract;
        }

        if(!$concrete instanceOf Closure){
            $concrete = $this->getClosure($abstract, $concrete);
        }

        $this->building[$abstract] =  compact("concrete", "shared");
    }

    //注冊(cè)一個(gè)共享的綁定 單例
    public function singleton($abstract, $concrete, $shared = true){
        $this->bind($abstract, $concrete, $shared);
    }

    /**
     * 默認(rèn)生成實(shí)例的回調(diào)閉包
     *
     * @param $abstract
     * @param $concrete
     * @return Closure
     */
    public function getClosure($abstract, $concrete)
    {
        return function($c) use($abstract, $concrete){
            $method = ($abstract == $concrete)? 'build' : 'make';

            return $c->$method($concrete);
        };
    }

    /**
     * 生成實(shí)例 
     */
    public function make($abstract)
    {
        $concrete = $this->getConcrete($abstract);

        if($this->isBuildable($concrete, $abstract)){
            $object = $this->build($concrete);
        }else{
            $object = $this->make($concrete);
        }

        return $object;
    }

    /**
     * 獲取綁定的回調(diào)函數(shù)
     */
    public function getConcrete($abstract)
    {
        if(! isset($this->building[$abstract])){
            return $abstract;
        }

        return $this->building[$abstract]['concrete'];
    }

    /**
     * 判斷 是否 可以創(chuàng)建服務(wù)實(shí)體
     */
    public function isBuildable($concrete, $abstract)
    {
        return $concrete === $abstract || $concrete instanceof Closure;
    }

    /**
     * 根據(jù)實(shí)例具體名稱實(shí)例具體對(duì)象
     */
    public function build($concrete)
    {
        if($concrete instanceof Closure){
            return $concrete($this);
        }

        //創(chuàng)建反射對(duì)象
        $reflector = new ReflectionClass($concrete);

        if( ! $reflector->isInstantiable()){
            //拋出異常
            throw new \Exception('無法實(shí)例化');
        }

        $constructor = $reflector->getConstructor();
        if(is_null($constructor)){
            return new $concrete;
        }

        $dependencies = $constructor->getParameters();
        $instance = $this->getDependencies($dependencies);

        return $reflector->newInstanceArgs($instance);

    }

    //通過反射解決參數(shù)依賴
    public function getDependencies(array $dependencies)
    {
        $results = [];
        foreach( $dependencies as $dependency ){
            $results[] = is_null($dependency->getClass())
                ?$this->resolvedNonClass($dependency)
                :$this->resolvedClass($dependency);
        }

        return $results;
    }

    //解決一個(gè)沒有類型提示依賴
    public function resolvedNonClass(ReflectionParameter $parameter)
    {
        if($parameter->isDefaultValueAvailable()){
            return $parameter->getDefaultValue();
        }
        throw new \Exception('出錯(cuò)');

    }

    //通過容器解決依賴
    public function resolvedClass(ReflectionParameter $parameter)
    {
        return $this->make($parameter->getClass()->name);

    }

}

容器的工作流程

接著上面遛狗的例子:

//實(shí)例化容器類
$app =  new Container();
//向容器中填充Dog
$app->bind('Dog','App\Dog');
//填充People
$app->bind('People', 'App\People');
//通過容器實(shí)現(xiàn)依賴注入聊倔,完成類的實(shí)例化;
$people = $app->make('People');
//調(diào)用方法
echo $people->putDog();

上面示例中我們先實(shí)例化容器類生巡,然后使用bind()方法 綁定接口和 生成相應(yīng)的實(shí)例的閉包函數(shù)耙蔑。然后使用make() 函數(shù)生成實(shí)例對(duì)象,在make()中會(huì)調(diào)用 isBuildable($concrete, $abstract) 來判斷 給定的服務(wù)實(shí)體($concrete參數(shù))是否可以創(chuàng)建孤荣,可以創(chuàng)建 就會(huì)調(diào)用 build($concrete) 函數(shù) 甸陌,build($concrete) 函數(shù)會(huì)判斷傳的參數(shù)是 是** 閉包 還是 具體類名 **,如果是閉包則直接運(yùn)行盐股,如果是具體類名的話钱豁,則通過反射獲取該類的構(gòu)造函數(shù)所需的依賴,完成實(shí)例化疯汁。

** 重點(diǎn)理解 下面這幾個(gè)函數(shù)中 反射的用法牲尺,應(yīng)該就很好理解了 **

build($concrete)
getDependencies(array $dependencies)
resolvedNonClass(ReflectionParameter $parameter)
resolvedClass(ReflectionParameter $parameter) ``` 

## 最后 ##
> IoC 理解起來是有點(diǎn)難度,可能文中描述讓你感覺不是很清楚幌蚊,可以將文中代碼 在php中用debug觀察 運(yùn)行狀態(tài)谤碳。
理解了容器的具體實(shí)現(xiàn)原理溃卡,再去看Laravel中的相關(guān)實(shí)現(xiàn),就會(huì)感覺豁然開朗蜒简。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末瘸羡,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子臭蚁,更是在濱河造成了極大的恐慌最铁,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件垮兑,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡漱挎,警方通過查閱死者的電腦和手機(jī)系枪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來磕谅,“玉大人私爷,你說我怎么就攤上這事〔布校” “怎么了衬浑?”我有些...
    開封第一講書人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)放刨。 經(jīng)常有香客問我工秩,道長(zhǎng),這世上最難降的妖魔是什么进统? 我笑而不...
    開封第一講書人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任助币,我火速辦了婚禮,結(jié)果婚禮上螟碎,老公的妹妹穿的比我還像新娘眉菱。我一直安慰自己,他們只是感情好掉分,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開白布俭缓。 她就那樣靜靜地躺著,像睡著了一般酥郭。 火紅的嫁衣襯著肌膚如雪华坦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,598評(píng)論 1 305
  • 那天褥民,我揣著相機(jī)與錄音季春,去河邊找鬼。 笑死消返,一個(gè)胖子當(dāng)著我的面吹牛载弄,可吹牛的內(nèi)容都是我干的耘拇。 我是一名探鬼主播,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼宇攻,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼惫叛!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起逞刷,我...
    開封第一講書人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤嘉涌,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后夸浅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體仑最,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年帆喇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了警医。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡坯钦,死狀恐怖预皇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情婉刀,我是刑警寧澤吟温,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站突颊,受9級(jí)特大地震影響鲁豪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜洋丐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一呈昔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧友绝,春花似錦堤尾、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至掷漱,卻和暖如春粘室,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背卜范。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工衔统, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓锦爵,卻偏偏與公主長(zhǎng)得像舱殿,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子险掀,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355

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

  • 學(xué)習(xí)laravel快小一年了沪袭,到現(xiàn)在才去研究laravel 的核心 '容器 IOC' 這些概念. 寫項(xiàng)目的時(shí)候有大...
    哎喲我的巴扎黑閱讀 5,189評(píng)論 6 26
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)樟氢,斷路器冈绊,智...
    卡卡羅2017閱讀 134,657評(píng)論 18 139
  • 追光者2017閱讀 205評(píng)論 0 0