# 服務容器
- [簡介](#introduction)
- [綁定](#binding)
- [綁定基礎](#binding-basics)
- [綁定接口至實現](#binding-interfaces-to-implementations)
- [情境綁定](#contextual-binding)
- [標記](#tagging)
- [解析](#resolving)
- [Make 方法](#the-make-method)
- [自動注入](#automatic-injection)
- [容器事件](#container-events)
<a name="introduction"></a>
## 簡介
Laravel 服務容器是管理類依賴和運行依賴注入的有力工具。依賴注入是一個花俏的名詞,它實質上是指:類的依賴通過構造器或在某些情況下通過「setter」方法進行「注入」。
來看一個簡單的例子:
<?php
namespace App\Http\Controllers;
use App\User;
use App\Repositories\UserRepository;
use App\Http\Controllers\Controller;
class UserController extends Controller
{
/**
* User Repository 的實現。
*
* @var UserRepository
*/
protected $users;
/**
* 創建新的控制器實例。
*
* @param UserRepository $users
* @return void
*/
public function __construct(UserRepository $users)
{
$this->users = $users;
}
/**
* 顯示指定用戶的詳細信息。
*
* @param int $id
* @return Response
*/
public function show($id)
{
$user = $this->users->find($id);
return view('user.profile', ['user' => $user]);
}
}
在這個例子中,控制器 `UserController` 需要從數據源中獲取 users 。因此,我們要 **注入** 可以獲取 users 的服務。在這種情況下, `UserRepository` 可能是通過使用 [Eloquent](/docs/{{version}}/eloquent) 來從數據庫中獲取 user 信息。因為 `UserRepository` 是通過注入獲取,所以我們可以容易地切換為其他實現。當測試應用程序時,我們還可以輕松地 「mock」 ,或創建假的 `UserRepository` 實例。
在構建強大的應用程序,和為 Laravel 核心貢獻代碼時,必須深入理解 Laravel 的服務容器。
<a name="binding"></a>
## 綁定
<a name="binding-basics"></a>
### 綁定基礎
幾乎所有服務容器的綁定都是在 [服務提供者](/docs/{{version}}/providers) 中進行的,所以下面的例子將示范在該情景中使用容器。
> {tip} 但是,如果類沒有依賴任何接口,那么就沒有必要將類綁定到容器中了。容器綁定時,并不需要指定如何構建這些類,因為容器中會通過 PHP 的反射自動解析對象。
#### 簡單綁定
在服務提供者中,你經常可以通過 `$this->app` 屬性訪問容器。我們可以通過 `bind` 方法注冊一個綁定,通過傳遞注冊類或接口的名稱、及返回該實例的 `Closure` 作為參數:
$this->app->bind('HelpSpot\API', function ($app) {
return new HelpSpot\API($app->make('HttpClient'));
});
注意,我們將獲得的容器本身作為參數傳遞到解析器中,這樣就可以使用容器來解決綁定對象對容器的子依賴。
#### 綁定一個單例
通過 `singleton` 方法可以綁定一個只會被解析一次的類或接口到容器中。且后面的調用都會從容器中返回相同的實例:
$this->app->singleton('HelpSpot\API', function ($app) {
return new HelpSpot\API($app->make('HttpClient'));
});
#### 綁定實例
你也可以使用 `instance` 方法綁定一個已經存在的對象至容器中。后面的調用都會從容器中返回指定的實例:
$api = new HelpSpot\API(new HttpClient);
$this->app->instance('HelpSpot\Api', $api);
#### 綁定初始數據
有時,你的類不僅需要注入類,還需要注入一些原始數據,如一個整數。此時,你可以容易地通過情景綁定注入需要的任何值:
$this->app->when('App\Http\Controllers\UserController')
->needs('$variableName')
->give($value);
<a name="binding-interfaces-to-implementations"></a>
### 綁定接口至實現
服務容器有一個強大的功能,就是將一個指定接口的實現綁定到接口上。例如,如果我們有一個 `EventPusher` 接口和一個它的實現類 `RedisEventPusher` 。編寫完接口的 `RedisEventPusher` 實現類后,我們就可以在服務容器中像下面例子一樣注冊它:
$this->app->bind(
'App\Contracts\EventPusher',
'App\Services\RedisEventPusher'
);
這么做會告訴容器當一個類需要 `EventPusher` 接口的實例時, `RedisEventPusher` 的實例將會被容器注入。現在我們就可以在構造函數中,或者任何其他需要通過容器注入依賴的地方,使用 `EventPusher` 接口的類型提示:
use App\Contracts\EventPusher;
/**
* Create a new class instance.
*
* @param EventPusher $pusher
* @return void
*/
public function __construct(EventPusher $pusher)
{
$this->pusher = $pusher;
}
<a name="contextual-binding"></a>
### 情境綁定
有時候,你可能有兩個類使用到相同的接口,但你希望每個類都能注入不同的實現。例如,兩個控制器可能需要依賴不同的 `Illuminate\Contracts\Filesystem\Filesystem` [契約](/docs/{{version}}/contracts) 的實現類。 Laravel 為此定義了一種簡單、平滑的接口:
use Illuminate\Support\Facades\Storage;
use App\Http\Controllers\PhotoController;
use App\Http\Controllers\VideoController;
use Illuminate\Contracts\Filesystem\Filesystem;
$this->app->when(PhotoController::class)
->needs(Filesystem::class)
->give(function () {
return Storage::disk('local');
});
$this->app->when(VideoController::class)
->needs(Filesystem::class)
->give(function () {
return Storage::disk('s3');
});
<a name="tagging"></a>
### 標記
有時候,你可能需要解析某個「分類」下的所有綁定。例如,你正在構建一個報表的聚合器,它需要接受不同 `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'));
});
<a name="resolving"></a>
## 解析
<a name="the-make-method"></a>
#### `make` 方法
你可以在服務容器外使用 `make` 方法來獲得一個實例化的類。它接受你希望解析的類或是接口名稱作為參數:
$api = $this->app->make('HelpSpot\API');
如果你的代碼不能直接使用 `$app` 變量,你可以使用全局的 `resolve` 助手:
$api = resolve('HelpSpot\API');
<a name="automatic-injection"></a>
#### 自動注入
另外,并且也是重要的,你可以在類的構造函數中對依賴使用「類型提示」,依賴的類將會被容器自動進行解析,包括在 [控制器](/docs/{{version}}/controllers) , [事件監聽器](/docs/{{version}}/events) , [隊列任務](/docs/{{version}}/queues) , [中間件](/docs/{{version}}/middleware) 等地方。 事實上,這也是大部分類被容器解析的方式。
例如,你可以在控制器的構造函數中對應用程序定義的 `Repository` 使用類型提示。這樣 `Repository` 實例會被自動解析并注入到類中:
<?php
namespace App\Http\Controllers;
use App\Users\Repository as UserRepository;
class UserController extends Controller
{
/**
* user repository 實例。
*/
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)
{
//
}
}
<a name="container-events"></a>
## 容器事件
每當服務容器解析一個對象時就會觸發一個事件。你可以使用 `resolving` 方法監聽這個事件:
$this->app->resolving(function ($object, $app) {
// 解析任何類型的對象時都會調用該方法...
});
$this->app->resolving(HelpSpot\API::class, function ($api, $app) {
// 解析「HelpSpot\API」類型的對象時調用...
});
如你所見,被解析的對象會被傳遞至回調中,讓你在對象被傳遞到消費者前可以設置任何額外屬性到對象上。
## 譯者署名
| 用戶名 | 頭像 | 職能 | 簽名 |
|---|---|---|---|
| [@yangjingqzp](https://phphub.org/users/5742) | <img class="avatar-66 rm-style" src="https://dn-phphub.qbox.me/uploads/avatars/5742_1473308107.jpeg?imageView2/1/w/100/h/100"> | 翻譯 | 大神們請多多指教,[@yangjingqzp](https://github.com/yangjingqzp) at Github |
- 說明
- 翻譯說明
- 發行說明
- 升級說明
- 貢獻導引
- 入門指南
- 安裝
- 配置信息
- 文件夾結構
- 錯誤與日志
- 開發環境
- HomeStead
- Valet
- 核心概念
- 服務容器
- 服務提供者
- 門面(facades)
- contracts
- HTTP層
- 路由
- 中間件
- CSRF保護
- 控制器
- 請求
- 響應
- Session
- 表單驗證
- 視圖與模板
- 視圖
- Blade模板
- 本地化
- Javascript與CSS
- 入門指南
- laravel-elixir
- 安全
- 用戶認證
- 用戶授權
- 重置密碼
- API授權
- 加密解密
- 哈希
- 綜合話題
- 廣播系統
- 緩存系統
- 事件系統
- 文件存儲
- 郵件發送
- 消息通知
- 隊列
- 數據庫
- 快速入門
- 查詢構造器
- 分頁
- 數據庫遷移
- 數據填充
- redis
- Eloquent ORM
- 快速入門
- 模型關聯
- Eloquent集合
- 修改器
- 序列化
- Artisan控制臺
- Artisan 命令行
- 任務調度
- 測試
- 快速入門
- 應用程序測試
- 數據庫測試
- 模擬器
- 官方擴展包
- Cashier交易包
- Envoy 部署工具
- Passport OAuth 認證
- Scout 全文搜索
- Socialite 社交化登錄
- 附錄
- 集合
- 輔助函數
- 擴展包開發
- 交流說明