国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

深入剖析 Laravel 服務(wù)容器

abson / 3711人閱讀

摘要:劃下重點(diǎn),服務(wù)容器是用于管理類的依賴和執(zhí)行依賴注入的工具。類的實(shí)例化及其依賴的注入,完全由服務(wù)容器自動(dòng)的去完成。

本文首發(fā)于 深入剖析 Laravel 服務(wù)容器,轉(zhuǎn)載請(qǐng)注明出處。喜歡的朋友不要吝嗇你們的贊同,謝謝。

之前在 深度挖掘 Laravel 生命周期 一文中,我們有去探究 Laravel 究竟是如何接收 HTTP 請(qǐng)求,又是如何生成響應(yīng)并最終呈現(xiàn)給用戶的工作原理。

本章將帶領(lǐng)大家研究另一個(gè) Laravel 框架的核心內(nèi)容:「服務(wù)容器」。有閱讀過 Laravel 文檔 的朋友應(yīng)該有注意到在「核心架構(gòu)」篇章中包含了幾個(gè)主題:生命周期服務(wù)容器服務(wù)提供者FacadesConcracts.

今天就讓我們一起來揭開「Laravel 服務(wù)容器」的神秘面紗。

提示:本文內(nèi)容較長可能需要耗費(fèi)較多的閱讀時(shí)間,另外文中包含 Laravel 內(nèi)核代碼建議選擇合適的 IDE 或文本編輯器進(jìn)行源碼閱讀。
目錄結(jié)構(gòu)

序章

依賴注入基本概念

什么是依賴注入

什么是依賴注入容器

什么是控制反轉(zhuǎn)(IoC)

Laravel 服務(wù)容器是什么

小結(jié)

Laravel 服務(wù)容器的使用方法

管理待創(chuàng)建類的依賴

常用綁定方法

bind 簡單綁定

singleton 單例綁定

instance 實(shí)例綁定

contextual-binding 上下文綁定

自動(dòng)注入和解析

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

注冊(cè)基礎(chǔ)服務(wù)

注冊(cè)基礎(chǔ)服務(wù)提供者

注冊(cè)核心服務(wù)別名到容器

管理所需創(chuàng)建的類及其依賴

bind 方法執(zhí)行原理

make 解析處理

資料

序章

如果您有閱讀我的前作 深度挖掘 Laravel 生命周期 一文,你應(yīng)該已經(jīng)注意到「APP 容器」、「服務(wù)容器」、「綁定」和「解析」這些字眼。沒錯(cuò)這些技術(shù)都和「Laravel 服務(wù)容器」有著緊密的聯(lián)系。

在學(xué)習(xí)什么是「Laravel 服務(wù)容器」之前,如果您對(duì)「IoC(控制反轉(zhuǎn))」、「DI(依賴注入)」和「依賴注入容器」等相關(guān)知識(shí)還不夠了解的話,建議先學(xué)習(xí)一下這些資料:

Inversion of Control Containers and the Dependency Injection pattern:學(xué)習(xí)依賴注入必讀經(jīng)典;

依賴注入系列教程:原教程由 Symfony 框架的創(chuàng)造者所寫,我給出的是我翻譯的文章。原教程一共分 6 篇,前兩篇講解了依賴注入基礎(chǔ)知識(shí),后 4 篇講解依賴注入在 Symfony 中的應(yīng)用,所以可作為選讀材料;

深入淺出依賴注入:這是本人所寫的關(guān)于依賴注入的文章,試圖以一種易于理解的行文講解什么是「依賴注入」這種設(shè)計(jì)模式。

雖然,這些學(xué)習(xí)資料都有細(xì)致的講解容器相關(guān)的概念。但介紹一下與「Laravel 服務(wù)容器」有關(guān)的基本概念仍然有必要。

依賴注入基本概念

這個(gè)小節(jié)會(huì)捎帶講解下「IoC(控制反轉(zhuǎn))」、「DI(依賴注入)」和「依賴注入容器」這些概念。

什么是依賴注入

應(yīng)用程序?qū)π枰褂玫囊蕾嚒覆寮乖诰幾g(編碼)階段僅依賴于接口的定義,到運(yùn)行階段由一個(gè)獨(dú)立的組裝模塊(容器)完成對(duì)實(shí)現(xiàn)類的實(shí)例化工作,并將其「注射」到應(yīng)用程序中稱之為「依賴注入」。

一言以蔽之:面向接口編程。

至于如何實(shí)現(xiàn)面向接口編程,在 依賴注入系列教程 的前兩篇中有實(shí)例演示,感興趣的朋友可以去閱讀這個(gè)教程。更多細(xì)節(jié)可以閱讀 Inversion of Control Containers and the Dependency Injection pattern 和 深入淺出依賴注入。

什么是依賴注入容器

在依賴注入過程中,由一個(gè)獨(dú)立的組裝模塊(容器)完成對(duì)實(shí)現(xiàn)類的實(shí)例化工作,那么這個(gè)組裝模塊就是「依賴注入容器」。

通俗一點(diǎn)講,使用「依賴注入容器」時(shí)無需人肉使用 new 關(guān)鍵字去實(shí)例化所依賴的「插件」,轉(zhuǎn)而由「依賴注入容器」自動(dòng)的完成一個(gè)模塊的組裝、配置、實(shí)例化等工作。

什么是控制反轉(zhuǎn)(IoC)

IoC 是 Inversion of Control 的簡寫,通常被稱為控制反轉(zhuǎn),控制反轉(zhuǎn)從字面上來說比較不容易被理解。

要掌握什么是「控制反轉(zhuǎn)」需要整明白項(xiàng)目中「控制反轉(zhuǎn)」究竟「反轉(zhuǎn)」了哪方面的「控制」,它需要解決如何去定位(獲取)服務(wù)所需要的依賴的實(shí)現(xiàn)。

實(shí)現(xiàn)控制反轉(zhuǎn)時(shí),通過將原先在模塊內(nèi)部完成具體實(shí)現(xiàn)類的實(shí)例化,移至模塊的外部,然后再通過「依賴注入」的方式將具體實(shí)例「注入」到模塊內(nèi)即完成了對(duì)控制的反轉(zhuǎn)操作。

「依賴注入」的結(jié)果就是「控制反轉(zhuǎn)」的目的,也就說 控制反轉(zhuǎn) 的最終目標(biāo)是為了 實(shí)現(xiàn)項(xiàng)目的高內(nèi)聚低耦合,而 實(shí)現(xiàn)這種目標(biāo) 的方式則是通過 依賴注入 這種設(shè)計(jì)模式。

以上就是一些有關(guān)服務(wù)容器的一些基本概念。和我前面說的一樣,本文不是一篇講解依賴注入的文章,所以更多的細(xì)節(jié)需要大家自行去學(xué)習(xí)我之前列出的參考資料。

接下來才是今天的正餐,我將從以下幾個(gè)角度講解 Laravel 服務(wù)容器的相關(guān)內(nèi)容:

Laravel 服務(wù)容器是什么;

Laravel 服務(wù)容器的使用方法;

Laravel 服務(wù)容器技術(shù)原理。

Laravel 服務(wù)容器是什么

在 Laravel 文檔 中,有一段關(guān)于 Laravel 服務(wù)容器的介紹:

Laravel 服務(wù)容器是用于管理類的依賴和執(zhí)行依賴注入的工具。依賴注入這個(gè)花俏名詞實(shí)質(zhì)上是指:類的依賴項(xiàng)通過構(gòu)造函數(shù),或者某些情況下通過「setter」方法「注入」到類中。

劃下重點(diǎn),「Laravel 服務(wù)容器」是用于 管理類的依賴執(zhí)行依賴注入工具

通過前一節(jié)「依賴注入基本概念」相關(guān)闡述,我們不難得出這樣一個(gè)簡單的結(jié)論「Laravel 服務(wù)容器」就是「依賴注入容器」。

其實(shí),服務(wù)容器作為「依賴注入容器」去完成 Laravel 所需依賴的注冊(cè)、綁定和解析工作只是 「Laravel 服務(wù)容器」核心功能之一;另外,「Laravel 服務(wù)容器」還擔(dān)綱 Laravel 應(yīng)用的注冊(cè)程序的功能。

節(jié)選一段「深度挖掘 Laravel 生命周期」一文中有關(guān)服務(wù)容器的內(nèi)容:

創(chuàng)建應(yīng)用實(shí)例即實(shí)例化 IlluminateFoundationApplication 這個(gè)服務(wù)容器,后續(xù)我們稱其為 APP 容器。在創(chuàng)建 APP 容器主要會(huì)完成:注冊(cè)應(yīng)用的基礎(chǔ)路徑并將路徑綁定到 APP 容器 、注冊(cè)基礎(chǔ)服務(wù)提供者至 APP 容器 、注冊(cè)核心容器別名至 APP 容器 等基礎(chǔ)服務(wù)的注冊(cè)工作。

所以要了解 Larvel 服務(wù)容器必然需要研究 IlluminateFoundationApplication 的構(gòu)造函數(shù):

    /**
     * Create a new Illuminate application instance.
     *
     * @see https://github.com/laravel/framework/blob/5.6/src/Illuminate/Foundation/Application.php#L162:27
     * @param  string|null  $basePath
     * @return void
     */
    public function __construct($basePath = null)
    {
        if ($basePath) {
            $this->setBasePath($basePath);
        }
        $this->registerBaseBindings();
        $this->registerBaseServiceProviders();
        $this->registerCoreContainerAliases();
    }

沒錯(cuò)在 Application 類的構(gòu)造函數(shù)一共完成 3 個(gè)操作的處理功能:

通過 registerBaseBindings() 方法將「App 實(shí)例(即 Laravel 服務(wù)容器)」自身注冊(cè)到「Laravel 服務(wù)容器」;

通過 registerBaseServiceProviders() 注冊(cè)應(yīng)用 Laravel 框架的基礎(chǔ)服務(wù)提供者;

通過 registerCoreContainerAliases() 將具體的「依賴注入容器」及其別名注冊(cè)到「Laravel 服務(wù)容器」。

這里所說的「注冊(cè)」歸根到底還是在執(zhí)行「Laravel 服務(wù)容器」的「綁定(bind)」操作,完成綁定接口到實(shí)現(xiàn)。

為了表名我所言非虛,讓我們看看 registerBaseBindings() 方法:

    /**
     * Register the basic bindings into the container. 注冊(cè) App 實(shí)例本身到 App 容器
     *
     * @return void
     */
    protected function registerBaseBindings()
    {
        static::setInstance($this);

        $this->instance("app", $this);
        $this->instance(Container::class, $this);
        $this->instance(PackageManifest::class, new PackageManifest(
            new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
        ));
    }

我們知道 instance() 方法會(huì)將對(duì)象實(shí)例 $this** 綁定到容器的 **app** 和 **Container::class** 接口。后續(xù)無論是通過 **app()->make("app")** 還是 **app()->make(ontainer::class)** 獲取到的實(shí)現(xiàn)類都是 **$this(即 Laravel 服務(wù)容器實(shí)例) 對(duì)象。有關(guān) instance 的使用方法可以查閱 Laravel 服務(wù)容器解析文檔,不過我也會(huì)在下文中給出相關(guān)使用說明。

到這里相信大家對(duì)「Laravel 服務(wù)容器」有了一個(gè)比較清晰的理解了。

小結(jié)

我們所說的「Laravel 服務(wù)容器」除了擔(dān)綱「依賴注入容器」職能外;同時(shí),還會(huì)作為 Laravel 項(xiàng)目的注冊(cè)中心去完成基礎(chǔ)服務(wù)的注冊(cè)工作。直白一點(diǎn)講在它的內(nèi)部會(huì)將諸多服務(wù)的實(shí)現(xiàn)類「綁定」到「Laravel 服務(wù)容器」。總結(jié)起來它的作用主要可以歸為以下 2 方面:

注冊(cè)基礎(chǔ)服務(wù);

管理所需創(chuàng)建的類及其依賴。

Laravel 服務(wù)容器的使用方法

Laravel 服務(wù)容器在使用時(shí)一般分為兩個(gè)階段:使用之前進(jìn)行綁定(bind)完成將實(shí)現(xiàn)綁定到接口;使用時(shí)對(duì)通過接口解析(make)出服務(wù)。

Laravel 內(nèi)置多種不同的綁定方法以用于不同的使用場景。但無論哪種綁定方式,它們的最終目標(biāo)是一致的:綁定接口到實(shí)現(xiàn)。

這樣的好處是在項(xiàng)目的編碼階段建立起接口和實(shí)現(xiàn)的映射關(guān)系,到使用階段通過抽象類(接口)解析出它的具體實(shí)現(xiàn),這樣就實(shí)現(xiàn)了項(xiàng)目中的解耦。

在講解這些綁定方法前,先講一個(gè) Laravel 服務(wù)容器的使用場景。

管理待創(chuàng)建類的依賴

通過向服務(wù)容器中綁定需要?jiǎng)?chuàng)建的類及其依賴,當(dāng)需要使用這個(gè)類時(shí)直接從服務(wù)容器中解析出這個(gè)類的實(shí)例。類的實(shí)例化及其依賴的注入,完全由服務(wù)容器自動(dòng)的去完成。

舉個(gè)示例,相比于通過 new 關(guān)鍵詞創(chuàng)建類實(shí)例:


每次實(shí)例化時(shí)我們都需要手動(dòng)的將依賴 $dependency 傳入到構(gòu)造函數(shù)內(nèi)。

而如果我們通過「Laravel 服務(wù)容器」綁定來管理依賴的話:


僅需在匿名函數(shù)內(nèi)一次創(chuàng)建所需依賴 $dependency,再將依賴傳入到服務(wù)進(jìn)行實(shí)例化,并返回服務(wù)實(shí)例。

此時(shí),使用 Cache 服務(wù)時(shí)只要從「Laravel 服務(wù)容器」中解析(make)出來即可,而無需每次手動(dòng)傳入 ConfigDependency 依賴再實(shí)例化服務(wù)。因?yàn)椋械囊蕾囎⑷牍ぷ鞔藭r(shí)都由 Laravel 服務(wù)容器 自動(dòng)的給我們做好了,這樣就簡化了服務(wù)處理。

下面演示了如何解析出 Cache 服務(wù):


先了解 Laravel 服務(wù)容器的一個(gè)使用場景,會(huì)對(duì)學(xué)習(xí)服務(wù)容器的 綁定方式 大有裨益。

從 Laravel 服務(wù)容器解析 - 綁定 這部分的文檔我們知道常用的綁定方式有:

bind($abstract, $concrete) 簡單綁定:將實(shí)現(xiàn)綁定到接口,解析時(shí)每次返回新的實(shí)例;

singleton($abstract, $concrete) 單例綁定:將實(shí)現(xiàn)綁定到接口,與 bind 方法不同的是首次解析是創(chuàng)建實(shí)例,后續(xù)解析時(shí)直接獲取首次解析的實(shí)例對(duì)象;

instance($abstract, $instance) 實(shí)例綁定:將實(shí)現(xiàn)實(shí)例綁定到接口;

上下文綁定和自動(dòng)注入。

接下來我們將學(xué)習(xí)這些綁定方法。

常用綁定方法 bind 簡單綁定

bind 方法的功能是將服務(wù)的實(shí)現(xiàn)綁定到抽象類,然后在每次執(zhí)行服務(wù)解析操作時(shí),Laravel 容器都會(huì)重新創(chuàng)建實(shí)例對(duì)象。

bind 的使用方法已經(jīng)在「管理待創(chuàng)建類的依賴」一節(jié)中有過簡單的演示,它會(huì)在每次使用 App::make(Cache::class) 去解析 Cache 服務(wù)時(shí),重新執(zhí)行「綁定」操作中定義的閉包而重新創(chuàng)建 MemcachedCache 緩存實(shí)例。

bind 方法除了能夠接收閉包作為實(shí)現(xiàn)外,還可以:

接收具體實(shí)現(xiàn)類的類名;

接收 null 值以綁定自身。

singleton 單例綁定

采用單例綁定時(shí),僅在首次解析時(shí)創(chuàng)建實(shí)例,后續(xù)使用 make 進(jìn)行解析服務(wù)操作都將直接獲取這個(gè)已解析的對(duì)象,實(shí)現(xiàn)了 共享 操作。

綁定處理類似 bind 綁定,只需將 bind 方法替換成 singleton 方法即可:

App::singleton(Cache::class, function () {
    $dependency = new ConfigDependency(config("cache.config.setting"));
    return $cache = new MemcachedCache($dependency);
});
instance 實(shí)例綁定

實(shí)例綁定的功能是將已經(jīng)創(chuàng)建的實(shí)例對(duì)象綁定到接口以供后續(xù)使用,這種使用場景類似于 注冊(cè)表

比如用于存儲(chǔ)用戶模型:


contextual-binding 上下文綁定

在了解上下文綁定之前,先解釋下什么是上下文,引用「輪子哥」的一段解釋:

每一段程序都有很多外部變量。只有像Add這種簡單的函數(shù)才是沒有外部變量的。一旦你的一段程序有了外部變量,這段程序就不完整,不能獨(dú)立運(yùn)行。你為了使他們運(yùn)行,就要給所有的外部變量一個(gè)一個(gè)寫一些值進(jìn)去。這些值的集合就叫上下文。 「編程中什么是「Context(上下文)」?」 - vczh的回答。

上下文綁定在 Laravel 服務(wù)容器解析 - 上下文綁定 文檔中給出了相關(guān)示例:

use IlluminateSupportFacadesStorage;
use AppHttpControllersPhotoController;
use AppHttpControllersVideoController;
use IlluminateContractsFilesystemFilesystem;

$this->app->when(PhotoController::class)
          ->needs(Filesystem::class)
          ->give(function () {
              return Storage::disk("local");
          });

$this->app->when(VideoController::class)
          ->needs(Filesystem::class)
          ->give(function () {
              return Storage::disk("s3");
          });

在項(xiàng)目中常會(huì)用到存儲(chǔ)功能,得益于 Laravel 內(nèi)置集成了 FlySystemFilesystem 接口,我們很容易實(shí)現(xiàn)多種存儲(chǔ)服務(wù)的項(xiàng)目。

示例中將用戶頭像存儲(chǔ)到本地,將用戶上傳的小視頻存儲(chǔ)到云服務(wù)。那么這個(gè)時(shí)就需要區(qū)分這樣不同的使用場景(即上下文或者說環(huán)境)。

當(dāng)用戶存儲(chǔ)頭像(PhotoController::class)需要使用存儲(chǔ)服務(wù)(Filesystem::class)時(shí),我們將本地存儲(chǔ)驅(qū)動(dòng),作為實(shí)現(xiàn)給到 PhotoController::class

function () {
    return Storage::disk("local");
}

而當(dāng)用戶上傳視頻 VideoController::class,需要使用存儲(chǔ)服務(wù)(Filesystem::class)時(shí),我們則將云服務(wù)驅(qū)動(dòng),作為實(shí)現(xiàn)給到 VideoController::class

function () {
    return Storage::disk("s3");
}

這樣就實(shí)現(xiàn)了基于不同的環(huán)境獲取不同的服務(wù)實(shí)現(xiàn)。

自動(dòng)注入和解析

「Laravel 服務(wù)容器」功能強(qiáng)大的原因在于除了提供手動(dòng)的綁定接口到實(shí)現(xiàn)的方法,還支持自動(dòng)注入和解析的功能。

我們?cè)诰帉懣刂破鲿r(shí),經(jīng)常會(huì)使用類型提示功能將某個(gè)類作為依賴傳入構(gòu)造函數(shù);但在執(zhí)行這個(gè)類時(shí)卻無需我們?nèi)?shí)例化這個(gè)類所需的依賴,這一切歸功于自動(dòng)解析的能力。

比如,我們的用戶控制器需要獲取用戶信息,然后在構(gòu)造函數(shù)中定義 User 模型作為依賴:

user = $user;
    }
}

然后,當(dāng)訪問用戶模塊時(shí) Laravel 會(huì)自動(dòng)解析出 User 模型,而無需手動(dòng)的常見模型示例。

除了以上幾種數(shù)據(jù)綁定方法外還有 tag(標(biāo)簽綁定)extend(擴(kuò)展綁定) 等,毫無疑問這些內(nèi)容在 Laravel 文檔 也有介紹,所以這里就不再過多介紹了。

下一節(jié),我們將深入到源碼中去窺探下 Laravel 服務(wù)容器是如何進(jìn)行綁定和解析處理的。

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

要了解一項(xiàng)技術(shù)的實(shí)現(xiàn)原理,免不了去探索源碼,源碼學(xué)習(xí)是個(gè)有意思的事情。這個(gè)過程不但讓我們理解它是如何工作的,或許還會(huì)帶給我們一些意外驚喜。

我們知道 Laravel 服務(wù)容器其實(shí)會(huì)處理以下兩方面的工作:

注冊(cè)基礎(chǔ)服務(wù);

管理所需創(chuàng)建的類及其依賴。

注冊(cè)基礎(chǔ)服務(wù)

關(guān)于注冊(cè)基礎(chǔ)服務(wù),在「深度挖掘 Laravel 生命周期」一文中其實(shí)已經(jīng)有所涉及,但并并不深入。

本文將進(jìn)一步的研究注冊(cè)基礎(chǔ)服務(wù)的細(xì)節(jié)。除了研究這些服務(wù)究竟如何被注冊(cè)到服務(wù)容器,還將學(xué)習(xí)它們是如何被使用的。所有的這些都需要我們深入到 IlluminateFoundationApplication 類的內(nèi)部:

    /**
     * Create a new Illuminate application instance.
     *
     * @see https://github.com/laravel/framework/blob/5.6/src/Illuminate/Foundation/Application.php#L162:27
     * @param  string|null  $basePath
     * @return void
     */
    public function __construct($basePath = null)
    {
        if ($basePath) {
            $this->setBasePath($basePath);
        }
        $this->registerBaseBindings();
        $this->registerBaseServiceProviders();
        $this->registerCoreContainerAliases();
    }

前面我們已經(jīng)研究過 registerBaseBindings() 方法,了解到該方法主要是將自身綁定到了服務(wù)容器,如此我們便可以在項(xiàng)目中使用 $this->app->make("something") 去解析一項(xiàng)服務(wù)。

現(xiàn)在讓我們將焦點(diǎn)集中到 registerBaseServiceProvidersregisterCoreContainerAliases 這兩個(gè)方法。

注冊(cè)基礎(chǔ)服務(wù)提供者

打開 registerBaseServiceProviders 方法將發(fā)現(xiàn)在方法體中僅有 3 行代碼,分別是注冊(cè) EventServiceProviderLogServiceProviderRoutingServiceProvider 這 3 個(gè)服務(wù)提供者:

    /**
     * Register all of the base service providers. 注冊(cè)應(yīng)用基礎(chǔ)服務(wù)提供者
     *
     * @return void
     */
    protected function registerBaseServiceProviders()
    {
        $this->register(new EventServiceProvider($this));
        $this->register(new LogServiceProvider($this));
        $this->register(new RoutingServiceProvider($this));
    }

    /**
     * Register a service provider with the application.
     *
     * @param  IlluminateSupportServiceProvider|string  $provider
     * @param  array  $options
     * @param  bool   $force
     * @return IlluminateSupportServiceProvider
     */
    public function register($provider, $options = [], $force = false)
    {
        if (($registered = $this->getProvider($provider)) && ! $force) {
            return $registered;
        }

        // If the given "provider" is a string, we will resolve it, passing in the
        // application instance automatically for the developer. This is simply
        // a more convenient way of specifying your service provider classes.
        if (is_string($provider)) {
            $provider = $this->resolveProvider($provider);
        }

        // 當(dāng)服務(wù)提供者存在 register 方法時(shí),執(zhí)行 register 方法,完成綁定處理
        if (method_exists($provider, "register")) {
            $provider->register();
        }

        $this->markAsRegistered($provider);

        // If the application has already booted, we will call this boot method on
        // the provider class so it has an opportunity to do its boot logic and
        // will be ready for any usage by this developer"s application logic.
        // 執(zhí)行服務(wù)提供者 boot 方法啟動(dòng)程序
        if ($this->booted) {
            $this->bootProvider($provider);
        }

        return $provider;
    }

    /**
     * Boot the given service provider. 啟動(dòng)給定服務(wù)提供者
     *
     * @param  IlluminateSupportServiceProvider  $provider
     * @return mixed
     */
    protected function bootProvider(ServiceProvider $provider)
    {
        if (method_exists($provider, "boot")) {
            return $this->call([$provider, "boot"]);
        }
    }

Laravel 服務(wù)容器在執(zhí)行注冊(cè)方法時(shí),需要進(jìn)行如下處理:

如果服務(wù)提供者存在 register 方法,會(huì)將服務(wù)實(shí)現(xiàn)綁定到容器操作 $provider->register();;

如果服務(wù)提供者存在 boot 方法,會(huì)在 bootProvider 方法內(nèi)執(zhí)行啟動(dòng)方法來啟動(dòng)這個(gè)服務(wù)。

值得指出的是在服務(wù)提供者的 register 方法中,最好僅執(zhí)行「綁定」操作。

為了更好的說明服務(wù)提供者僅完成綁定操作,還是讓我們來瞧瞧 EventServiceProvider 服務(wù),看看它究竟做了什么:

app->singleton("events", function ($app) {
            return (new Dispatcher($app))->setQueueResolver(function () use ($app) {
                return $app->make(QueueFactoryContract::class);
            });
        });
    }
}

沒錯(cuò) EventServiceProvider 所做的全部事情,僅僅通過 register 方法將閉包綁定到了服務(wù)容器,除此之外就什么都沒有了。

注冊(cè)核心服務(wù)別名到容器

用過 Laravel 框架的朋友應(yīng)該知道在 Laravel 中有個(gè)別名系統(tǒng)。最常見的使用場景就是設(shè)置路由時(shí),可以通過 Route 類完成一個(gè)新路由的注冊(cè),如:

Route::get("/", function() {
    return "Hello World";
});

得益于 Laravel Facades 和別名系統(tǒng)我們可以很方便的通過別名來使用 Laravel 內(nèi)置提供的各種服務(wù)。

注冊(cè)別名和對(duì)應(yīng)服務(wù)的映射關(guān)系,便是在 registerCoreContainerAliases 方法內(nèi)來完成的。由于篇幅所限本文就不做具體細(xì)節(jié)的展開,后續(xù)會(huì)多帶帶出一篇講解別名系統(tǒng)的文章。

不過現(xiàn)在還是有必要瀏覽下 Laravel 提供了哪些別名服務(wù):

    /**
     * Register the core class aliases in the container. 在容器中注冊(cè)核心服務(wù)的別名
     *
     * @return void
     */
    public function registerCoreContainerAliases()
    {
        foreach ([
            "app"                  => [IlluminateFoundationApplication::class, IlluminateContractsContainerContainer::class, IlluminateContractsFoundationApplication::class,  PsrContainerContainerInterface::class],
            "auth"                 => [IlluminateAuthAuthManager::class, IlluminateContractsAuthFactory::class],
            "auth.driver"          => [IlluminateContractsAuthGuard::class],
            "blade.compiler"       => [IlluminateViewCompilersBladeCompiler::class],
            "cache"                => [IlluminateCacheCacheManager::class, IlluminateContractsCacheFactory::class],
            "cache.store"          => [IlluminateCacheRepository::class, IlluminateContractsCacheRepository::class],
            "config"               => [IlluminateConfigRepository::class, IlluminateContractsConfigRepository::class],
            "cookie"               => [IlluminateCookieCookieJar::class, IlluminateContractsCookieFactory::class, IlluminateContractsCookieQueueingFactory::class],
            "encrypter"            => [IlluminateEncryptionEncrypter::class, IlluminateContractsEncryptionEncrypter::class],
            "db"                   => [IlluminateDatabaseDatabaseManager::class],
            "db.connection"        => [IlluminateDatabaseConnection::class, IlluminateDatabaseConnectionInterface::class],
            "events"               => [IlluminateEventsDispatcher::class, IlluminateContractsEventsDispatcher::class],
            "files"                => [IlluminateFilesystemFilesystem::class],
            "filesystem"           => [IlluminateFilesystemFilesystemManager::class, IlluminateContractsFilesystemFactory::class],
            "filesystem.disk"      => [IlluminateContractsFilesystemFilesystem::class],
            "filesystem.cloud"     => [IlluminateContractsFilesystemCloud::class],
            "hash"                 => [IlluminateContractsHashingHasher::class],
            "translator"           => [IlluminateTranslationTranslator::class, IlluminateContractsTranslationTranslator::class],
            "log"                  => [IlluminateLogWriter::class, IlluminateContractsLoggingLog::class, PsrLogLoggerInterface::class],
            "mailer"               => [IlluminateMailMailer::class, IlluminateContractsMailMailer::class, IlluminateContractsMailMailQueue::class],
            "auth.password"        => [IlluminateAuthPasswordsPasswordBrokerManager::class, IlluminateContractsAuthPasswordBrokerFactory::class],
            "auth.password.broker" => [IlluminateAuthPasswordsPasswordBroker::class, IlluminateContractsAuthPasswordBroker::class],
            "queue"                => [IlluminateQueueQueueManager::class, IlluminateContractsQueueFactory::class, IlluminateContractsQueueMonitor::class],
            "queue.connection"     => [IlluminateContractsQueueQueue::class],
            "queue.failer"         => [IlluminateQueueFailedFailedJobProviderInterface::class],
            "redirect"             => [IlluminateRoutingRedirector::class],
            "redis"                => [IlluminateRedisRedisManager::class, IlluminateContractsRedisFactory::class],
            "request"              => [IlluminateHttpRequest::class, SymfonyComponentHttpFoundationRequest::class],
            "router"               => [IlluminateRoutingRouter::class, IlluminateContractsRoutingRegistrar::class, IlluminateContractsRoutingBindingRegistrar::class],
            "session"              => [IlluminateSessionSessionManager::class],
            "session.store"        => [IlluminateSessionStore::class, IlluminateContractsSessionSession::class],
            "url"                  => [IlluminateRoutingUrlGenerator::class, IlluminateContractsRoutingUrlGenerator::class],
            "validator"            => [IlluminateValidationFactory::class, IlluminateContractsValidationFactory::class],
            "view"                 => [IlluminateViewFactory::class, IlluminateContractsViewFactory::class],
        ] as $key => $aliases) {
            foreach ($aliases as $alias) {
                $this->alias($key, $alias);
            }
        }
    }
管理所需創(chuàng)建的類及其依賴

對(duì)于 Laravel 服務(wù)容器來講,其內(nèi)部實(shí)現(xiàn)上無論是 bindsingletontag 還是 extend 它們的基本原理大致類似。所以本文中我們僅研究 bind 綁定來管中窺豹。

我們知道綁定方法定義在 Laravel 服務(wù)容器 IlluminateFoundationApplication 類內(nèi),而 Application繼承自 IlluminateContainerContainer 類。這些與服務(wù)容器綁定相關(guān)的方法便直接繼承自 Container 類。

bind 方法執(zhí)行原理

bind 綁定作為最基本的綁定方法,可以很好的說明 Laravel 是如何實(shí)現(xiàn)綁定服務(wù)處理的。

下面摘出 Container 容器中 bind 方法及其相關(guān)聯(lián)的方法。由于綁定處理中涉及較多方法,所以我直接將重要的代碼片段相關(guān)注釋做了翻譯及補(bǔ)充說明,以便閱讀:

    /**
     * Register a binding with the container.
     *
     * @param  string  $abstract
     * @param  Closure|string|null  $concrete
     * @param  bool  $shared
     * @return void
     */
    public function bind($abstract, $concrete = null, $shared = false)
    {
        // 如果未提供實(shí)現(xiàn)類 $concrete,我們直接將抽象類作為實(shí)現(xiàn) $abstract。
        // 這之后,我們無需明確指定 $abstract 和 $concrete 是否為單例模式,
        // 而是通過 $shared 標(biāo)識(shí)來決定它們是單例還是每次都需要實(shí)例化處理。
        $this->dropStaleInstances($abstract);

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

        // 如果綁定時(shí)傳入的實(shí)現(xiàn)類非閉包,即綁定時(shí)是直接給定了實(shí)現(xiàn)類的類名,
        // 這時(shí)要稍微處理下將類名封裝成一個(gè)閉包,保證解析時(shí)處理手法的統(tǒng)一。
        if (! $concrete instanceof Closure) {
            $concrete = $this->getClosure($abstract, $concrete);
        }

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

        // 最后如果抽象類已經(jīng)被容器解析過,我們將觸發(fā) rebound 監(jiān)聽器。
        // 并且通過觸發(fā) rebound 監(jiān)聽器回調(diào),將任何已被解析過的服務(wù)更新最新的實(shí)現(xiàn)到抽象接口。
        if ($this->resolved($abstract)) {
            $this->rebound($abstract);
        }
    }

    /**
     * Get the Closure to be used when building a type. 當(dāng)綁定實(shí)現(xiàn)為類名時(shí),則封裝成閉包并返回。
     *
     * @param  string  $abstract
     * @param  string  $concrete
     * @return Closure
     */
    protected function getClosure($abstract, $concrete)
    {
        return function ($container, $parameters = []) use ($abstract, $concrete) {
            if ($abstract == $concrete) {
                return $container->build($concrete);
            }

            return $container->make($concrete, $parameters);
        };
    }

    /**
     * Fire the "rebound" callbacks for the given abstract type. 依據(jù)給定的抽象服務(wù)接口,觸發(fā)其 "rebound" 回調(diào)
     *
     * @param  string  $abstract
     * @return void
     */
    protected function rebound($abstract)
    {
        $instance = $this->make($abstract);

        foreach ($this->getReboundCallbacks($abstract) as $callback) {
            call_user_func($callback, $this, $instance);
        }
    }

    /**
     * Get the rebound callbacks for a given type. 獲取給定抽象服務(wù)的回調(diào)函數(shù)。
     *
     * @param  string  $abstract
     * @return array
     */
    protected function getReboundCallbacks($abstract)
    {
        if (isset($this->reboundCallbacks[$abstract])) {
            return $this->reboundCallbacks[$abstract];
        }

        return [];
    }

bind 方法中,主要完成以下幾個(gè)方面的處理:

干掉之前解析過的服務(wù)實(shí)例;

將綁定的實(shí)現(xiàn)類封裝成閉包,以確保后續(xù)處理的統(tǒng)一;

針對(duì)已解析過的服務(wù)實(shí)例,再次觸發(fā)重新綁定回調(diào)函數(shù),同時(shí)將最新的實(shí)現(xiàn)類更新到接口里面。

在綁定過程中,服務(wù)容器并不會(huì)執(zhí)行服務(wù)的解析操作,這樣有利于提升服務(wù)的性能。直到在項(xiàng)目運(yùn)行期間,被使用時(shí)才會(huì)真正解析出需要使用的對(duì)應(yīng)服務(wù),實(shí)現(xiàn)「按需加載」。

make 解析處理

解析處理和綁定一樣定義在 IlluminateContainerContainer 類中,無論是手動(dòng)解析還是通過自動(dòng)注入的方式,實(shí)現(xiàn)原理都是基于 PHP 的反射機(jī)制。

所有我們還是直接從 make 方法開始去挖出相關(guān)細(xì)節(jié):

    /**
     * Resolve the given type from the container. 從容器中解析出給定服務(wù)具體實(shí)現(xiàn)
     *
     * @param  string  $abstract
     * @param  array  $parameters
     * @return mixed
     */
    public function make($abstract, array $parameters = [])
    {
        return $this->resolve($abstract, $parameters);
    }

    /**
     * Resolve the given type from the container. 從容器中解析出給定服務(wù)具體實(shí)現(xiàn)
     *
     * @param  string  $abstract
     * @param  array  $parameters
     * @return mixed
     */
    protected function resolve($abstract, $parameters = [])
    {
        $abstract = $this->getAlias($abstract);

        // 如果綁定時(shí)基于上下文綁定,此時(shí)需要解析出上下文實(shí)現(xiàn)類
        $needsContextualBuild = ! empty($parameters) || ! is_null(
            $this->getContextualConcrete($abstract)
        );

        // 如果給定的類型已單例模式綁定,直接從服務(wù)容器中返回這個(gè)實(shí)例而無需重新實(shí)例化
        if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
            return $this->instances[$abstract];
        }

        $this->with[] = $parameters;

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

        // 已準(zhǔn)備就緒創(chuàng)建這個(gè)綁定的實(shí)例。下面將實(shí)例化給定實(shí)例及內(nèi)嵌的所有依賴實(shí)例。
        // 到這里我們已經(jīng)做好創(chuàng)建實(shí)例的準(zhǔn)備工作。只有可以構(gòu)建的服務(wù)才可以執(zhí)行 build 方法去實(shí)例化服務(wù);
        // 否則也就是說我們的服務(wù)還存在依賴,然后不斷的去解析嵌套的依賴,知道它們可以去構(gòu)建(isBuildable)。
        if ($this->isBuildable($concrete, $abstract)) {
            $object = $this->build($concrete);
        } else {
            $object = $this->make($concrete);
        }

        // 如果我們的服務(wù)存在擴(kuò)展(extend)綁定,此時(shí)就需要去執(zhí)行擴(kuò)展。
        // 擴(kuò)展綁定適用于修改服務(wù)的配置或者修飾(decorating)服務(wù)實(shí)現(xiàn)。
        foreach ($this->getExtenders($abstract) as $extender) {
            $object = $extender($object, $this);
        }

        // 如果我們的服務(wù)已單例模式綁定,此時(shí)無要將已解析的服務(wù)緩存到單例對(duì)象池中(instances),
        // 后續(xù)便可以直接獲取單例服務(wù)對(duì)象了。
        if ($this->isShared($abstract) && ! $needsContextualBuild) {
            $this->instances[$abstract] = $object;
        }

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

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

        array_pop($this->with);

        return $object;
    }

    /**
     * Determine if the given concrete is buildable. 判斷給定的實(shí)現(xiàn)是否立馬進(jìn)行構(gòu)建
     *
     * @param  mixed   $concrete
     * @param  string  $abstract
     * @return bool
     */
    protected function isBuildable($concrete, $abstract)
    {
        // 僅當(dāng)實(shí)現(xiàn)類和接口相同或者實(shí)現(xiàn)為閉包時(shí)可構(gòu)建
        return $concrete === $abstract || $concrete instanceof Closure;
    }

    /**
     * Instantiate a concrete instance of the given type. 構(gòu)建(實(shí)例化)給定類型的實(shí)現(xiàn)類(匿名函數(shù))實(shí)例
     *
     * @param  string  $concrete
     * @return mixed
     *
     * @throws IlluminateContractsContainerBindingResolutionException
     */
    public function build($concrete)
    {
        // 如果給定的實(shí)現(xiàn)是一個(gè)閉包,直接執(zhí)行并閉包,返回執(zhí)行結(jié)果
        if ($concrete instanceof Closure) {
            return $concrete($this, $this->getLastParameterOverride());
        }

        $reflector = new ReflectionClass($concrete);

        // 如果需要解析的類無法實(shí)例化,即試圖解析一個(gè)抽象類類型如: 接口或抽象類而非實(shí)現(xiàn)類,直接拋出異常。
        if (! $reflector->isInstantiable()) {
            return $this->notInstantiable($concrete);
        }

        $this->buildStack[] = $concrete;

        // 通過反射獲取實(shí)現(xiàn)類構(gòu)造函數(shù)
        $constructor = $reflector->getConstructor();

        // 如果實(shí)現(xiàn)類并沒有定義構(gòu)造函數(shù),說明這個(gè)實(shí)現(xiàn)類沒有相關(guān)依賴。
        // 我們可以直接實(shí)例化這個(gè)實(shí)現(xiàn)類,而無需自動(dòng)解析依賴(自動(dòng)注入)。
        if (is_null($constructor)) {
            array_pop($this->buildStack);

            return new $concrete;
        }

        // 獲取到實(shí)現(xiàn)類構(gòu)造函數(shù)依賴參數(shù)
        $dependencies = $constructor->getParameters();

        // 解析出所有依賴
        $instances = $this->resolveDependencies(
            $dependencies
        );

        array_pop($this->buildStack);

        // 這是我們就可以創(chuàng)建服務(wù)實(shí)例并返回。
        return $reflector->newInstanceArgs($instances);
    }

    /**
     * Resolve all of the dependencies from the ReflectionParameters. 從 ReflectionParameters 解析出所有構(gòu)造函數(shù)所需依賴
     *
     * @param  array  $dependencies
     * @return array
     */
    protected function resolveDependencies(array $dependencies)
    {
        $results = [];

        foreach ($dependencies as $dependency) {
            // If this dependency has a override for this particular build we will use
            // that instead as the value. Otherwise, we will continue with this run
            // of resolutions and let reflection attempt to determine the result.
            if ($this->hasParameterOverride($dependency)) {
                $results[] = $this->getParameterOverride($dependency);

                continue;
            }

            // 構(gòu)造函數(shù)參數(shù)為非類時(shí),即參數(shù)為 string、int 等標(biāo)量類型或閉包時(shí),按照標(biāo)量和閉包解析;
            // 否則需要解析類。
            $results[] = is_null($dependency->getClass())
                            ? $this->resolvePrimitive($dependency)
                            : $this->resolveClass($dependency);
        }

        return $results;
    }

    /**
     * Resolve a non-class hinted primitive dependency. 依據(jù)類型提示解析出標(biāo)量類型(閉包)數(shù)據(jù)
     *
     * @param  ReflectionParameter  $parameter
     * @return mixed
     *
     * @throws IlluminateContractsContainerBindingResolutionException
     */
    protected function resolvePrimitive(ReflectionParameter $parameter)
    {
        if (! is_null($concrete = $this->getContextualConcrete("$".$parameter->name))) {
            return $concrete instanceof Closure ? $concrete($this) : $concrete;
        }

        if ($parameter->isDefaultValueAvailable()) {
            return $parameter->getDefaultValue();
        }

        $this->unresolvablePrimitive($parameter);
    }

    /**
     * Resolve a class based dependency from the container. 從服務(wù)容器中解析出類依賴(自動(dòng)注入)
     *
     * @param  ReflectionParameter  $parameter
     * @return mixed
     *
     * @throws IlluminateContractsContainerBindingResolutionException
     */
    protected function resolveClass(ReflectionParameter $parameter)
    {
        try {
            return $this->make($parameter->getClass()->name);
        }

        catch (BindingResolutionException $e) {
            if ($parameter->isOptional()) {
                return $parameter->getDefaultValue();
            }

            throw $e;
        }
    }

以上,便是 Laravel 服務(wù)容器解析的核心,得益于 PHP 的反射機(jī)制,實(shí)現(xiàn)了自動(dòng)依賴注入和服務(wù)解析處理,概括起來包含以下步驟:

對(duì)于單例綁定數(shù)據(jù)如果一解析過服務(wù)則直接返回,否則繼續(xù)執(zhí)行解析;

非單例綁定的服務(wù)類型,通過接口獲取綁定實(shí)現(xiàn)類;

接口即服務(wù)或者閉包時(shí)進(jìn)行構(gòu)建(build)處理,構(gòu)建時(shí)依托于 PHP 反射機(jī)制進(jìn)行自動(dòng)依賴注入解析出完整的服務(wù)實(shí)例對(duì)象;否則繼續(xù)解析(make)出所有嵌套的依賴;

如果服務(wù)存在擴(kuò)展綁定,解析出擴(kuò)展綁定結(jié)果;

如果綁定服務(wù)為單例綁定類型(singleton),將解析到的服務(wù)加入到單例對(duì)象池;

其它處理如觸發(fā)綁定監(jiān)聽器、將服務(wù)標(biāo)記為已解析狀態(tài)等,并返回服務(wù)實(shí)例。

更多細(xì)節(jié)處理還是需要我們進(jìn)一步深入的內(nèi)核中才能發(fā)掘出來,但到這其實(shí)已經(jīng)差不太多了。有興趣的朋友可以親自了解下其它綁定方法的源碼解析處理。

以上便是今天 Laravel 服務(wù)容器的全部內(nèi)容,希望對(duì)大家有所啟發(fā)。

資料

感謝一下優(yōu)秀的學(xué)習(xí)資料:

https://www.insp.top/learn-la...

https://laravel-china.org/art...

https://laravel-china.org/art...

https://hk.saowen.com/a/6c880...

http://rrylee.github.io/2015/...

https://blog.tanteng.me/2016/...

https://juejin.im/entry/5916a...

https://laravel-china.org/top...

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/28702.html

相關(guān)文章

  • 深入剖析 Laravel 服務(wù)提供者實(shí)現(xiàn)原理

    摘要:服務(wù)提供者啟動(dòng)原理之前我們有學(xué)習(xí)深度挖掘生命周期和深入剖析服務(wù)容器,今天我們將學(xué)習(xí)服務(wù)提供者。的所有核心服務(wù)都是通過服務(wù)提供者進(jìn)行引導(dǎo)啟動(dòng)的,所以想深入了解那么研究服務(wù)提供者的原理是個(gè)繞不開的話題。 本文首發(fā)于 深入剖析 Laravel 服務(wù)提供者實(shí)現(xiàn)原理,轉(zhuǎn)載請(qǐng)注明出處。 今天我們將學(xué)習(xí) Laravel 框架另外一個(gè)核心內(nèi)容「服務(wù)提供者(Service Provider)」。服務(wù)提供...

    yeooo 評(píng)論0 收藏0
  • 深入淺出 Laravel 的 Facade 外觀系統(tǒng)

    摘要:外觀模式定義了一個(gè)高層接口,這個(gè)接口使得這一子系統(tǒng)更加容易使用。將使用者與子系統(tǒng)從直接耦合,轉(zhuǎn)變成由外觀類提供統(tǒng)一的接口給使用者使用,以降低客戶端與子系統(tǒng)之間的耦合度。接下來將深入分析外觀服務(wù)的加載過程。引導(dǎo)程序?qū)⒃谔幚碚?qǐng)求是完成引導(dǎo)啟動(dòng)。 本文首發(fā)于 深入淺出 Laravel 的 Facade 外觀系統(tǒng),轉(zhuǎn)載請(qǐng)注明出處。 今天我們將學(xué)習(xí) Laravel 核心架構(gòu)中的另一個(gè)主題「Fac...

    KavenFan 評(píng)論0 收藏0
  • 剖析Laravel隊(duì)列系統(tǒng)--Worker

    摘要:一旦這一切完成,方法會(huì)運(yùn)行在類屬性在命令構(gòu)造后設(shè)置容器解析實(shí)例,在中我們?cè)O(shè)置了將使用的緩存驅(qū)動(dòng),我們也根據(jù)命令來決定我們調(diào)用什么方法。作業(yè)只在以上起效在上也無效處理作業(yè)方法調(diào)用觸發(fā)事件觸發(fā)事件。 譯文GitHub https://github.com/yuansir/diving-laravel-zh 原文鏈接https://divinglaravel.com/queue-system...

    CollinPeng 評(píng)論0 收藏0
  • 剖析Laravel隊(duì)列系統(tǒng)--推送作業(yè)到隊(duì)列

    摘要:有幾種有用的方法可以使用將作業(yè)推送到特定的隊(duì)列在給定的秒數(shù)之后推送作業(yè)延遲后將作業(yè)推送到特定的隊(duì)列推送多個(gè)作業(yè)推送特定隊(duì)列上的多個(gè)作業(yè)調(diào)用這些方法之后,所選擇的隊(duì)列驅(qū)動(dòng)會(huì)將給定的信息存儲(chǔ)在存儲(chǔ)空間中,供按需獲取。 原文鏈接https://divinglaravel.com/queue-system/pushing-jobs-to-queue There are several ways...

    maochunguang 評(píng)論0 收藏0
  • Laravel 運(yùn)行機(jī)制簡略剖析

    1. 預(yù)備知識(shí) 1.1 composer 基本用法 1.1.1 參考文章 composer 基本用法 1.1.2 要求掌握的知識(shí)點(diǎn) composer 依賴管理 composer 自動(dòng)加載(關(guān)鍵) 1.2 DIP、IOC、DI、IOC 容器 詳情文章 2. Laravel 運(yùn)行機(jī)制剖析 2.1 場景 范例:http://laravel.com/test?name=chenxuelong 2.2 ...

    zhaofeihao 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

abson

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<