深刻剖析 Laravel 服務容器

本文首發於 深刻剖析 Laravel 服務容器,轉載請註明出處。喜歡的朋友不要吝嗇大家的贊同,謝謝。

以前在 深度挖掘 Laravel 生命週期 一文中,咱們有去探究 Laravel 到底是如何接收 HTTP 請求,又是如何生成響應並最終呈現給用戶的工做原理。php

本章將帶領你們研究另外一個 Laravel 框架的核心內容:「服務容器」。有閱讀過 Laravel 文檔 的朋友應該有注意到在「核心架構」篇章中包含了幾個主題:生命週期服務容器服務提供者FacadesConcracts.html

今天就讓咱們一塊兒來揭開「Laravel 服務容器」的神祕面紗。laravel

提示:本文內容較長可能須要耗費較多的閱讀時間,另外文中包含 Laravel 內核代碼建議選擇合適的 IDE 或文本編輯器進行源碼閱讀。

目錄結構

  • 序章
  • 依賴注入基本概念git

    • 什麼是依賴注入
    • 什麼是依賴注入容器
    • 什麼是控制反轉(IoC)
  • Laravel 服務容器是什麼github

    • 小結
  • Laravel 服務容器的使用方法redis

    • 管理待建立類的依賴
    • 經常使用綁定方法編程

      • bind 簡單綁定
      • singleton 單例綁定
      • instance 實例綁定
      • contextual-binding 上下文綁定
      • 自動注入和解析
  • Laravel 服務容器實現原理設計模式

    • 註冊基礎服務緩存

      • 註冊基礎服務提供者
      • 註冊核心服務別名到容器
    • 管理所需建立的類及其依賴cookie

      • bind 方法執行原理
      • make 解析處理
  • 資料

序章

若是您有閱讀個人前做 深度挖掘 Laravel 生命週期 一文,你應該已經注意到「APP 容器」、「服務容器」、「綁定」和「解析」這些字眼。沒錯這些技術都和「Laravel 服務容器」有着緊密的聯繫。

在學習什麼是「Laravel 服務容器」以前,若是您對「IoC(控制反轉)」、「DI(依賴注入)」和「依賴注入容器」等相關知識還不夠了解的話,建議先學習一下這些資料:

雖然,這些學習資料都有細緻的講解容器相關的概念。但介紹一下與「Laravel 服務容器」有關的基本概念仍然有必要。

依賴注入基本概念

這個小節會捎帶講解下「IoC(控制反轉)」、「DI(依賴注入)」和「依賴注入容器」這些概念。

什麼是依賴注入

應用程序對須要使用的依賴「插件」在編譯(編碼)階段僅依賴於接口的定義,到運行階段由一個獨立的組裝模塊(容器)完成對實現類的實例化工做,並將其「注射」到應用程序中稱之爲「依賴注入」。

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

至於如何實現面向接口編程,在 依賴注入系列教程 的前兩篇中有實例演示,感興趣的朋友能夠去閱讀這個教程。更多細節能夠閱讀 Inversion of Control Containers and the Dependency Injection pattern深刻淺出依賴注入

什麼是依賴注入容器

在依賴注入過程當中,由一個獨立的組裝模塊(容器)完成對實現類的實例化工做,那麼這個組裝模塊就是「依賴注入容器」。

通俗一點講,使用「依賴注入容器」時無需人肉使用 new 關鍵字去實例化所依賴的「插件」,轉而由「依賴注入容器」自動的完成一個模塊的組裝、配置、實例化等工做。

什麼是控制反轉(IoC)

IoC 是 Inversion of Control 的簡寫,一般被稱爲控制反轉,控制反轉從字面上來講比較不容易被理解。

要掌握什麼是「控制反轉」須要整明白項目中「控制反轉」究竟「反轉」了哪方面的「控制」,它須要解決如何去定位(獲取)服務所須要的依賴的實現。

實現控制反轉時,經過將原先在模塊內部完成具體實現類的實例化,移至模塊的外部,而後再經過「依賴注入」的方式將具體實例「注入」到模塊內即完成了對控制的反轉操做。

「依賴注入」的結果就是「控制反轉」的目的,也就說 控制反轉 的最終目標是爲了 實現項目的高內聚低耦合,而 實現這種目標 的方式則是經過 依賴注入 這種設計模式。

以上就是一些有關服務容器的一些基本概念。和我前面說的同樣,本文不是一篇講解依賴注入的文章,因此更多的細節須要你們自行去學習我以前列出的參考資料。

接下來纔是今天的正餐,我將從如下幾個角度講解 Laravel 服務容器的相關內容:

  • Laravel 服務容器是什麼;
  • Laravel 服務容器的使用方法;
  • Laravel 服務容器技術原理。

Laravel 服務容器是什麼

Laravel 文檔 中,有一段關於 Laravel 服務容器的介紹:

Laravel 服務容器是用於管理類的依賴和執行依賴注入的工具。依賴注入這個花俏名詞實質上是指:類的依賴項經過構造函數,或者某些狀況下經過「setter」方法「注入」到類中。

劃下重點,「Laravel 服務容器」是用於 管理類的依賴執行依賴注入工具

經過前一節「依賴注入基本概念」相關闡述,咱們不可貴出這樣一個簡單的結論「Laravel 服務容器」就是「依賴注入容器」。

其實,服務容器做爲「依賴注入容器」去完成 Laravel 所需依賴的註冊、綁定和解析工做只是 「Laravel 服務容器」核心功能之一;另外,「Laravel 服務容器」還擔綱 Laravel 應用的註冊程序的功能。

節選一段「深度挖掘 Laravel 生命週期」一文中有關服務容器的內容:

建立應用實例即實例化 Illuminate\Foundation\Application 這個服務容器,後續咱們稱其爲 APP 容器。在建立 APP 容器主要會完成:註冊應用的基礎路徑並將路徑綁定到 APP 容器 、註冊基礎服務提供者至 APP 容器 、註冊核心容器別名至 APP 容器 等基礎服務的註冊工做。

因此要了解 Larvel 服務容器必然須要研究 Illuminate\Foundation\Application 的構造函數:

/**
     * 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();
    }

沒錯在 Application 類的構造函數一共完成 3 個操做的處理功能:

  • 經過 registerBaseBindings() 方法將「App 實例(即 Laravel 服務容器)」自身註冊到「Laravel 服務容器」;
  • 經過 registerBaseServiceProviders() 註冊應用 Laravel 框架的基礎服務提供者;
  • 經過 registerCoreContainerAliases() 將具體的「依賴注入容器」及其別名註冊到「Laravel 服務容器」。

這裏所說的「註冊」歸根到底仍是在執行「Laravel 服務容器」的「綁定(bind)」操做,完成綁定接口到實現。

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

/**
     * Register the basic bindings into the container. 註冊 App 實例自己到 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() 方法會將對象實例 $this** 綁定到容器的 **app** 和 **Container::class** 接口。後續不管是經過 **app()->make('app')** 仍是 **app()->make(ontainer::class)** 獲取到的實現類都是 **$this(即 Laravel 服務容器實例) 對象。有關 instance 的使用方法能夠查閱 Laravel 服務容器解析文檔,不過我也會在下文中給出相關使用說明。

到這裏相信你們對「Laravel 服務容器」有了一個比較清晰的理解了。

小結

咱們所說的「Laravel 服務容器」除了擔綱「依賴注入容器」職能外;同時,還會做爲 Laravel 項目的註冊中心去完成基礎服務的註冊工做。直白一點講在它的內部會將諸多服務的實現類「綁定」到「Laravel 服務容器」。總結起來它的做用主要能夠歸爲如下 2 方面:

  1. 註冊基礎服務;
  2. 管理所需建立的類及其依賴。

Laravel 服務容器的使用方法

Laravel 服務容器在使用時通常分爲兩個階段:使用以前進行綁定(bind)完成將實現綁定到接口;使用時對經過接口解析(make)出服務。

Laravel 內置多種不一樣的綁定方法以用於不一樣的使用場景。但不管哪一種綁定方式,它們的最終目標是一致的:綁定接口到實現。

這樣的好處是在項目的編碼階段創建起接口和實現的映射關係,到使用階段經過抽象類(接口)解析出它的具體實現,這樣就實現了項目中的解耦。

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

管理待建立類的依賴

經過向服務容器中綁定須要建立的類及其依賴,當須要使用這個類時直接從服務容器中解析出這個類的實例。類的實例化及其依賴的注入,徹底由服務容器自動的去完成。

舉個示例,相比於經過 new 關鍵詞建立類實例:

<?php
$dependency = new ConfigDependency(config('cache.config.setting'));
$cache = new MemcachedCache($dependency);

每次實例化時咱們都須要手動的將依賴 $dependency 傳入到構造函數內。

而若是咱們經過「Laravel 服務容器」綁定來管理依賴的話:

<?php
App::bind(Cache::class, function () {
    $dependency = new ConfigDependency(config('cache.config.setting'));
    return $cache = new MemcachedCache($dependency);
});

僅需在匿名函數內一次建立所需依賴 $dependency,再將依賴傳入到服務進行實例化,並返回服務實例。

此時,使用 Cache 服務時只要從「Laravel 服務容器」中解析(make)出來便可,而無需每次手動傳入 ConfigDependency 依賴再實例化服務。由於,全部的依賴注入工做此時都由 Laravel 服務容器 自動的給咱們作好了,這樣就簡化了服務處理。

下面演示瞭如何解析出 Cache 服務:

<?php
$cache = App::make(Cache::class);

先了解 Laravel 服務容器的一個使用場景,會對學習服務容器的 綁定方式 大有裨益。

Laravel 服務容器解析 - 綁定 這部分的文檔咱們知道經常使用的綁定方式有:

  • bind($abstract, $concrete) 簡單綁定:將實現綁定到接口,解析時每次返回新的實例;
  • singleton($abstract, $concrete) 單例綁定:將實現綁定到接口,與 bind 方法不一樣的是首次解析是建立實例,後續解析時直接獲取首次解析的實例對象;
  • instance($abstract, $instance) 實例綁定:將實現實例綁定到接口;
  • 上下文綁定和自動注入。

接下來咱們將學習這些綁定方法。

經常使用綁定方法

bind 簡單綁定

bind 方法的功能是將服務的實現綁定到抽象類,而後在每次執行服務解析操做時,Laravel 容器都會從新建立實例對象。

bind 的使用方法已經在「管理待建立類的依賴」一節中有過簡單的演示,它會在每次使用 App::make(Cache::class) 去解析 Cache 服務時,從新執行「綁定」操做中定義的閉包而從新建立 MemcachedCache 緩存實例。

bind 方法除了可以接收閉包做爲實現外,還能夠:

  • 接收具體實現類的類名;
  • 接收 null 值以綁定自身。

singleton 單例綁定

採用單例綁定時,僅在首次解析時建立實例,後續使用 make 進行解析服務操做都將直接獲取這個已解析的對象,實現了 共享 操做。

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

App::singleton(Cache::class, function () {
    $dependency = new ConfigDependency(config('cache.config.setting'));
    return $cache = new MemcachedCache($dependency);
});

instance 實例綁定

實例綁定的功能是將已經建立的實例對象綁定到接口以供後續使用,這種使用場景相似於 註冊表

好比用於存儲用戶模型:

<?php
// 建立一個用戶實例
$artisan = new User('柳公子');

// 將實例綁定到服務容器
App::instance('login-user', $artisan);

// 獲取用戶實例
$artisan = App::make('login-user');

contextual-binding 上下文綁定

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

每一段程序都有不少外部變量。只有像Add這種簡單的函數纔是沒有外部變量的。一旦你的一段程序有了外部變量,這段程序就不完整,不能獨立運行。你爲了使他們運行,就要給全部的外部變量一個一個寫一些值進去。這些值的集合就叫上下文。 「編程中什麼是「Context(上下文)」?」 - vczh的回答

上下文綁定在 Laravel 服務容器解析 - 上下文綁定 文檔中給出了相關示例:

use Illuminate\Support\Facades\Storage;
use App\Http\Controllers\PhotoController;
use App\Http\Controllers\VideoController;
use Illuminate\Contracts\Filesystem\Filesystem;

$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');
          });

在項目中常會用到存儲功能,得益於 Laravel 內置集成了 FlySystemFilesystem 接口,咱們很容易實現多種存儲服務的項目。

示例中將用戶頭像存儲到本地,將用戶上傳的小視頻存儲到雲服務。那麼這個時就須要區分這樣不一樣的使用場景(即上下文或者說環境)。

當用戶存儲頭像(PhotoController::class)須要使用存儲服務(Filesystem::class)時,咱們將本地存儲驅動,做爲實現給到 PhotoController::class

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

而當用戶上傳視頻 VideoController::class,須要使用存儲服務(Filesystem::class)時,咱們則將雲服務驅動,做爲實現給到 VideoController::class

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

這樣就實現了基於不一樣的環境獲取不一樣的服務實現。

自動注入和解析

「Laravel 服務容器」功能強大的緣由在於除了提供手動的綁定接口到實現的方法,還支持自動注入和解析的功能。

咱們在編寫控制器時,常常會使用類型提示功能將某個類做爲依賴傳入構造函數;但在執行這個類時卻無需咱們去實例化這個類所需的依賴,這一切歸功於自動解析的能力。

好比,咱們的用戶控制器須要獲取用戶信息,而後在構造函數中定義 User 模型做爲依賴:

<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\User;
class UserController
{
    private $user = null;

    public function __construct(User $user)
    {
        $this->user = $user;
    }
}

而後,當訪問用戶模塊時 Laravel 會自動解析出 User 模型,而無需手動的常見模型示例。

除了以上幾種數據綁定方法外還有 tag(標籤綁定)extend(擴展綁定) 等,毫無疑問這些內容在 Laravel 文檔 也有介紹,因此這裏就再也不過多介紹了。

下一節,咱們將深刻到源碼中去窺探下 Laravel 服務容器是如何進行綁定和解析處理的。

Laravel 服務容器實現原理

要了解一項技術的實現原理,免不了去探索源碼,源碼學習是個有意思的事情。這個過程不但讓咱們理解它是如何工做的,或許還會帶給咱們一些意外驚喜。

咱們知道 Laravel 服務容器其實會處理如下兩方面的工做:

  1. 註冊基礎服務;
  2. 管理所需建立的類及其依賴。

註冊基礎服務

關於註冊基礎服務,在「深度挖掘 Laravel 生命週期」一文中其實已經有所涉及,但並並不深刻。

本文將進一步的研究註冊基礎服務的細節。除了研究這些服務究竟如何被註冊到服務容器,還將學習它們是如何被使用的。全部的這些都須要咱們深刻到 Illuminate\Foundation\Application 類的內部:

/**
     * 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();
    }

前面咱們已經研究過 registerBaseBindings() 方法,瞭解到該方法主要是將自身綁定到了服務容器,如此咱們即可以在項目中使用 $this->app->make('something') 去解析一項服務。

如今讓咱們將焦點集中到 registerBaseServiceProvidersregisterCoreContainerAliases 這兩個方法。

註冊基礎服務提供者

打開 registerBaseServiceProviders 方法將發如今方法體中僅有 3 行代碼,分別是註冊 EventServiceProviderLogServiceProviderRoutingServiceProvider 這 3 個服務提供者:

/**
     * Register all of the base service providers. 註冊應用基礎服務提供者
     *
     * @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  \Illuminate\Support\ServiceProvider|string  $provider
     * @param  array  $options
     * @param  bool   $force
     * @return \Illuminate\Support\ServiceProvider
     */
    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);
        }

        // 當服務提供者存在 register 方法時,執行 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.
        // 執行服務提供者 boot 方法啓動程序
        if ($this->booted) {
            $this->bootProvider($provider);
        }

        return $provider;
    }

    /**
     * Boot the given service provider. 啓動給定服務提供者
     *
     * @param  \Illuminate\Support\ServiceProvider  $provider
     * @return mixed
     */
    protected function bootProvider(ServiceProvider $provider)
    {
        if (method_exists($provider, 'boot')) {
            return $this->call([$provider, 'boot']);
        }
    }

Laravel 服務容器在執行註冊方法時,須要進行以下處理:

  1. 若是服務提供者存在 register 方法,會將服務實現綁定到容器操做 $provider->register();;
  2. 若是服務提供者存在 boot 方法,會在 bootProvider 方法內執行啓動方法來啓動這個服務。

值得指出的是在服務提供者的 register 方法中,最好僅執行「綁定」操做。

爲了更好的說明服務提供者僅完成綁定操做,仍是讓咱們來瞧瞧 EventServiceProvider 服務,看看它究竟作了什麼:

<?php

namespace Illuminate\Events;

use Illuminate\Support\ServiceProvider;
use Illuminate\Contracts\Queue\Factory as QueueFactoryContract;

class EventServiceProvider extends ServiceProvider
{
    /**
     * Register the service provider. 註冊服務提供者
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton('events', function ($app) {
            return (new Dispatcher($app))->setQueueResolver(function () use ($app) {
                return $app->make(QueueFactoryContract::class);
            });
        });
    }
}

沒錯 EventServiceProvider 所作的所有事情,僅僅經過 register 方法將閉包綁定到了服務容器,除此以外就什麼都沒有了。

註冊核心服務別名到容器

用過 Laravel 框架的朋友應該知道在 Laravel 中有個別名系統。最多見的使用場景就是設置路由時,能夠經過 Route 類完成一個新路由的註冊,如:

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

得益於 Laravel Facades 和別名系統咱們能夠很方便的經過別名來使用 Laravel 內置提供的各類服務。

註冊別名和對應服務的映射關係,即是在 registerCoreContainerAliases 方法內來完成的。因爲篇幅所限本文就不作具體細節的展開,後續會單獨出一篇講解別名系統的文章。

不過如今仍是有必要瀏覽下 Laravel 提供了哪些別名服務:

/**
     * Register the core class aliases in the container. 在容器中註冊核心服務的別名
     *
     * @return void
     */
    public function registerCoreContainerAliases()
    {
        foreach ([
            'app'                  => [\Illuminate\Foundation\Application::class, \Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class,  \Psr\Container\ContainerInterface::class],
            'auth'                 => [\Illuminate\Auth\AuthManager::class, \Illuminate\Contracts\Auth\Factory::class],
            'auth.driver'          => [\Illuminate\Contracts\Auth\Guard::class],
            'blade.compiler'       => [\Illuminate\View\Compilers\BladeCompiler::class],
            'cache'                => [\Illuminate\Cache\CacheManager::class, \Illuminate\Contracts\Cache\Factory::class],
            'cache.store'          => [\Illuminate\Cache\Repository::class, \Illuminate\Contracts\Cache\Repository::class],
            'config'               => [\Illuminate\Config\Repository::class, \Illuminate\Contracts\Config\Repository::class],
            'cookie'               => [\Illuminate\Cookie\CookieJar::class, \Illuminate\Contracts\Cookie\Factory::class, \Illuminate\Contracts\Cookie\QueueingFactory::class],
            'encrypter'            => [\Illuminate\Encryption\Encrypter::class, \Illuminate\Contracts\Encryption\Encrypter::class],
            'db'                   => [\Illuminate\Database\DatabaseManager::class],
            'db.connection'        => [\Illuminate\Database\Connection::class, \Illuminate\Database\ConnectionInterface::class],
            'events'               => [\Illuminate\Events\Dispatcher::class, \Illuminate\Contracts\Events\Dispatcher::class],
            'files'                => [\Illuminate\Filesystem\Filesystem::class],
            'filesystem'           => [\Illuminate\Filesystem\FilesystemManager::class, \Illuminate\Contracts\Filesystem\Factory::class],
            'filesystem.disk'      => [\Illuminate\Contracts\Filesystem\Filesystem::class],
            'filesystem.cloud'     => [\Illuminate\Contracts\Filesystem\Cloud::class],
            'hash'                 => [\Illuminate\Contracts\Hashing\Hasher::class],
            'translator'           => [\Illuminate\Translation\Translator::class, \Illuminate\Contracts\Translation\Translator::class],
            'log'                  => [\Illuminate\Log\Writer::class, \Illuminate\Contracts\Logging\Log::class, \Psr\Log\LoggerInterface::class],
            'mailer'               => [\Illuminate\Mail\Mailer::class, \Illuminate\Contracts\Mail\Mailer::class, \Illuminate\Contracts\Mail\MailQueue::class],
            'auth.password'        => [\Illuminate\Auth\Passwords\PasswordBrokerManager::class, \Illuminate\Contracts\Auth\PasswordBrokerFactory::class],
            'auth.password.broker' => [\Illuminate\Auth\Passwords\PasswordBroker::class, \Illuminate\Contracts\Auth\PasswordBroker::class],
            'queue'                => [\Illuminate\Queue\QueueManager::class, \Illuminate\Contracts\Queue\Factory::class, \Illuminate\Contracts\Queue\Monitor::class],
            'queue.connection'     => [\Illuminate\Contracts\Queue\Queue::class],
            'queue.failer'         => [\Illuminate\Queue\Failed\FailedJobProviderInterface::class],
            'redirect'             => [\Illuminate\Routing\Redirector::class],
            'redis'                => [\Illuminate\Redis\RedisManager::class, \Illuminate\Contracts\Redis\Factory::class],
            'request'              => [\Illuminate\Http\Request::class, \Symfony\Component\HttpFoundation\Request::class],
            'router'               => [\Illuminate\Routing\Router::class, \Illuminate\Contracts\Routing\Registrar::class, \Illuminate\Contracts\Routing\BindingRegistrar::class],
            'session'              => [\Illuminate\Session\SessionManager::class],
            'session.store'        => [\Illuminate\Session\Store::class, \Illuminate\Contracts\Session\Session::class],
            'url'                  => [\Illuminate\Routing\UrlGenerator::class, \Illuminate\Contracts\Routing\UrlGenerator::class],
            'validator'            => [\Illuminate\Validation\Factory::class, \Illuminate\Contracts\Validation\Factory::class],
            'view'                 => [\Illuminate\View\Factory::class, \Illuminate\Contracts\View\Factory::class],
        ] as $key => $aliases) {
            foreach ($aliases as $alias) {
                $this->alias($key, $alias);
            }
        }
    }

管理所需建立的類及其依賴

對於 Laravel 服務容器來說,其內部實現上不管是 bindsingletontag 仍是 extend 它們的基本原理大體相似。因此本文中咱們僅研究 bind 綁定來管中窺豹。

咱們知道綁定方法定義在 Laravel 服務容器 Illuminate\Foundation\Application 類內,而 Application繼承自 Illuminate\Container\Container 類。這些與服務容器綁定相關的方法便直接繼承自 Container 類。

bind 方法執行原理

bind 綁定做爲最基本的綁定方法,能夠很好的說明 Laravel 是如何實現綁定服務處理的。

下面摘出 Container 容器中 bind 方法及其相關聯的方法。因爲綁定處理中涉及較多方法,因此我直接將重要的代碼片斷相關注釋作了翻譯及補充說明,以便閱讀:

/**
     * 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)
    {
        // 若是未提供實現類 $concrete,咱們直接將抽象類做爲實現 $abstract。
        // 這以後,咱們無需明確指定 $abstract 和 $concrete 是否爲單例模式,
        // 而是經過 $shared 標識來決定它們是單例仍是每次都須要實例化處理。
        $this->dropStaleInstances($abstract);

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

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

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

        // 最後若是抽象類已經被容器解析過,咱們將觸發 rebound 監聽器。
        // 而且經過觸發 rebound 監聽器回調,將任何已被解析過的服務更新最新的實現到抽象接口。
        if ($this->resolved($abstract)) {
            $this->rebound($abstract);
        }
    }

    /**
     * Get the Closure to be used when building a type. 當綁定實現爲類名時,則封裝成閉包並返回。
     *
     * @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. 依據給定的抽象服務接口,觸發其 "rebound" 回調
     *
     * @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. 獲取給定抽象服務的回調函數。
     *
     * @param  string  $abstract
     * @return array
     */
    protected function getReboundCallbacks($abstract)
    {
        if (isset($this->reboundCallbacks[$abstract])) {
            return $this->reboundCallbacks[$abstract];
        }

        return [];
    }

bind 方法中,主要完成如下幾個方面的處理:

  • 幹掉以前解析過的服務實例;
  • 將綁定的實現類封裝成閉包,以確保後續處理的統一;
  • 針對已解析過的服務實例,再次觸發從新綁定回調函數,同時將最新的實現類更新到接口裏面。

在綁定過程當中,服務容器並不會執行服務的解析操做,這樣有利於提高服務的性能。直到在項目運行期間,被使用時纔會真正解析出須要使用的對應服務,實現「按需加載」。

make 解析處理

解析處理和綁定同樣定義在 Illuminate\Container\Container 類中,不管是手動解析仍是經過自動注入的方式,實現原理都是基於 PHP 的反射機制。

全部咱們仍是直接從 make 方法開始去挖出相關細節:

/**
     * Resolve the given type from the container. 從容器中解析出給定服務具體實現
     *
     * @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. 從容器中解析出給定服務具體實現
     *
     * @param  string  $abstract
     * @param  array  $parameters
     * @return mixed
     */
    protected function resolve($abstract, $parameters = [])
    {
        $abstract = $this->getAlias($abstract);

        // 若是綁定時基於上下文綁定,此時須要解析出上下文實現類
        $needsContextualBuild = ! empty($parameters) || ! is_null(
            $this->getContextualConcrete($abstract)
        );

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

        $this->with[] = $parameters;

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

        // 已準備就緒建立這個綁定的實例。下面將實例化給定實例及內嵌的全部依賴實例。
        // 到這裏咱們已經作好建立實例的準備工做。只有能夠構建的服務才能夠執行 build 方法去實例化服務;
        // 不然也就是說咱們的服務還存在依賴,而後不斷的去解析嵌套的依賴,知道它們能夠去構建(isBuildable)。
        if ($this->isBuildable($concrete, $abstract)) {
            $object = $this->build($concrete);
        } else {
            $object = $this->make($concrete);
        }

        // 若是咱們的服務存在擴展(extend)綁定,此時就須要去執行擴展。
        // 擴展綁定適用於修改服務的配置或者修飾(decorating)服務實現。
        foreach ($this->getExtenders($abstract) as $extender) {
            $object = $extender($object, $this);
        }

        // 若是咱們的服務已單例模式綁定,此時無要將已解析的服務緩存到單例對象池中(instances),
        // 後續即可以直接獲取單例服務對象了。
        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. 判斷給定的實現是否立馬進行構建
     *
     * @param  mixed   $concrete
     * @param  string  $abstract
     * @return bool
     */
    protected function isBuildable($concrete, $abstract)
    {
        // 僅當實現類和接口相同或者實現爲閉包時可構建
        return $concrete === $abstract || $concrete instanceof Closure;
    }

    /**
     * Instantiate a concrete instance of the given type. 構建(實例化)給定類型的實現類(匿名函數)實例
     *
     * @param  string  $concrete
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    public function build($concrete)
    {
        // 若是給定的實現是一個閉包,直接執行並閉包,返回執行結果
        if ($concrete instanceof Closure) {
            return $concrete($this, $this->getLastParameterOverride());
        }

        $reflector = new ReflectionClass($concrete);

        // 若是須要解析的類沒法實例化,即試圖解析一個抽象類類型如: 接口或抽象類而非實現類,直接拋出異常。
        if (! $reflector->isInstantiable()) {
            return $this->notInstantiable($concrete);
        }

        $this->buildStack[] = $concrete;

        // 經過反射獲取實現類構造函數
        $constructor = $reflector->getConstructor();

        // 若是實現類並無定義構造函數,說明這個實現類沒有相關依賴。
        // 咱們能夠直接實例化這個實現類,而無需自動解析依賴(自動注入)。
        if (is_null($constructor)) {
            array_pop($this->buildStack);

            return new $concrete;
        }

        // 獲取到實現類構造函數依賴參數
        $dependencies = $constructor->getParameters();

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

        array_pop($this->buildStack);

        // 這是咱們就能夠建立服務實例並返回。
        return $reflector->newInstanceArgs($instances);
    }

    /**
     * Resolve all of the dependencies from the ReflectionParameters. 從 ReflectionParameters 解析出全部構造函數所需依賴
     *
     * @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;
            }

            // 構造函數參數爲非類時,即參數爲 string、int 等標量類型或閉包時,按照標量和閉包解析;
            // 不然須要解析類。
            $results[] = is_null($dependency->getClass())
                            ? $this->resolvePrimitive($dependency)
                            : $this->resolveClass($dependency);
        }

        return $results;
    }

    /**
     * Resolve a non-class hinted primitive dependency. 依據類型提示解析出標量類型(閉包)數據
     *
     * @param  \ReflectionParameter  $parameter
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    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. 從服務容器中解析出類依賴(自動注入)
     *
     * @param  \ReflectionParameter  $parameter
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    protected function resolveClass(ReflectionParameter $parameter)
    {
        try {
            return $this->make($parameter->getClass()->name);
        }

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

            throw $e;
        }
    }

以上,即是 Laravel 服務容器解析的核心,得益於 PHP 的反射機制,實現了自動依賴注入和服務解析處理,歸納起來包含如下步驟:

    1. 對於單例綁定數據若是一解析過服務則直接返回,不然繼續執行解析;
    1. 非單例綁定的服務類型,經過接口獲取綁定實現類;
    1. 接口即服務或者閉包時進行構建(build)處理,構建時依託於 PHP 反射機制進行自動依賴注入解析出完整的服務實例對象;不然繼續解析(make)出全部嵌套的依賴;
    1. 若是服務存在擴展綁定,解析出擴展綁定結果;
    1. 若是綁定服務爲單例綁定類型(singleton),將解析到的服務加入到單例對象池;
    1. 其它處理如觸發綁定監聽器、將服務標記爲已解析狀態等,並返回服務實例。

更多細節處理仍是須要咱們進一步深刻的內核中才能發掘出來,但到這其實已經差不太多了。有興趣的朋友能夠親自了解下其它綁定方法的源碼解析處理。

以上即是今天 Laravel 服務容器的所有內容,但願對你們有所啓發。

資料

感謝一下優秀的學習資料:

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...

相關文章
相關標籤/搜索