15分鐘搞懂Laravel的服務供應器

15分鐘搞懂Laravel的服務供應器

服務供應器是所有 Laravel 應用的引導中心。你寫的程式,以及 Laravel 的核心服務都是通過服務供應器所引導

但是文中常說的「引導」是什麼意思呢? 通常,可以被理解為註冊,比如註冊服務容器綁定,事件監聽器,中介層,甚至是路由。服務供應器是設置應用程序的中心

當你打開 Laravel 的 config/app.php 文件時,你會看到 providers 陣列,陣列中的這些供應器類別全部都會被應用程序所加載。預設情況下,有一組 Laravel 核心供應器會被列在這裏頭

這些供應器引導 Laravel 的核心單元,比如 郵件mailer .隊列 queue. 快取 cache 以及其他的

當然,其中有很多屬於「延遲」供應器,代表它們並不會在每次請求時都加載,只有在該服務實際被需要時才加載

本篇你將會學到如何寫自己的服務供應器,並將其註冊到你的 Laravel 應用中

假如你想要學習更多關於 Laravel 是如何處理請求與內部處理流程,請看 10分鐘搞懂Laravel請求的生命週期

撰寫服務供應器

所有的服務供應器都會繼承 Illuminate\Support\ServiceProvider 這個類別,而且 大多數的服務供應器都包含一個 register 和一個 boot 方法。在 register 方法中, 你只需將服務綁定到服務容器,而不應該在 register 方法中註冊任何監聽器,路由,或者其他任何功能

要生成一個新的服務供應器,可使用 Artisan 命令行工具,透過 make:provider 命令:

php artisan make:provider RiakServiceProvider

register 方法

如上所述,在 register 方法裏頭,你只需要將服務綁定到服務容器中。而不應該在 register 方法中註冊任何監聽器,路由,或者其他任何功能。否則,你可能會意外地使用到尚未加載的服務供應器所提供的服務,因此造成錯誤

就讓我們來看一下基礎的服務供應器。在所有的服務供應器方法裏頭,你總是通過 $app 屬性來取得服務容器:

//app\Providers\RiakServiceProvider.php

namespace App\Providers;

use App\Services\Riak\Connection;
use Illuminate\Support\ServiceProvider;

class RiakServiceProvider extends ServiceProvider
{
    /**
     * 註冊應用服務.
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton(Connection::class, function ($app) {
            return new Connection(config('riak'));
        });
    }
}

這個服務供應器只定義了一個 register 方法,並且使用這個方法在服務容器中定義了一個 App\Services\Riak\Connection 的實作。如果你不太熟悉服務容器的話,請看20分鐘搞懂LARAVEL的服務容器概念 章節

bindings 和 singletons 屬性

如果你的服務供應器註冊了許多簡單的綁定,你可能想用 bindings 和 singletons 屬性來取代手動的去註冊每個容器綁定。當服務供應器被框架加載時,將自動檢查這些屬性並註冊相應的綁定:

//app\Providers\AppServiceProvider.php

namespace App\Providers;

use App\Contracts\DowntimeNotifier;
use App\Contracts\ServerProvider;
use App\Services\DigitalOceanServerProvider;
use App\Services\PingdomDowntimeNotifier;
use App\Services\ServerToolsProvider;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * 所有的容器綁定將會被註冊
     *
     * @var array
     */
    public $bindings = [
        ServerProvider::class => DigitalOceanServerProvider::class,
    ];

    /**
     * 所有的容器單例將會被註冊
     *
     * @var array
     */
    public $singletons = [
        DowntimeNotifier::class => PingdomDowntimeNotifier::class,
        ServerProvider::class => ServerToolsProvider::class,
    ];
}

boot 方法

如果我們要在服務供應器中註冊一個視圖合成器該怎麼做呢? 這就需要在 boot 方法裏頭處理了。 這方法需在所有服務供應器被註冊以後才能被調用,所以我們可以在其中訪問框架已註冊的所有服務:

//app\Providers\ComposerServiceProvider.php

namespace App\Providers;

use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;

class ComposerServiceProvider extends ServiceProvider
{
    /**
     * 引導應用裡的服務
     *
     * @return void
     */
    public function boot()
    {
        View::composer('view', function () {
            //
        });
    }
}
boot 方法的依賴注入

你可以為服務供應器的 boot 方法設置型別提示,服務容器將會自動注入所有你所需要的依賴:

use Illuminate\Contracts\Routing\ResponseFactory;

/**
 * 引導應用裡的服務
 *
 * @param  \Illuminate\Contracts\Routing\ResponseFactory
 * @return void
 */
public function boot(ResponseFactory $response)
{
    $response->macro('serialized', function ($value) {
        //
    });
}

註冊服務供應器

所有服務供應器都是通過設定檔案 config/app.php 來進行註冊。該檔案包含了一個列出所有服務供應器名字的 providers 陣列,預設情況下有一組 Laravel 的核心服務供應器會被列在這,這些服務供應器引導 Laravel 的核心元件,比如郵件、隊列、快取等等

如要註冊你自己的服務供應器,只需要將其添加到陣列內:

//config\app.php

'providers' => [
    // Other Service Providers
    //你的服務供應器
    App\Providers\ComposerServiceProvider::class,
],

延期供應器

如果你的服務供應器只在服務容器中註冊,可以選擇延遲加載該綁定,直到所綁定的服務真的有需要時再加載它,延遲加載將會提升應用的性能,因為它不會在每次請求時都從檔案系統中加載

Laravel 編譯並保存延遲服務供應器提供的所有服務列表,以及其服務供應器類別的名稱。因此,只有當你在嘗試解析其中一項服務時,Laravel 才會去加載對應的服務供應器

要延遲加載服務供應器,需要實作 \Illuminate\Contracts\Support\DeferrableProvider 介面並定義一個 provides 方法。這個 provides 方法回傳該服務供應器所註冊的服務容器綁定,聽起來有點難懂,看下面的程式碼比較好理解:

//app\Providers\RiakServiceProvider.php

namespace App\Providers;

use App\Services\Riak\Connection;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\ServiceProvider;

class RiakServiceProvider extends ServiceProvider implements DeferrableProvider
{
    /**
     * 引導應用裡的服務
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton(Connection::class, function ($app) {
            return new Connection($app['config']['riak']);
        });
    }

    /**
     * 取得供應器的服務
     *
     * @return array
     */
    public function provides()
    {
        return [Connection::class];
    }
}

分享這篇文章:

關聯文章: