# 架構 —— 服務容器
## 1、簡介
Laravel服務容器是一個用于管理類依賴和執行依賴注入的強大工具。依賴注入聽上去很花哨,其實質是通過構造函數或者某些情況下通過set方法將類依賴注入到類中。
讓我們看一個簡單的例子:
~~~
<?php
namespace App\Jobs;
use App\User;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Contracts\Bus\SelfHandling;
class PurchasePodcast implements SelfHandling{
/**
*?郵件實現
*/
protected $mailer;
/**
*?創建一個新的實例
*
* @param Mailer $mailer
* @return void
*/
public function __construct(Mailer $mailer)
{
$this->mailer = $mailer;
}
/**
*?購買一個播客
*
* @return void
*/
public function handle()
{
//
}
}
~~~
在本例中,當播客被購買后`PurchasePodcast`任務需要發送郵件,因此,你需要注入一個可以發送郵件的服務。由于該服務是被注入的,我們可以方便的使用其另一個實現來替換它,在測試的時候我們還可以”模擬“或創建一個假的郵件實現。
深入理解Laravel服務容器對于構建功能強大的大型Laravel應用而言至關重要,對于貢獻代碼到Laravel核心也很有幫助。
## 2、綁定
幾乎所有的服務容器綁定都是在[服務提供者](http://laravelacademy.org/post/91.html)中完成。因此本章節的演示例子用到的容器都是在這種上下文環境中,如果一個類沒有基于任何接口那么就沒有必要將其綁定到容器。容器并不需要被告知如何構建對象,因為它會使用PHP的反射服務自動解析出具體的對象。
在一個服務提供者中,可以通過`$this->app`變量訪問容器,然后使用bind方法注冊一個綁定,該方法需要兩個參數,第一個參數是我們想要注冊的類名或接口名稱,第二個參數是返回類的實例的閉包:
~~~
$this->app->bind('HelpSpot\API', function ($app) {
return new HelpSpot\API($app['HttpClient']);
});
~~~
注意到我們接受容器本身作為解析器的一個參數,然后我們可以使用該容器來解析我們正在構建的對象的子依賴。
**綁定一個單例**
`singleton`方法綁定一個只需要解析一次的類或接口到容器,然后接下來對容器的調用將會返回同一個實例:
~~~
$this->app->singleton('FooBar', function ($app) {
return new FooBar($app['SomethingElse']);
});
~~~
**綁定實例**
你還可以使用`instance`方法綁定一個已存在的對象實例到容器,隨后對容器的調用將總是返回給定的實例:
~~~
$fooBar = new FooBar(new SomethingElse);
$this->app->instance('FooBar', $fooBar);
~~~
### 2.1 綁定接口到實現
服務容器的一個非常強大的特性是其綁定接口到實現的能力。我們假設有一個`EventPusher`接口及其`RedisEventPusher`實現,編寫完該接口的`RedisEventPusher`實現后,就可以將其注冊到服務容器:
~~~
$this->app->bind('App\Contracts\EventPusher', 'App\Services\RedisEventPusher');
~~~
這段代碼告訴容器當一個類需要`EventPusher`的實現時將會注入`RedisEventPusher`,現在我們可以在構造器或者任何其它通過服務容器注入依賴的地方進行EventPusher接口的類型提示:
~~~
use App\Contracts\EventPusher;
/**
*?創建一個新的類實例
*
* @param EventPusher $pusher
* @return void
*/
public function __construct(EventPusher $pusher){
$this->pusher = $pusher;
}
~~~
### 2.2 上下文綁定
有時侯我們可能有兩個類使用同一個接口,但我們希望在每個類中注入不同實現,例如,當系統接到一個新的訂單的時候,我們想要通過[PubNub](http://www.pubnub.com/)而不是Pusher發送一個事件。Laravel定義了一個簡單、平滑的方式來定義這種行為:
~~~
$this->app->when('App\Handlers\Commands\CreateOrderHandler')
->needs('App\Contracts\EventPusher')
->give('App\Services\PubNubEventPusher');
~~~
你甚至還可以傳遞一個閉包到give方法:
~~~
$this->app->when('App\Handlers\Commands\CreateOrderHandler')
->needs('App\Contracts\EventPusher')
->give(function () {
// Resolve dependency...
});
~~~
### 2.3?標簽
少數情況下我們需要解析特定分類下的所有綁定,比如,也許你正在構建一個接收多個不同`Report`接口實現的報告聚合器,在注冊完`Report`實現之后,可以通過tag方法給它們分配一個標簽:
~~~
$this->app->bind('SpeedReport', function () {
//
});
$this->app->bind('MemoryReport', function () {
//
});
$this->app->tag(['SpeedReport', 'MemoryReport'], 'reports');
~~~
這些服務被打上標簽后,可以通過`tagged`方法來輕松解析它們:
~~~
$this->app->bind('ReportAggregator', function ($app) {
return new ReportAggregator($app->tagged('reports'));
});
~~~
## 3、解析
有很多方式可以從容器中解析對象,首先,你可以使用`make`方法,該方法接收你想要解析的類名或接口名作為參數:
~~~
$fooBar = $this->app->make('FooBar');
~~~
其次,你可以以數組方式訪問容器,因為其實現了PHP的`ArrayAccess`接口:
~~~
$fooBar = $this->app['FooBar'];
~~~
最后,也是最常用的,你可以簡單的通過在類的構造函數中對依賴進行類型提示來從容器中解析對象,包括[控制器](http://laravelacademy.org/post/60.html)、[事件監聽器](http://laravelacademy.org/post/198.html)、[隊列任務](http://laravelacademy.org/post/222.html)、[中間件](http://laravelacademy.org/post/57.html)等都是通過這種方式。在實踐中,這是大多數對象從容器中解析的方式。
容器會自動為其解析類注入依賴,比如,你可以在控制器的構造函數中為應用定義的倉庫進行類型提示,該倉庫會自動解析并注入該類:
~~~
<?php
namespace App\Http\Controllers;
use Illuminate\Routing\Controller;
use App\Users\Repository as UserRepository;
class UserController extends Controller{
/**
*?用戶倉庫實例
*/
protected $users;
/**
*?創建一個控制器實例
*
* @param UserRepository $users
* @return void
*/
public function __construct(UserRepository $users)
{
$this->users = $users;
}
/**
*?通過指定ID顯示用戶
*
* @param int $id
* @return Response
*/
public function show($id)
{
//
}
}
~~~
## 4、容器事件
服務容器在每一次解析對象時都會觸發一個事件,可以使用`resolving`方法監聽該事件:
~~~
$this->app->resolving(function ($object, $app) {
//?容器解析所有類型對象時調用
});
$this->app->resolving(function (FooBar $fooBar, $app) {
//?容器解析“FooBar”對象時調用
});
~~~
正如你所看到的,被解析的對象將會傳遞給回調,從而允許你在對象被傳遞給消費者之前為其設置額外屬性。
> 擴展閱讀:[實例教程——深入理解控制反轉和依賴注入](http://laravelacademy.org/post/769.html)
- 前言
- 序言
- 序言 ―― 發行版本說明
- 序言 ―― 升級指南
- 序言 ―― 貢獻代碼
- 開始
- 開始 ―― 安裝及配置
- 開始 ―― Laravel Homestead
- 基礎
- 基礎 ―― HTTP路由
- 基礎 ―― HTTP 中間件
- 基礎 ―― HTTP 控制器
- 基礎 ―― HTTP 請求
- 基礎 ―― HTTP 響應
- 基礎 ―― 視圖
- 基礎 ―― Blade模板
- 架構
- 架構 ―― 一次請求的生命周期
- 架構 ―― 應用目錄結構
- 架構 ―― 服務提供者
- 架構 ―― 服務容器
- 架構 ―― 契約
- 架構 ―― 門面
- 數據庫
- 數據庫 ―― 起步
- 數據庫 ―― 查詢構建器
- 數據庫 ―― 遷移
- 數據庫 ―― 填充數據
- Eloquent ORM
- Eloquent ORM ―― 起步
- Eloquent ORM ―― 關聯關系
- Eloquent ORM ―― 集合
- Eloquent ORM ―― 調整器
- Eloquent ORM ―― 序列化
- 服務
- 服務 ―― 用戶認證
- 服務 ―― Artisan 控制臺
- 服務 ―― Laravel Cashier(交易)
- 服務 ―― 緩存
- 服務 ―― 集合
- 服務 ―― Laravel Elixir
- 服務 ―― 加密
- 服務 ―― 錯誤&日志
- 服務 ―― 事件
- 服務 ―― 文件系統/云存儲
- 服務 ―― 哈希
- 服務 ―― 幫助函數
- 服務 ―― 本地化
- 服務 ―― 郵件
- 服務 ―― 包開發
- 服務 ―― 分頁
- 服務 ―― 隊列
- 服務 ―― Redis
- 服務 ―― Session
- 服務 ―― Envoy 任務運行器(SSH任務)
- 服務 ―― 任務調度
- 服務 ―― 測試
- 服務 ―― 驗證