淺談Laravel Container及其項(xiàng)目實(shí)踐

前言

公司的后臺(tái)是使用Laravel框架寫的,最近在把其中的訂單處理部分抽出來,準(zhǔn)備寫個(gè)單獨(dú)的Library。特地好好的研究了一下設(shè)計(jì)模式诸迟,Laravel學(xué)院上面有一個(gè)專題,便是談設(shè)計(jì)模式的愕乎,甚好!


為了降低耦合性壁公,在我的項(xiàng)目中使用了Laravel Container以支持IoC(控制反轉(zhuǎn))感论。但是就如何在Laravel之外使用illuminate/container這方面資料寥寥無幾,所以這篇文章記錄一下自己的學(xué)習(xí)心得紊册。

背景知識(shí)

  • php/composer
  • IoC

安裝

直接使用composer:

composer require illuminate/container

Laravel之外使用Illuminate/Container

還是以我的Order項(xiàng)目為例比肄,用其中的部分作為講解。先看一下部分架構(gòu)圖囊陡。

架構(gòu)圖

訂單生成的主要流程:Cashier通過OrderFactory來生成訂單芳绩,OrderFactory內(nèi)部存在一條Pipeline,數(shù)據(jù)以流的形式在Pipeline中流經(jīng)各個(gè)PipeWorker撞反,通過不同的加工階段最終生成訂單妥色。

  • Container

首先需要在自己項(xiàng)目中定義一個(gè)容器對(duì)象,來“容納”所有的實(shí)例或者方法等遏片。這里我將Cashier類作為統(tǒng)一的對(duì)外處理對(duì)象嘹害,它繼承自Illuminate\Container\Container撮竿。

一般配置信息會(huì)作為容器的構(gòu)造函數(shù)的參數(shù)。在Laravel中笔呀,由于配置內(nèi)容較多幢踏,這一形式表現(xiàn)為__construct函數(shù)中的$basePath參數(shù)。通過約定的目錄結(jié)構(gòu)結(jié)合$appPath動(dòng)態(tài)讀取配置信息许师。本項(xiàng)目則選擇直接給予一個(gè)Config類的形式房蝉,注入配置信息。Config類實(shí)質(zhì)是一個(gè)鍵值對(duì)的數(shù)組微渠。

class Cashier extends Container implements \WilliamWei\FancyOrder\Contracts\Cashier
{
    protected $providers = [
        OrderServiceProvider::class,
        PipelineServiceProvider::class,
    ];


    public function __construct($config)
    {
        $this['config'] = function () use ($config) {
            return new Config($config);
        };

        //register the provider
        foreach ($this->providers as $provider)
        {
            $provider = new $provider();
            $provider->register($this);
        }

    }
}

Illuminate\Container\Container已經(jīng)實(shí)現(xiàn)了ArrayAccess接口搭幻,可以直接以數(shù)組方式訪問容器中的對(duì)象。構(gòu)造時(shí)敛助,首先為config對(duì)象定義實(shí)例化的閉包函數(shù)粗卜。然后依次將各個(gè)模塊對(duì)應(yīng)的ServiceProvider進(jìn)行注冊(cè)。

  • ServiceProvider

將項(xiàng)目劃分為更小的子模塊有助于控制規(guī)模纳击,可以大大提高可維護(hù)性以及可測(cè)試性续扔。每個(gè)子模塊都有一個(gè)ServiceProvider,用于暴露本模塊可提供的服務(wù)對(duì)象焕数。自定義的ServiceProvider可以繼承Illuminate\Support\ServiceProvider纱昧,這里我是選擇自己實(shí)現(xiàn)的ServiceProvider
接口:

interface ServiceProvider
{
    public function register(Container $app);
}

以PipelineServiceProvider為例:

class PipelineServiceProvider implements ServiceProvider
{
    public function register(Container $app)
    {
        $workers = [];

        foreach ($app['config']['pipeline_workers'] as $name => $worker)
        {
            $app->bind($name,function($app) use ($worker) {
                return new $worker();
            });

            array_push($workers,$app[$name]);
        }

        $app->bind('pipeline',function($app) use ($workers) {
            return new Pipeline($workers);
        });
    }
}

PipelineServiceProviderregister方法中堡赔,將特定函數(shù)與實(shí)例的名字進(jìn)行綁定识脆,當(dāng)通過該名字訪問實(shí)例時(shí),若對(duì)象不存在善已,則容器會(huì)調(diào)用被綁定的函數(shù)來實(shí)例化一個(gè)對(duì)象灼捂,并將其置于容器,以供后續(xù)調(diào)用使用换团。對(duì)于Pipeline悉稠,他并不需要關(guān)心是哪些Pipeworker在工作,只需要知道他們存在艘包,并且可以正常工作就好的猛,從而以這種形式達(dá)到解耦目的。

當(dāng)然有可能有時(shí)候需要訪問容器內(nèi)其他對(duì)象想虎,則可以將容器本身作為構(gòu)造函數(shù)的參數(shù)傳入卦尊,如:

$app->bind('pipeline',function($app) use ($workers) {
    return new Pipeline($app,$workers);
});

那么在Pipeline內(nèi)部就可以通過
$this->app['XXX']的形式訪問XXX對(duì)象,同時(shí)也無需關(guān)心XXX是如何構(gòu)造的舌厨。

代碼分析

這里的代碼分析部分只關(guān)注主體部分岂却,不會(huì)面面具到的分析到每個(gè)函數(shù)。

可以看到整個(gè)包一共就3個(gè)PHP文件,最核心的是Container.php淌友,它定義了容器類煌恢,并實(shí)現(xiàn)了其中絕大多數(shù)功能。當(dāng)我們bind一個(gè)實(shí)例到通過[]下標(biāo)訪問時(shí)發(fā)生了什么震庭?

    /**
     * Register a binding with the container.
     *
     * @param  string|array  $abstract
     * @param  \Closure|string|null  $concrete
     * @param  bool  $shared
     * @return void
     */
    public function bind($abstract, $concrete = null, $shared = false)
    {
        // If no concrete type was given, we will simply set the concrete type to the
        // abstract type. After that, the concrete type to be registered as shared
        // without being forced to state their classes in both of the parameters.
        $this->dropStaleInstances($abstract);

        if (is_null($concrete)) {
            $concrete = $abstract;
        }

        // If the factory is not a Closure, it means it is just a class name which is
        // bound into this container to the abstract type and we will just wrap it
        // up inside its own Closure to give us more convenience when extending.
        if (! $concrete instanceof Closure) {
            $concrete = $this->getClosure($abstract, $concrete);
        }

        $this->bindings[$abstract] = compact('concrete', 'shared');

        // If the abstract type was already resolved in this container we'll fire the
        // rebound listener so that any objects which have already gotten resolved
        // can have their copy of the object updated via the listener callbacks.
        if ($this->resolved($abstract)) {
            $this->rebound($abstract);
        }
    }

這是bind函數(shù)瑰抵,當(dāng)我們執(zhí)行一個(gè)綁定操作時(shí),容器首先會(huì)把該名字之前綁定的實(shí)例與別名清除掉器联,即$this->dropStaleInstances($abstract);如果該名字對(duì)應(yīng)實(shí)例是已經(jīng)解析過的二汛,則會(huì)觸發(fā)rebound,執(zhí)行對(duì)應(yīng)回調(diào)。對(duì)于第一次綁定則不會(huì)出現(xiàn)這種情況拨拓。到此bind就結(jié)束了肴颊。

當(dāng)通過下標(biāo)方式獲取實(shí)例時(shí)

    public function offsetGet($key)
    {
        return $this->make($key);
    }

可以看到調(diào)用了make方法。

    /**
     * Resolve the given type from the container.
     *
     * @param  string  $abstract
     * @return mixed
     */
    public function make($abstract)
    {
        $needsContextualBuild = ! is_null(
            $this->getContextualConcrete($abstract = $this->getAlias($abstract))
        );

        // If an instance of the type is currently being managed as a singleton we'll
        // just return an existing instance instead of instantiating new instances
        // so the developer can keep using the same objects instance every time.
        if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
            return $this->instances[$abstract];
        }

        $concrete = $this->getConcrete($abstract);

        // We're ready to instantiate an instance of the concrete type registered for
        // the binding. This will instantiate the types, as well as resolve any of
        // its "nested" dependencies recursively until all have gotten resolved.
        if ($this->isBuildable($concrete, $abstract)) {
            $object = $this->build($concrete);
        } else {
            $object = $this->make($concrete);
        }

        // If we defined any extenders for this type, we'll need to spin through them
        // and apply them to the object being built. This allows for the extension
        // of services, such as changing configuration or decorating the object.
        foreach ($this->getExtenders($abstract) as $extender) {
            $object = $extender($object, $this);
        }

        // If the requested type is registered as a singleton we'll want to cache off
        // the instances in "memory" so we can return it later without creating an
        // entirely new instance of an object on each subsequent request for it.
        if ($this->isShared($abstract) && ! $needsContextualBuild) {
            $this->instances[$abstract] = $object;
        }

        $this->fireResolvingCallbacks($abstract, $object);

        $this->resolved[$abstract] = true;

        return $object;
    }

略有些復(fù)雜渣磷,大致流程是先檢查實(shí)例數(shù)組中該實(shí)例是否存在婿着,存在的話則返回。對(duì)于不存在的情況醋界,由于我們是實(shí)用閉包的方式進(jìn)行bind竟宋,所以會(huì)調(diào)用該閉包,即$object = $this->build($concrete);得到$object形纺。后面會(huì)通知該實(shí)例對(duì)應(yīng)類的子類丘侠,告知他們?cè)搶?shí)例已被創(chuàng)建。注冊(cè)該實(shí)例到實(shí)例數(shù)組逐样,(如果存在解析完成回調(diào)函數(shù)則會(huì)去執(zhí)行)蜗字,返回實(shí)例。

這個(gè)便是容器內(nèi)部的一個(gè)流程脂新。

BoundMethod.php主要以靜態(tài)的形式實(shí)現(xiàn)了直接調(diào)用某個(gè)類的某一方法的目標(biāo)挪捕。

ContextualBindingBuilder.php則主要是用于將實(shí)例與一個(gè)上下文情景進(jìn)行綁定。這兩部分都是比較高級(jí)的內(nèi)容争便,這里不作展開了担神。

歡迎關(guān)注個(gè)人博客 :)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市始花,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌孩锡,老刑警劉巖酷宵,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異躬窜,居然都是意外死亡浇垦,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門荣挨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來男韧,“玉大人朴摊,你說我怎么就攤上這事〈寺牵” “怎么了甚纲?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)朦前。 經(jīng)常有香客問我介杆,道長(zhǎng),這世上最難降的妖魔是什么韭寸? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任春哨,我火速辦了婚禮,結(jié)果婚禮上恩伺,老公的妹妹穿的比我還像新娘赴背。我一直安慰自己,他們只是感情好晶渠,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布凰荚。 她就那樣靜靜地躺著,像睡著了一般乱陡。 火紅的嫁衣襯著肌膚如雪浇揩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天憨颠,我揣著相機(jī)與錄音胳徽,去河邊找鬼。 笑死爽彤,一個(gè)胖子當(dāng)著我的面吹牛养盗,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播适篙,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼往核,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了嚷节?” 一聲冷哼從身側(cè)響起聂儒,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎硫痰,沒想到半個(gè)月后衩婚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡效斑,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年非春,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡奇昙,死狀恐怖护侮,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情储耐,我是刑警寧澤羊初,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站弧岳,受9級(jí)特大地震影響凳忙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜禽炬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一涧卵、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧腹尖,春花似錦柳恐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至绎巨,卻和暖如春近尚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背场勤。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工戈锻, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人和媳。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓格遭,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親留瞳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子拒迅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

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

  • 先說幾句廢話,調(diào)和氣氛她倘。事情的起由來自客戶需求頻繁變更璧微,偉大的師傅決定橫刀立馬的改革使用新的框架(created ...
    wsdadan閱讀 3,040評(píng)論 0 12
  • 這本書是以期末考試來臨,從小子書包里截留下來的硬梁。其實(shí)他已經(jīng)讀完一遍前硫,而我花了三個(gè)晚上,讀讀停停靶溜,不能流暢的...
    明續(xù)閱讀 815評(píng)論 2 0
  • 學(xué)了第五單元,我懂得了生命與水的關(guān)系,沒有水就沒有我們?nèi)祟愓窒ⅲ瑳]有水嗤详,人們就會(huì)死亡 我學(xué)了《古詩(shī)二首》過分水嶺,飲湖...
    王明亮_fcc0閱讀 162評(píng)論 2 2
  • 01 前陣子我在朋友圈發(fā)了一條狀態(tài)瓷炮,收到了接二連三的回復(fù)葱色,其中一條是“你搬家了?是結(jié)婚了嗎娘香?”我回復(fù)她說沒有結(jié)婚苍狰。...
    star心中無別人閱讀 575評(píng)論 0 0