### 作為引導者
Laravel 服務提供者主要用來進行注冊服務容器綁定(即注冊接口及其實現類的綁定)。事實上,Laravel 有好幾十個服務提供者,用于管理框架核心組件的容器綁定。幾乎框架里每一個組件的容器綁定都是靠服務提供者來完成的。你可以在 `config/app.php` 這個配置文件里查看項目目前有哪些服務提供者(從 Laravel 5.5 開始,Laravel 提供了包自動發現功能,所以這里也不一定全了)。
一個服務提供者必須至少有一個 `register` 方法。你可以在這個方法里將類綁定到容器,就像我們前面做的那樣。當一個請求進入應用,框架啟動時,所有羅列在配置文件里的服務提供者的 `register` 方法就會被調用。這在應用請求生命周期很早的階段就會發生,所以在我們編寫業務邏輯代碼時,所有的服務都已經準備好了。
> `register` Vs. `boot` 方法:永遠不要在 `register` 方法里面使用任何服務。該方法只是用來將對象綁定到服務容器的地方。所有關于綁定類的解析、交互都要在 `boot` 方法(服務提供者的另一個方法)里進行。
一些通過 Composer 安裝的第三方擴展包也會有服務提供者。這些第三方擴展包的安裝說明里一般都會告訴你要在配置文件 `config/app.php` 的 `providers` 數組里注冊其提供的服務提供者(如果支持包自動發現,則不必這么做)。只有注冊了對應的服務提供者,才能使用擴展包提供的服務。
> 包提供者:不是所有的第三方擴展包都需要服務提供者。實際上,任何擴展包都不需要服務提供者,因為服務提供者只是啟動服務組件并讓它們可以立即使用,它們只是一個用來組織啟動代碼和容器綁定的地方。
#### 延遲加載的服務提供者
不是每一個羅列在配置文件 `config/app.php` 的 `providers` 數組里的服務提供者在每次請求時都需要被實例化。這會對性能有影響,尤其是服務提供者注冊的服務在這個請求中根本用不到的情況下。例如,`QueueServiceProvider` 注冊的服務就不是每次請求都用得到,只有在請求用到隊列時才會用到。
在 Laravel 4 中,延遲服務提供者加載是通過存放在 `app/storage/meta` 目錄下的「服務清單」來實現的,清單中羅列了應用的所有服務提供者及其注冊到容器中的名稱。當需要獲取 `queue` 容器綁定時,就會實例化并運行 `QueueServiceProvider`。
在 Laravel 5 中,我們通過一種新的方式來實現延遲加載服務提供者,在需要延遲加載的服務提供者中將屬性 `$defer` 設置為 `true`,并重寫 `providers` 方法即可,在這個方法中,我們會以數組方式返回該服務提供者注冊的服務容器綁定:
```php
<?php
namespace App\Providers;
use Riak\Connection;
use Illuminate\Support\ServiceProvider;
class RiakServiceProvider extends ServiceProvider{
/**
* 服務提供者加是否延遲加載.
*
* @var bool
*/
protected $defer = true;
/**
* 注冊服務提供者
*
* @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];
}
}
```
### 作為管理者
構建一個架構良好的 Laravel 應用的關鍵就是學習使用服務提供者作為管理工具。
我們先來看個例子吧。也許我們的應用正在使用 [Pusher](https://pusher.com/) 通過 WebSocket 推送消息給客戶端。為了將我們的應用和 Pusher 解耦,最好創建一個新的 `EventPusherInterface` 接口和對應的實現類 `PusherEventPusher`,這樣隨著需求變化或應用增長,我們就可以隨時輕松切換 WebSocket 提供商:
```php
interface EventPusherInterface
{
public function push($message, array $data = array());
}
<?php
namespace App\Services;
use App\Contracts\EventPusherInterface;
use App\Contracts\PusherSdkInterface;
class PusherEventPusher implements EventPusherInterface
{
public function __construct(PusherSdkInterface $pusher)
{
$this->pusher = $pusher;
}
public function push($message, array $data = array())
{
// 通過 Pusher SDK 推送消息
}
}
```
接下來,我們創建一個服務提供者 `EventPusherServiceProvider`:
```php
<?php
namespace App\Providers;
use App\Contracts\EventPusherInterface;
use App\Contracts\PusherSdkInterface;
use App\Services\PusherEventPusher;
use Illuminate\Support\ServiceProvider;
use Pusher\Pusher;
class EventPusherServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->singleton(PusherSdkInterface::class, function () {
return new Pusher('app-key', 'secret-key', 'app-id');
});
$this->app->singleton(EventPusherInterface::class, PusherEventPusher::class);
}
}
```
很好!現在我們對事件推送進行了清晰的抽象,同時也有了一個很方便的地方注冊和綁定其他相關對象到容器里。最后,只需要將 `EventPusherServiceProvider` 注冊到 `config/app.php` 配置文件的`providers` 數組就可以了。現在我們就可以將 `EventPusherInterface` 注入到應用代碼里的任何控制器或類中。
> 在綁定接口實現類時使用 `bind` 還是 `singleton` 方法可以這樣來考慮:如果在一次請求生命周期中該類只需要有一個實例,就使用 `singleton`;否則就使用 `bind`。
我們在容器章節里面已經提到過,繼承自 `Illuminate\Support\ServiceProvider` 的服務提供者都有一個 `$app` 屬性,該屬性是一個繼承自 `Container` 類的完整 `Illuminate\Foundation\Application ` 實例。因此,我們可以通過這個 `$app` 屬性調用服務容器中的所有方法,如果你更喜歡用 `App` 門面,也可以這么做實現同樣的功能:
```php
App::singleton(EventPusherInterface::class, PusherEventPusher::class);
```
當然,服務提供者的功能不僅僅局限于注冊特定類型的服務。我們還可以使用它們注冊云存儲服務、數據庫訪問服務、自定義的視圖引擎如 Twig 等等。服務提供者只是應用程序的引導和管理工具,沒什么其他的。
所以,盡情的去創建你自己的服務提供者吧。并不是非要等到發布個什么擴展包才需要服務提供者,對 Laravel 應用而言,它們只是非常好的代碼管理工具而已。你要學會善用它們去引導和管理應用中的各個組件,以便組件之間可以相互組合,協同工作。
### 啟動提供者
在所有服務提供者都注冊以后(`register` 方法調用完),它們就進入了「啟動」狀態。這將會觸發每個服務提供者執行各自的 `boot` 方法。在使用服務提供者時,一種常見的錯誤就是在`register` 方法里面調用其他提供者注冊的服務。由于在某個服務提供者的 `register` 方法里,不能保證所有其他服務都已經被注冊,在該方法里調用別的服務有可能會出現該服務不可用。因此,調用其它服務的代碼應該被定義在服務提供者的 `boot` 方法中。`register` 方法只能用于注冊服務到容器。
在 `boot` 方法中,你想做什么都可以:注冊事件監聽器、引入路由文件、注冊過濾器、或者任何其他你能想到的事。再次強調,使用服務提供者作為管理工具的時候,如果你想將幾個相關的事件監聽器聚合到一起,就將它們放到該服務提供者的 `boot` 方法里。
現在,我們已經學習了依賴注入,以及如何通過服務提供者來組織管理我們的項目。此時此刻,我們已經為構建可維護、可測試、架構良好的 Laravel 應用打下了一個很好的基礎。接下來,我們將探索 Laravel 框架本身是如何使用服務提供者的,并且深究其原理!
> 不要被條條框框束縛。記住,服務提供者并不只是擴展包才能使用。請盡情使用它來組織管理你的應用服務。
### 框架核心
至此,你可能已經注意到,在 `config/app.php` 配置文件里面已經有了很多服務提供者,其中每一個都負責引導框架核心的一部分服務。比如`MigrationServiceProvider` 負責引導用于運行數據庫遷移的類,包括Artisan 遷移命令。`EventServiceProvide` 負責引導和注冊事件調度類。盡管不同的服務提供者有著不同的復雜度,有些比較大,另一些相對較小,但它們都負責引導核心的一部分功能。
> 提升對 Laravel 核心代碼理解的最好方法是去讀核心服務提供者的源碼。如果你對這些服務提供者的功能以及每個服務提供者注冊了什么都很熟悉,那么你將會對Laravel 底層是如何工作的有更加深刻的理解。
大部分核心服務提供者是延遲加載的,這意味著不是每次請求都會加載它們;不過,一些用于引導框架基礎服務的服務提供者是每一次請求都會被加載的,比如 `FilesystemServiceProvide` 和 `ExceptionServiceProvider`。核心服務提供者和應用容器將框架的不同部分聯系起來,形成一個單一的、內聚的整體。這些核心服務提供者就是框架的構建塊。
正如之前提到的那樣,如果你想深入理解框架是如何運行的,請閱讀 Laravel 框架的核心服務提供者的源碼。通讀之后,你將會對框架如何把各部分功能模塊組合在一起,以及每一個服務提供者為應用提供了哪些功能有更加扎實的理解。此外,有了這些更深入的理解,你也可以為更好的 Laravel 生態系統添磚加瓦!