--- tags: laravel --- # 服務供應器 ![](https://i.imgur.com/2I6x30L.jpg) ## 介紹 服務供應器是所有 Laravel 應用的引導中心。你寫的程式,以及 Laravel 的核心服務都是通過服務供應器所引導 但是文中常說的「引導」是什麼意思呢? 通常,可以被理解為註冊,比如註冊服務容器綁定,事件監聽器,中介層,甚至是路由。服務供應器是設置應用程序的中心 當你打開 Laravel 的 config/app.php 文件時,你會看到 providers 陣列,陣列中的這些供應器類別全部都會被應用程序所加載。預設情況下,有一組 Laravel核心供應器會被列在這裏頭 這些供應器引導 Laravel 的核心單元,比如 郵件mailer .隊列queue. 快取cache以及其他的 當然,其中有很多屬於「延遲」供應器,代表它們並不會在每次請求時都加載,只有在該服務實際被需要時才加載 本篇你將會學到如何寫自己的服務供應器,並將其註冊到你的 Laravel 應用中 假如你想要學習更多關於 Laravel是如何處理請求與內部處理流程,請看 [Laravel請求的生命週期](/YM2-G97dS8uTbJ2UTDwlRA) 章節 ## 撰寫服務供應器 所有的服務供應器都會繼承 Illuminate\Support\ServiceProvider 這個類別,而且 大多數的服務供應器都包含一個 register 和一個 boot 方法。在 register 方法中, 你只需將服務綁定到 服務容器,而不應該在 register 方法中註冊任何監聽器,路由,或者其他任何功能 要生成一個新的服務供應器,可使用 Artisan 命令行工具,透過 make:provider 命令: `php artisan make:provider RiakServiceProvider` ### register 方法 如上所述,在 register 方法裏頭,你只需要將服務綁定到服務容器中。而不應該在 register 方法中註冊任何監聽器,路由,或者其他任何功能。否則,你可能會意外地使用到尚未加載的服務供應器所提供的服務,因此造成錯誤 就讓我們來看一下基礎的服務供應器。在所有的服務供應器方法裏頭,你總是通過 $app 屬性來取得服務容器: ``` <?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 的實作。如果你不太熟悉服務容器的話,請看[服務容器](/tBv0iPqSR1ms0NRsr8-HuQ) 章節 #### bindings 和 singletons 屬性 如果你的服務供應器註冊了許多簡單的綁定,你可能想用 bindings 和 singletons 屬性來取代手動的去註冊每個容器綁定。當服務供應器被框架加載時,將自動檢查這些屬性並註冊相應的綁定: ``` <?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 方法裏頭處理了。 這方法需在所有服務供應器被註冊以後才能被調用,所以我們可以在其中訪問框架已註冊的所有服務: ``` <?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 的核心元件,比如郵件、隊列、快取等等 如要註冊你自己的服務供應器,只需要將其添加到陣列內: ``` 'providers' => [ // Other Service Providers //你的服務供應器 App\Providers\ComposerServiceProvider::class, ], ``` ## 延期供應器 如果你的服務供應器只在服務容器中註冊,可以選擇延遲加載該綁定,直到所綁定的服務真的有需要時再加載它,延遲加載將會提升應用的性能,因為它不會在每次請求時都從檔案系統中加載 Laravel 編譯並保存延遲服務供應器提供的所有服務列表,以及其服務供應器類別的名稱。因此,只有當你在嘗試解析其中一項服務時,Laravel 才會去加載對應的服務供應器 要延遲加載服務供應器,需要實作 \Illuminate\Contracts\Support\DeferrableProvider 介面並定義一個 provides 方法。這個 provides 方法回傳該服務供應器所註冊的服務容器綁定,聽起來有點難懂,看下面的程式碼比較好理解: ``` <?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]; } } ```