Service Provider
簡介
Service Provider 是 Laravel 中負責啟動應用程式的中心點。不過是你自己開發的應用程式,還是 Laravel 的核心服務,都是使用 Service Provider 啟動的。
不過,「啟動」是什麼意思呢?一般來說,我們指的是 註冊 一些東西,包含註冊 Service Container 的繫結、事件監聽常式、Middleware、甚至是 Route。Service Provider 是用來設定應用程式的中心點。
若打開 Laravel 中的 config/app.php
,可以看到一個 providers
陣列。這個陣列中的就是你的程式會載入的所有 Service Provider。預設情況下,這個陣列中列出了一組 Laravel 的核心 Service Provider。這些 Provider 會啟動 Laravel 的核心元件,如:Mailer、佇列 (Queue)⋯⋯等。其中的許多 Provider 是屬於「延遲的」Provider,延遲的 Provider 是指:不是所有 Request 中都會載入這些 Provider,只有在我們有需要這些 Provider 提供的服務時才會載入。
在本篇概覽中,讀者將可以學習到如何撰寫你自己的 Service Provider,並將這些 Provider 註冊到 Laravel 專案上。
若想瞭解 Laravel 如何處理 Request 以及其內部如何運作,請參考我們有關 Laravel Request 的生命週期說明文件。
撰寫 Service Provider
所有的 Service Provider 都繼承自 Illuminate\Support\ServiceProvider
。大多數的 Service Provider 都包含了 registe
與 boot
方法。register
方法 只負責將事物繫結到 Service Container 上。請絕對不要在 register
方法中註冊任何事件監聽常式、Route、或是任何其他的功能。
Artisan CLI 提供了一個 make:provider
指令來新增新 Provider:
1php artisan make:provider RiakServiceProvider
1php artisan make:provider RiakServiceProvider
Register 方法
剛才也提到過,在 register
方法中應只能將東西註冊到 Service Provider 內。絕對不要嘗試在 register
方法內註冊事件監聽常式、Route、或其他任何功能。否則,我們可能會不小心使用到還沒載入的 Service Provider 提供的服務。
來看看一個基礎的 Service Provider。在 Service Provider 中的任何方法都可以存取一個 $app
屬性,該屬性可用來存取 Service Container:
1<?php23namespace App\Providers;45use App\Services\Riak\Connection;6use Illuminate\Contracts\Foundation\Application;7use Illuminate\Support\ServiceProvider;89class RiakServiceProvider extends ServiceProvider10{11 /**12 * Register any application services.13 */14 public function register(): void15 {16 $this->app->singleton(Connection::class, function (Application $app) {17 return new Connection(config('riak'));18 });19 }20}
1<?php23namespace App\Providers;45use App\Services\Riak\Connection;6use Illuminate\Contracts\Foundation\Application;7use Illuminate\Support\ServiceProvider;89class RiakServiceProvider extends ServiceProvider10{11 /**12 * Register any application services.13 */14 public function register(): void15 {16 $this->app->singleton(Connection::class, function (Application $app) {17 return new Connection(config('riak'));18 });19 }20}
這個 Service Provider 只註冊了一個 register
方法,我們使用這個方法來向 Service Container 定義 App\Services\Riak\Connection
的實作。若你不熟悉 Laravel 的 Service Container,請參考 Service Container 的說明文件。
The bindings
and singletons
Properties
若你的 Service Provider 會註冊很多的繫結,則可以使用 bindings
或 singletons
屬性,而不用手動註冊個別的 Container 繫結。Laravel 載入這個 Service Provider 後,會自動檢查這些屬性並註冊這些繫結:
1<?php23namespace App\Providers;45use App\Contracts\DowntimeNotifier;6use App\Contracts\ServerProvider;7use App\Services\DigitalOceanServerProvider;8use App\Services\PingdomDowntimeNotifier;9use App\Services\ServerToolsProvider;10use Illuminate\Support\ServiceProvider;1112class AppServiceProvider extends ServiceProvider13{14 /**15 * All of the container bindings that should be registered.16 *17 * @var array18 */19 public $bindings = [20 ServerProvider::class => DigitalOceanServerProvider::class,21 ];2223 /**24 * All of the container singletons that should be registered.25 *26 * @var array27 */28 public $singletons = [29 DowntimeNotifier::class => PingdomDowntimeNotifier::class,30 ServerProvider::class => ServerToolsProvider::class,31 ];32}
1<?php23namespace App\Providers;45use App\Contracts\DowntimeNotifier;6use App\Contracts\ServerProvider;7use App\Services\DigitalOceanServerProvider;8use App\Services\PingdomDowntimeNotifier;9use App\Services\ServerToolsProvider;10use Illuminate\Support\ServiceProvider;1112class AppServiceProvider extends ServiceProvider13{14 /**15 * All of the container bindings that should be registered.16 *17 * @var array18 */19 public $bindings = [20 ServerProvider::class => DigitalOceanServerProvider::class,21 ];2223 /**24 * All of the container singletons that should be registered.25 *26 * @var array27 */28 public $singletons = [29 DowntimeNotifier::class => PingdomDowntimeNotifier::class,30 ServerProvider::class => ServerToolsProvider::class,31 ];32}
Boot 方法
那麼,若我們想在 Service Provider 內註冊 [View Composer] 該怎麼辦呢?我們可以在 boot
方法中註冊。這個方法會在所有 Service Provider 都註冊好後才被呼叫,這表示,我們就可以存取所有 Laravel 中已註冊好的服務:
1<?php23namespace App\Providers;45use Illuminate\Support\Facades\View;6use Illuminate\Support\ServiceProvider;78class ComposerServiceProvider extends ServiceProvider9{10 /**11 * Bootstrap any application services.12 */13 public function boot(): void14 {15 View::composer('view', function () {16 // ...17 });18 }19}
1<?php23namespace App\Providers;45use Illuminate\Support\Facades\View;6use Illuminate\Support\ServiceProvider;78class ComposerServiceProvider extends ServiceProvider9{10 /**11 * Bootstrap any application services.12 */13 public function boot(): void14 {15 View::composer('view', function () {16 // ...17 });18 }19}
Boot 方法的相依性插入
在 Service Provider 中,若 boot
方法有相依性 (Dependency),我們可以在該方法上做型別提示 (Type-Hint)。Service Container 會自動為你插入所有所需的相依性:
1use Illuminate\Contracts\Routing\ResponseFactory;23/**4 * Bootstrap any application services.5 */6public function boot(ResponseFactory $response): void7{8 $response->macro('serialized', function (mixed $value) {9 // ...10 });11}
1use Illuminate\Contracts\Routing\ResponseFactory;23/**4 * Bootstrap any application services.5 */6public function boot(ResponseFactory $response): void7{8 $response->macro('serialized', function (mixed $value) {9 // ...10 });11}
註冊 Provider
config/app.php
中註冊了所有的 Provider。這個檔案中包含了一個 providers
陣列,我們可以在其中列出所有 Service Provider 的類別名稱。預設情況下,這個陣列中已經註冊了一組 Laravel 的核心 Service Provider。預設的這些 Provider 負責啟動 Laravel 的核心元件,如:Mailer、佇列、快取⋯⋯等。
若要註冊 Provider,請將其加到該陣列中:
1'providers' => ServiceProvider::defaultProviders()->merge([2 // Other Service Providers34 App\Providers\ComposerServiceProvider::class,5])->toArray(),
1'providers' => ServiceProvider::defaultProviders()->merge([2 // Other Service Providers34 App\Providers\ComposerServiceProvider::class,5])->toArray(),
延遲的 Provider
若 Provider 只有 向 Service Container 註冊繫結,則可以選擇將其註冊過程延遲到真正有需要這些繫結時才註冊。由於我們就不需要每個 Request 都從檔案系統中載入這些 Provider,因此延遲載入這類 Provider 可以提升你程式的效能。
Laravel 會編譯並保存延遲的 Service Provider 名稱、以及其所提供的 Service 列表。接著,當有需要解析其中一個 Service 時,Laravel 就會載入這個 Service Provider:
若要延遲載入 Provider,請實作 \Illuminate\Contracts\Support\DeferrableProvider
介面,並定義 provides
方法。provides
方法應回傳該 Provider 中註冊的 Service Container 繫結:
1<?php23namespace App\Providers;45use App\Services\Riak\Connection;6use Illuminate\Contracts\Foundation\Application;7use Illuminate\Contracts\Support\DeferrableProvider;8use Illuminate\Support\ServiceProvider;910class RiakServiceProvider extends ServiceProvider implements DeferrableProvider11{12 /**13 * Register any application services.14 */15 public function register(): void16 {17 $this->app->singleton(Connection::class, function (Application $app) {18 return new Connection($app['config']['riak']);19 });20 }2122 /**23 * Get the services provided by the provider.24 *25 * @return array<int, string>26 */27 public function provides(): array28 {29 return [Connection::class];30 }31}
1<?php23namespace App\Providers;45use App\Services\Riak\Connection;6use Illuminate\Contracts\Foundation\Application;7use Illuminate\Contracts\Support\DeferrableProvider;8use Illuminate\Support\ServiceProvider;910class RiakServiceProvider extends ServiceProvider implements DeferrableProvider11{12 /**13 * Register any application services.14 */15 public function register(): void16 {17 $this->app->singleton(Connection::class, function (Application $app) {18 return new Connection($app['config']['riak']);19 });20 }2122 /**23 * Get the services provided by the provider.24 *25 * @return array<int, string>26 */27 public function provides(): array28 {29 return [Connection::class];30 }31}