深入剖析 Laravel 服務提供者實現原理

liuqing_hu發表於2018-06-07

本文首發於 深入剖析 Laravel 服務提供者實現原理,轉載請註明出處。

今天我們將學習 Laravel 框架另外一個核心內容「服務提供者(Service Provider)」。服務提供者的功能是完成 Laravel 應用的引導啟動,或者說是將 Laravel 中的各種服務「註冊」到「Laravel 服務容器」,這樣才能在後續處理 HTTP 請求時使用這些服務。

目錄

  • 服務提供者基本概念
  • 服務提供者入門
    • 建立自定義服務提供者
    • register 方法
      • 簡單註冊服務
    • boot 方法
    • 配置服務提供者
    • 延遲繫結服務提供者
    • 小結
  • 服務提供者啟動原理
    • 載入程式的啟動流程
      • Laravel 執行服務提供者註冊(register)處理
        • RegisterProviders 引導註冊
        • 由服務容器執行配置檔案中的所有服務提供者服務完成註冊。
        • 最後由服務提供者倉庫(ProviderRepository)執行服務提供者的註冊處理。
      • Laravel 執行服務提供者啟動(boot)處理
        • BootProviders 引導啟動
        • 由服務容器執行配置檔案中的所有服務提供者服務完成啟動。
      • Laravel 如何完成延遲載入型別的服務提供者
  • 總結

服務提供者基本概念

我們知道 「服務提供者」是配置應用的中心,它的主要工作是使用「服務容器」實現服務容器繫結、事件監聽器、中介軟體,甚至是路由的註冊。

除核心服務外,幾乎所有的服務提供者都定義在配置檔案 config/app.php 檔案中的 providers 節點中。

服務提供者的典型處理流程是,當接 Laravel 應用接收到 HTTP 請求時會去執行「服務提供者的 register(註冊)」方法,將各個服務「繫結」到容器內;之後,到了實際處理請求階段,依據使用情況按需載入所需服務。這樣的優勢很明顯能夠提升應用的效能。

細心的朋友可能發現這裡用了一個詞「幾乎」,沒錯還有一些屬於核心服務提供者,這些並沒有定義在 providers 配置節點中而是直接由 Illuminate\Foundation\Application 服務容器直接在例項化階段就完成了註冊服務。

<?php
...

class Application extends Container implements ApplicationContract, HttpKernelInterface
{
    public function __construct($basePath = null)
    {
        ...
        $this->registerBaseServiceProviders();
        ...
    }

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

對服務容器不是很熟悉的老鐵可以閱讀 深入剖析 Laravel 服務容器,並且在文中「註冊基礎服務提供者」一節也有詳細分析服務容器是如何註冊服務提供者的。

另外一個,我們還需要了解的是所有的服務提供者都繼承自 Illuminate\Support\ServiceProvider 類。不過對於我們來說目前還無需研究基類,所以我們將焦點放到如何實現一個自定義的服務提供者,然後還有兩個需要掌握方法。

服務提供者入門

建立自定義服務提供者

要建立自定義的「服務提供者」,可以直接使用 Laravel 內建的 artisan 命令完成。

php artisan make:provider RiskServiceProvider

這個命令會在 app/Providers 目錄下建立 RiskServiceProvider.php 檔案,開啟檔案內容如下:

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class RiskServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap the application services.引導啟動應用服務。
     *
     * @return void
     */
    public function boot()
    {
        //
    }

    /**
     * Register the application services. 註冊應用服務。
     *
     * @return void
     */
    public function register()
    {
        //
    }
}

register 方法

register 方法中,我們無需處理業務邏輯,在這個方法中你只需去處理「繫結」服務到服務容器中即可。

從文件中我們知道:

register 方法中,你只需要將類繫結到 服務容器 中。而不需要嘗試在 register 方法中註冊任何事件監聽器、路由或者任何其他功能。否則,你可能會意外使用到尚未載入的服務提供器提供的服務。

如何理解這句話的含義呢?

如果你有了解過服務容器執行原理,就會知道在「繫結」操作僅僅是建立起介面和實現的對應關係,此時並不會建立具體的例項,即不會存在真實的依賴關係。直到某個服務真的被用到時才會從「服務容器」中解析出來,而解析的過程發生在所有服務「註冊」完成之後。

一旦我們嘗試在 register 註冊階段使用某些未被載入的服務依賴,即這個服務目前還沒有被註冊所以不可用。

這樣就需要在「註冊」繫結時,同時需要關注服務的註冊順序,但這一點 Laravel 並不作出任何保證。

理解了這個道理,我們就可以隨便進入一個「服務提供者」來看看其中的 register 方法的邏輯,現在我們挑選的是 Illuminate\Cache\CacheServiceProvider 服務作為講解:

<?php

namespace Illuminate\Cache;

use Illuminate\Support\ServiceProvider;

class CacheServiceProvider extends ServiceProvider
{
    /**
     * Indicates if loading of the provider is deferred.
     *
     * @var bool
     */
    protected $defer = true;

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton('cache', function ($app) {
            return new CacheManager($app);
        });

        $this->app->singleton('cache.store', function ($app) {
            return $app['cache']->driver();
        });

        $this->app->singleton('memcached.connector', function () {
            return new MemcachedConnector;
        });
    }

    /**
     * Get the services provided by the provider.
     *
     * @return array
     */
    public function provides()
    {
        return [
            'cache', 'cache.store', 'memcached.connector',
        ];
    }
}

沒錯,如你所預料的一樣,它的 register 方法執行了三個單例繫結操作,僅此而已。

簡單註冊服務

對於處理複雜繫結邏輯,可以自定義「服務提供者」。但是如果是比較簡單的註冊服務,有沒有比較方便的繫結方法呢?畢竟,並不是每個服務都會有複雜的依賴處理。

我們可以從 文件 中得到解答:

如果你的服務提供商註冊許多簡單的繫結,你可能想使用 bindingssingletons 屬性而不是手動註冊每個容器繫結。

<?php
class AppServiceProvider extends ServiceProvider
{
    /**
     * 設定容器繫結的對應關係
     *
     * @var array
     */
    public $bindings = [
        ServerProvider::class => DigitalOceanServerProvider::class,
    ];

    /**
     * 設定單例模式的容器繫結對應關係
     *
     * @var array
     */
    public $singletons = [
        DowntimeNotifier::class => PingdomDowntimeNotifier::class,
    ];
}

此時,通過 bingdingssingletons 成員變數來設定簡單的繫結,就可以避免大量的「服務提供者」類的生成了。

boot 方法

聊完了 register 方法,接下來進入另一個主題,來研究一下服務提供者的 boot 方法。

通過前面的學習,我們知道在 register 方法中 Laravel 並不能保證所有其他服務已被載入。所以當需要處理具有依賴關係的業務邏輯時,應該將這些邏輯處理放置到 boot 方法內。在 boot 方法中我們可以去完成:註冊事件監聽器、引入路由檔案、註冊過濾器等任何你可以想象得到的業務處理。

config/app.php 配置中我們可以看到如下幾個服務提供者:

        /*
         * Application Service Providers...
         */
        App\Providers\AppServiceProvider::class,
        App\Providers\AuthServiceProvider::class,
        // App\Providers\BroadcastServiceProvider::class,
        App\Providers\EventServiceProvider::class,
        App\Providers\RouteServiceProvider::class,

選擇其中的 App\Providers\RouteServiceProvider::class 服務提供者它繼承自 Illuminate\Foundation\Support\Providers\RouteServiceProvider 基類來看下:

// 實現類
class RouteServiceProvider extends ServiceProvider
{
    /**
     * This namespace is applied to your controller routes. In addition, it is set as the URL generator's root namespace.
     */
    protected $namespace = 'App\Http\Controllers';

    /**
     * Define your route model bindings, pattern filters, etc.
     */
    public function boot()
    {
        parent::boot();
    }

    /**
     * Define the routes for the application. 定義應用路由
     */
    public function map()
    {
        $this->mapApiRoutes();
        $this->mapWebRoutes();
    }

    /**
     * Define the "web" routes for the application. These routes all receive session state, CSRF protection, etc.
     * 定義 web 路由。web 路由支援會話狀態和 CSRF 防禦中介軟體等。
     */
    protected function mapWebRoutes()
    {
        Route::middleware('web')
             ->namespace($this->namespace)
             ->group(base_path('routes/web.php'));
    }

    /**
     * Define the "api" routes for the application. These routes are typically stateless.
     * 定義 api 路由。api 介面路由支援典型的  HTTP 無狀態協議。
     */
    protected function mapApiRoutes()
    {
        Route::prefix('api')
             ->middleware('api')
             ->namespace($this->namespace)
             ->group(base_path('routes/api.php'));
    }
}

基類 Illuminate\Foundation\Support\Providers\RouteServiceProvider

//  基類
namespace Illuminate\Foundation\Support\Providers;

/**
 * @mixin \Illuminate\Routing\Router
 */
class RouteServiceProvider extends ServiceProvider
{
    /**
     * The controller namespace for the application.
     */
    protected $namespace;

    /**
     * Bootstrap any application services. 引導啟動服務
     */
    public function boot()
    {
        $this->setRootControllerNamespace();

        // 如果已快取路由,從快取檔案中載入路由
        if ($this->app->routesAreCached()) {
            $this->loadCachedRoutes();
        } else {
            //還沒有路由快取,載入路由
            $this->loadRoutes();

            $this->app->booted(function () {
                $this->app['router']->getRoutes()->refreshNameLookups();
                $this->app['router']->getRoutes()->refreshActionLookups();
            });
        }
    }

    /**
     * Load the application routes. 載入應用路由,呼叫例項的 map 方法,該方法定義在 App\Providers\RouteServiceProvider::class 中。
     */
    protected function loadRoutes()
    {
        if (method_exists($this, 'map')) {
            $this->app->call([$this, 'map']);
        }
    }
}

對於 RouteServiceProvider 來講,它的 boot 方法在處理一個路由載入的問題:

  • 判斷是否已有路由快取;
  • 有路由快取,則直接載入路由快取;
  • 無路由快取,執行 map 方法載入路由。

感興趣的朋友可以自行了解下 Application Service Providers 配置節點的相關服務提供者,這邊不再贅述。

配置服務提供者

瞭解完「服務提供者」兩個重要方法後,我們還需要知道 Laravel 是如何查詢到所有的服務提供者的。這個超找的過程就是去讀取 config/app.php 檔案中的 providers 節點內所有的「服務提供器」。

具體的讀取過程我們也會在「服務提供者啟動原理」一節中講解。

延遲繫結服務提供者

對於一個專案來說,除了要讓它跑起來,往往我們還需要關注它的效能問題。

當我們開啟 config/app.php 配置檔案時,你會發現有配置很多服務提供者,難道所有的都需要去執行它的 registerboot 方法麼?

對於不會每次使用的服務提供者很明顯,無需每次註冊和啟動,直到需要用到它的時候。

為了解決這個問題 Laravel 內建支援 延遲服務提供者 功能,啟用時延遲功能後,當它真正需要註冊繫結時才會執行 register 方法,這樣就可以提升我們服務的效能了。

啟用「延遲服務提供者」功能,需要完成兩個操作配置:

  1. 在對應服務提供者中將 defer 屬性設定為 true
  2. 並定義 provides 方法,方法返回在提供者 register 方法內需要註冊的服務介面名稱。

我們拿 config/app.php 配置中的 BroadcastServiceProvider 作為演示說明:

<?php

class BroadcastServiceProvider extends ServiceProvider
{
    /**
     * Indicates if loading of the provider is deferred. 標識提供者是否為延遲載入型別。
     *
     * @var bool
     */
    protected $defer = true;

    public function register()
    {
        $this->app->singleton(BroadcastManager::class, function ($app) {
            return new BroadcastManager($app);
        });

        $this->app->singleton(BroadcasterContract::class, function ($app) {
            return $app->make(BroadcastManager::class)->connection();
        });

        $this->app->alias(
            BroadcastManager::class, BroadcastingFactory::class
        );
    }

    /**
     * Get the services provided by the provider. 獲取提供者所提供的服務介面名稱。
     */
    public function provides()
    {
        return [
            BroadcastManager::class,
            BroadcastingFactory::class,
            BroadcasterContract::class,
        ];
    }
}

小結

在「服務提供者入門」這個小節我們學習了服務提供者的基本使用和效能優化相關知識,包括:

  • 如何建立自定義的服務提供者;
  • 建立 register 方法註冊服務到 Laravel 服務容器;
  • 建立 boot 方法啟動服務提供者的載入程式;
  • 配置我們的服務提供者到 config/app.php 檔案,這樣才能在容器中載入相應服務;
  • 通過延遲繫結技術,提升 Laravel 服務效能。

下一小節,我們將焦點轉移到「服務提供者」的實現原理中,深入到 Laravel 核心中去探索「服務提供者」如何被註冊和啟動,又是如何能夠通過延遲技術提升 Laravel 應用的效能的。

服務提供者啟動原理

之前我們有學習 深度挖掘 Laravel 生命週期深入剖析 Laravel 服務容器,今天我們將學習「服務提供者」。

Laravel 的所有核心服務都是通過服務提供者進行引導啟動的,所以想深入瞭解 Laravel 那麼研究「服務提供者」的原理是個繞不開的話題。

載入程式的啟動流程

服務提供者 註冊引導啟動 直到處理 HTTP 請求階段才開始。所以我們直接進入到 App\Console\Kernel::class 類,同時這個類繼承於 Illuminate\Foundation\Http\Kernel 類。

Illuminate\Foundation\Http\Kernel 類中我們可以看到如下內容:

class Kernel implements KernelContract
{
    ...

    /**
     * The bootstrap classes for the application. 應用引導類
     */
    protected $bootstrappers = [
        ...
        \Illuminate\Foundation\Bootstrap\RegisterProviders::class, // 用於註冊(register)「服務提供者」的引導類
        \Illuminate\Foundation\Bootstrap\BootProviders::class, // 用於啟動(boot)「服務提供者」的引導類
    ];

    /**
     * Handle an incoming HTTP request. 處理 HTTP 請求
     */
    public function handle($request)
    {
        try {
            $request->enableHttpMethodParameterOverride();

            $response = $this->sendRequestThroughRouter($request);
        } catch (Exception $e) {
            ...
        } catch (Throwable $e) {
            ...
        }

        ...
    }

    /**
     * Send the given request through the middleware / router. 對 HTTP 請求執行中介軟體處理後再傳送到指定路由。
     */
    protected function sendRequestThroughRouter($request)
    {
        ...

        // 1. 引導類引導啟動。
        $this->bootstrap();

        // 2. 中介軟體及請求處理,生成響應並返回響應。
        return (new Pipeline($this->app))
                    ->send($request)
                    ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                    ->then($this->dispatchToRouter());
    }

    /**
     * Bootstrap the application for HTTP requests. 接收 HTTP 請求時啟動應用載入程式。
     */
    public function bootstrap()
    {
        // 引導類啟動由 Application 容器引導啟動。
        if (! $this->app->hasBeenBootstrapped()) {
            $this->app->bootstrapWith($this->bootstrappers());
        }
    }
}

Illuminate\Foundation\Http\Kernel 我們的核心處理 HTTP 請求時會經過一下兩個主要步驟:

  1. 啟動載入程式通過 $this->bootstrap() 方法完成,其中包括所有服務提供者的註冊和引導處理;
  2. 處理 HTTP 請求(這個問題涉及到中介軟體、路由及相應處理,本文將不做深入探討)。

進入 Illuminate\Foundation\Application 容器中的 bootstrapWith() 方法,來看看容器是如何將引導類引導啟動的:

    /**
     * Run the given array of bootstrap classes. 執行給定載入程式
     */
    public function bootstrapWith(array $bootstrappers)
    {
        $this->hasBeenBootstrapped = true;

        foreach ($bootstrappers as $bootstrapper) {
            $this['events']->fire('bootstrapping: '.$bootstrapper, [$this]);

            // 從容器中解析出例項,然後呼叫例項的 bootstrap() 方法引導啟動。 
            $this->make($bootstrapper)->bootstrap($this);

            $this['events']->fire('bootstrapped: '.$bootstrapper, [$this]);
        }
    }

通過服務容器的 bootstrap() 方法引導啟動時,將定義的在 Illuminate\Foundation\Http\Kerne 類中的應用引導類($bootstrappers)交由 Application 服務容器引導啟動。其中與「服務提供者」有關的引導類為:

Illuminate\Foundation\Http\Kerne HTTP 核心通過 bootstrap() 方法引導啟動時,實際由服務容器(Application)去完成引導啟動的工作,並依據定義在 HTTP 核心中的引導類屬性配置順序依次引導啟動,最終「服務提供者」的啟動順序是:

  • 執行「服務提供者」register 方法的引導類:\Illuminate\Foundation\Bootstrap\RegisterProviders::class,將完成所有定義在 config/app.php 配置中的服務提供者的註冊(register)處理;
  • 執行「服務提供者」boot 方法的引導類:\Illuminate\Foundation\Bootstrap\BootProviders::class,將完成所有定義在 config/app.php 配置中的服務提供者的啟動(boot)處理。

Laravel 執行服務提供者註冊(register)處理

前面說過「服務提供者」的註冊由 \Illuminate\Foundation\Bootstrap\RegisterProviders::class 引導類啟動方法(botstrap())完成。

1. RegisterProviders 引導註冊
<?php
class RegisterProviders
{
    /**
     * Bootstrap the given application.
     */
    public function bootstrap(Application $app)
    {
        $app->registerConfiguredProviders();
    }
}

在其通過呼叫服務容器的 registerConfiguredProviders() 方法完成引導啟動,所以我們需要到容器中一探究竟。

2. 由服務容器執行配置檔案中的所有服務提供者服務完成註冊。
    /**
     * Register all of the configured providers. 執行所有配置服務提供者完成註冊處理。
     * 
     * @see https://github.com/laravel/framework/blob/5.6/src/Illuminate/Foundation/Application.php
     */
    public function registerConfiguredProviders()
    {
        $providers = Collection::make($this->config['app.providers'])
                        ->partition(function ($provider) {
                            return Str::startsWith($provider, 'Illuminate\\');
                        });

        $providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]);

        // 通過服務提供者倉庫(ProviderRepository)載入所有的提供者。
        (new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
                    ->load($providers->collapse()->toArray());
    }
3. 最後由服務提供者倉庫(ProviderRepository)執行服務提供者的註冊處理。
<?php

namespace Illuminate\Foundation;

use Exception;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Contracts\Foundation\Application as ApplicationContract;

class ProviderRepository
{
    ...

    /**
     * Register the application service providers. 註冊應用的服務提供者。
     *
     * @see https://github.com/laravel/framework/blob/5.6/src/Illuminate/Foundation/ProviderRepository.php
     */
    public function load(array $providers)
    {
        $manifest = $this->loadManifest();

        // 首先從服務提供者的快取清單檔案中載入服務提供者集合。其中包含「延遲載入」的服務提供者。
        if ($this->shouldRecompile($manifest, $providers)) {
            $manifest = $this->compileManifest($providers);
        }

        // Next, we will register events to load the providers for each of the events
        // that it has requested. This allows the service provider to defer itself
        // while still getting automatically loaded when a certain event occurs.
        foreach ($manifest['when'] as $provider => $events) {
            $this->registerLoadEvents($provider, $events);
        }

        // 到這裡,先執行應用必要(貪婪)的服務提供者完成服務註冊。
        foreach ($manifest['eager'] as $provider) {
            $this->app->register($provider);
        }

        // 最後將所有「延遲載入服務提供者」加入到容器中。
        $this->app->addDeferredServices($manifest['deferred']);
    }

    /**
     * Compile the application service manifest file. 將服務提供者編譯到清單檔案中快取起來。
     */
    protected function compileManifest($providers)
    {
        // The service manifest should contain a list of all of the providers for
        // the application so we can compare it on each request to the service
        // and determine if the manifest should be recompiled or is current.
        $manifest = $this->freshManifest($providers);

        foreach ($providers as $provider) {
            // 解析出 $provider 對應的例項
            $instance = $this->createProvider($provider);

            // 判斷當前服務提供者是否為「延遲載入」類行的,是則將其加入到快取檔案的「延遲載入(deferred)」集合中。
            if ($instance->isDeferred()) {
                foreach ($instance->provides() as $service) {
                    $manifest['deferred'][$service] = $provider;
                }

                $manifest['when'][$provider] = $instance->when();
            }

            // 如果不是「延遲載入」型別的服務提供者,則為貪婪載入必須立即去執行註冊方法。
            else {
                $manifest['eager'][] = $provider;
            }
        }

        // 將歸類後的服務提供者寫入清單檔案。
        return $this->writeManifest($manifest);
    }

服務提供者倉庫(ProviderRepository) 處理程式中依次執行如下處理:

    1. 如果存在服務提供者快取清單,則直接讀取「服務提供者」集合;
    1. 否則,將從 config/app.php 配置中的服務提供者編譯到快取清單中;
      • 2.1. 這個處理由 compileManifest() 方法完成;
      • 2.2. 編譯快取清單時將處理貪婪載入(eager)和延遲載入(deferred)的服務提供者;
    1. 對於貪婪載入的提供者直接執行服務容器的 register 方法完成服務註冊;
    1. 將延遲載入提供者加入到服務容器中,按需註冊和引導啟動。

最後通過 Illuminate\Foundation\Application 容器完成註冊處理:

    /**
     * Register a service provider with the application. 在應用服務容器中註冊一個服務提供者。
     */
    public function register($provider, $options = [], $force = false)
    {
        if (($registered = $this->getProvider($provider)) && ! $force) {
            return $registered;
        }

        // 如果給定的服務提供者是介面名稱,解析出它的例項。
        if (is_string($provider)) {
            $provider = $this->resolveProvider($provider);
        }

        // 服務提供者提供註冊方法時,執行註冊服務處理
        if (method_exists($provider, 'register')) {
            $provider->register();
        }

        $this->markAsRegistered($provider);

        // 判斷 Laravel 應用是否已啟動。已啟動的話需要去執行啟動處理。
        if ($this->booted) {
            $this->bootProvider($provider);
        }

        return $provider;
    }

為什麼需要判斷是否已經啟動過呢?

因為對於延遲載入的服務提供者只有在使用時才會被呼叫,所以這裡需要這樣判斷,然後再去啟動它。

以上,便是

Laravel 執行服務提供者啟動(boot)處理

「服務提供者」的啟動流程和註冊流程大致相同,有興趣的朋友可以深入原始碼瞭解一下。

1. BootProviders 引導啟動
<?php

namespace Illuminate\Foundation\Bootstrap;

use Illuminate\Contracts\Foundation\Application;

class BootProviders
{
    /**
     * Bootstrap the given application.
     *
     * @see https://github.com/laravel/framework/blob/5.6/src/Illuminate/Foundation/Bootstrap/BootProviders.php
     */
    public function bootstrap(Application $app)
    {
        $app->boot();
    }
}
2. 由服務容器執行配置檔案中的所有服務提供者服務完成啟動。
    /**
     * Boot the application's service providers. 引導啟動應用所有服務提供者
     *
     * @see https://github.com/laravel/framework/blob/5.6/src/Illuminate/Foundation/Application.php
     */
    public function boot()
    {
        if ($this->booted) {
            return;
        }

        // Once the application has booted we will also fire some "booted" callbacks
        // for any listeners that need to do work after this initial booting gets
        // finished. This is useful when ordering the boot-up processes we run.
        $this->fireAppCallbacks($this->bootingCallbacks);

        // 遍歷並執行服務提供者的 boot 方法。
        array_walk($this->serviceProviders, function ($p) {
            $this->bootProvider($p);
        });

        $this->booted = true;

        $this->fireAppCallbacks($this->bootedCallbacks);
    }

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

以上便是服務提供者執行 註冊繫結服務引導啟動 的相關實現。

但是稍等一下,我們是不是忘記了還有「延遲載入」型別的服務提供者,它們還沒有被註冊和引導啟動呢!

Laravel 如何完成延遲載入型別的服務提供者

對於延遲載入型別的服務提供者,我們要到使用時才會去執行它們內部的 registerboot 方法。這裡我們所說的使用即使需要 解析 它,我們知道解析處理由服務容器完成。

所以我們需要進入到 Illuminate\Foundation\Application 容器中探索 make 解析的一些細節。

    /**
     * Resolve the given type from the container. 從容器中解析出給定服務
     * 
     * @see https://github.com/laravel/framework/blob/5.6/src/Illuminate/Foundation/Application.php
     */
    public function make($abstract, array $parameters = [])
    {
        $abstract = $this->getAlias($abstract);

        // 判斷這個介面是否為延遲型別的並且沒有被解析過,是則去將它載入到容器中。
        if (isset($this->deferredServices[$abstract]) && ! isset($this->instances[$abstract])) {
            $this->loadDeferredProvider($abstract);
        }

        return parent::make($abstract, $parameters);
    }

    /**
     * Load the provider for a deferred service. 載入給定延遲載入服務提供者
     */
    public function loadDeferredProvider($service)
    {
        if (! isset($this->deferredServices[$service])) {
            return;
        }

        $provider = $this->deferredServices[$service];

        // 如果服務為註冊則去註冊並從延遲服務提供者集合中刪除它。
        if (! isset($this->loadedProviders[$provider])) {
            $this->registerDeferredProvider($provider, $service);
        }
    }

    /**
     * Register a deferred provider and service. 去執行服務提供者的註冊方法。
     */
    public function registerDeferredProvider($provider, $service = null)
    {
        // Once the provider that provides the deferred service has been registered we
        // will remove it from our local list of the deferred services with related
        // providers so that this container does not try to resolve it out again.
        if ($service) {
            unset($this->deferredServices[$service]);
        }

        // 執行服務提供者註冊服務。
        $this->register($instance = new $provider($this));

        // 執行服務提供者啟動服務。
        if (! $this->booted) {
            $this->booting(function () use ($instance) {
                $this->bootProvider($instance);
            });
        }
    }

總結

今天我們深入研究了 Laravel 服務提供者的註冊和啟動的實現原理,希望對大家有所幫助。

如果對如何自定義服務提供者不甚瞭解的朋友可以去閱讀 Laravel 服務提供者指南 這篇文章。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章