* * * * *
[TOC]
## 簡介
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](Eloquent 入門.md)?來從數據庫中獲取 user 信息。因為?`UserRepository`?是通過注入獲取,所以我們可以容易地切換為其他實現。當測試應用程序時,我們還可以輕松地 「mock」 ,或創建假的?`UserRepository`?實例。
在構建強大的應用程序,和為 Laravel 核心貢獻代碼時,必須深入理解 Laravel 的服務容器。
## 綁定
### 綁定基礎
幾乎所有服務容器的綁定都是在?[服務提供者](服務提供者.md)?中進行的,所以下面的例子將示范在該情景中使用容器。
> {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);
~~~
### 綁定接口至實現
服務容器有一個強大的功能,就是將一個指定接口的實現綁定到接口上。例如,如果我們有一個?`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;
}
~~~
### 情境綁定
有時候,你可能有兩個類使用到相同的接口,但你希望每個類都能注入不同的實現。例如,兩個控制器可能需要依賴不同的?`Illuminate\Contracts\Filesystem\Filesystem`?[契約](Contracts.md)?的實現類。 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');
});
~~~
### 標記
有時候,你可能需要解析某個「分類」下的所有綁定。例如,你正在構建一個報表的聚合器,它需要接受不同?`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'));
});
~~~
## 解析
#### `make`?方法
你可以在服務容器外使用?`make`?方法來獲得一個實例化的類。它接受你希望解析的類或是接口名稱作為參數:
~~~
$api = $this->app->make('HelpSpot\API');
~~~
如果你的代碼不能直接使用?`$app`?變量,你可以使用全局的?`resolve`?助手:
~~~
$api = resolve('HelpSpot\API');
~~~
#### 自動注入
另外,并且也是重要的,你可以在類的構造函數中對依賴使用「類型提示」,依賴的類將會被容器自動進行解析,包括在?[控制器](控制器.md)?,?[事件監聽器](事件系統.md)?,?[隊列任務](隊列.md)?,?[中間件](中間件.md)?等地方。 事實上,這也是大部分類被容器解析的方式。
例如,你可以在控制器的構造函數中對應用程序定義的?`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)
{
//
}
}
~~~
## 容器事件
每當服務容器解析一個對象時就會觸發一個事件。你可以使用?`resolving`?方法監聽這個事件:
~~~
$this->app->resolving(function ($object, $app) {
// 解析任何類型的對象時都會調用該方法...
});
$this->app->resolving(HelpSpot\API::class, function ($api, $app) {
// 解析「HelpSpot\API」類型的對象時調用...
});
~~~
如你所見,被解析的對象會被傳遞至回調中,讓你在對象被傳遞到消費者前可以設置任何額外屬性到對象上。
- 前言
- 翻譯說明
- 發行說明
- 升級說明
- 貢獻導引
- 入門指南
- 安裝
- 配置信息
- 文件夾結構
- 請求周期
- 開發環境部署
- Homestead
- Valet
- 核心概念
- 服務容器
- 服務提供者
- Facades
- Contracts
- HTTP層
- 路由
- 中間件
- CSRF 保護
- 控制器
- 請求
- 響應
- 視圖
- Session
- 表單驗證
- 前端
- Blade 模板
- 本地化
- 前端指南
- 編輯資源 Mix
- 安全
- 用戶認證
- Passport OAuth 認證
- 用戶授權
- 加密解密
- 哈希
- 重置密碼
- 綜合話題
- Artisan 命令行
- 廣播系統
- 緩存系統
- 集合
- 錯誤與日志
- 事件系統
- 文件存儲
- 輔助函數
- 郵件發送
- 消息通知
- 擴展包開發
- 隊列
- 任務調度
- 數據庫
- 快速入門
- 查詢構造器
- 分頁
- 數據庫遷移
- 數據填充
- Redis
- Eloquent ORM
- 快速入門
- 模型關聯
- Eloquent 集合
- 修改器
- 序列化
- 測試
- 快速入門
- HTTP 測試
- 瀏覽器測試 Dusk
- 數據庫測試
- 測試模擬器
- 官方擴展包
- Cashier 交易工具包
- Envoy 部署工具
- Scout 全文搜索
- Socialite 社會化登錄