# 簡介
Laravel 服務容器是一個用于管理類依賴和執行依賴注入的強大工具。依賴注入聽上去很花哨,其實質是通過構造函數或者某些情況下通過 setter 方法將類依賴注入到類中。
讓我們看一個簡單的例子:
~~~
<?php
namespace App\Http\Controllers;
use App\User;
use App\Repositories\UserRepository;
use App\Http\Controllers\Controller;
class UserController extends Controller
{
/**
* The user repository implementation.
*
* @var UserRepository
*/
protected $users;
/**
* Create a new controller instance.
*
* @param UserRepository $users
* @return void
*/
public function __construct(UserRepository $users)
{
$this->users = $users;
}
/**
* Show the profile for the given user.
*
* @param int $id
* @return Response
*/
public function show($id)
{
$user = $this->users->find($id);
return view('user.profile', ['user' => $user]);
}
}
~~~
在本例中,`UserController` 需要從數據源獲取用戶,所以,我們注入了一個可以獲取用戶的服務 UserRepository,其扮演的角色類似使用 Eloquent 從數據庫獲取用戶信息。注入 `UserRepository` 后,我們可以在其基礎上封裝其他實現,也可以模擬或者創建一個假的 `UserRepository` 實現用于測試。
深入理解 Laravel 服務容器對于構建功能強大的大型 Laravel 應用而言至關重要,對于貢獻代碼到 Laravel 核心也很有幫助。
# 綁定
## 綁定基礎
幾乎所有的服務容器綁定都是在`服務提供者`中完成。因此本文檔的演示例子用到的容器都是在服務提供者中綁定。
> 注:如果一個類沒有基于任何接口那么就沒有必要將其綁定到容器。容器并不需要被告知如何構建對象,因為它會使用 PHP 的反射服務自動解析出具體的對象。
**簡單的綁定**
在一個服務提供者中,可以通過 `$this->app` 變量訪問容器,然后使用 `bind` 方法注冊一個綁定,該方法需要兩個參數,第一個參數是我們想要注冊的類名或接口名稱,第二個參數是返回類的實例的閉包:
~~~
$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;
/**
* 創建一個新的類實例
*
* @param EventPusher $pusher
* @return void
*/
public function __construct(EventPusher $pusher){
$this->pusher = $pusher;
}
~~~
## 上下文綁定
有時侯我們可能有兩個類使用同一個接口,但我們希望在每個類中注入不同實現,例如,兩個控制器依賴 `Illuminate\Contracts\Filesystem\Filesystem 契約`的不同實現。Laravel 為此定義了簡單、平滑的接口:
~~~
use Illuminate\Support\Facades\Storage;
use App\Http\Controllers\VideoController;
use App\Http\Controllers\PhotoControllers;
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'));
});
~~~
## 擴展綁定
`extend` 方法允許對解析服務進行修改。例如,當服務被解析后,可以運行額外代碼裝飾或配置該服務。extend 方法接收一個閉包來返回修改后的服務:
~~~
$this->app->extend(Service::class, function($service) {
return new DecoratedService($service);
});
~~~
# 解析
## make 方法
有很多方式可以從容器中解析對象,首先,你可以使用 `make` 方法,該方法接收你想要解析的類名或接口名作為參數:
~~~
$fooBar = $this->app->make('HelpSpot\API');
~~~
如果你所在的代碼位置訪問不了 `$app` 變量,可以使用輔助函數`resolve`:
~~~
$api = resolve('HelpSpot\API');
~~~
某些類的依賴不能通過容器來解析,你可以通過關聯數組方式將其傳遞傳遞到 `makeWith` 方法來注入:
~~~
$api = $this->app->makeWith('HelpSpot\API', ['id' => 1]);
~~~
## 自動注入
最后,也是最常用的,你可以簡單的通過在類的構造函數中對依賴進行類型提示來從容器中解析對象,`控制器`、`事件監聽器`、`隊列任務`、`中間件`等都是通過這種方式。在具體實踐中,這是大多數對象從容器中解析的方式。
容器會自動為其解析類注入依賴,例如,你可以在控制器的構造函數中為應用定義的倉庫進行類型提示,該倉庫會自動解析并注入該類:
~~~
<?php
namespace App\Http\Controllers;
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)
{
//
}
}
~~~
## 容器事件
服務容器在每一次解析對象時都會觸發一個事件,可以使用 `resolving` 方法監聽該事件:
~~~
$this->app->resolving(function ($object, $app) {
// Called when container resolves object of any type...
});
$this->app->resolving(HelpSpot\API::class, function ($api, $app) {
// Called when container resolves objects of type "HelpSpot\API"...
});
~~~
正如你所看到的,被解析的對象將會傳遞給回調函數,從而允許你在對象被傳遞給消費者之前為其設置額外屬性。
## PSR-11
Laravel 的服務容器實現了 ## PSR-11 接口。所以,你可以通過類型提示 PSR-11 容器接口來獲取 Laravel 容器的實例:
~~~
use Psr\Container\ContainerInterface;
Route::get('/', function (ContainerInterface $container) {
$service = $container->get('Service');
//
});
~~~
> 注:如果綁定到容器的唯一標識有沖突調用 `get` 方法會拋出異常。
> 注:強烈推薦閱讀[深入理解控制反轉(IoC)和依賴注入(DI)](https://www.wandhi.com/post-865.html)深入理解服務容器和服務提供者的實現原理。
- 序言
- 新版特性
- 快速入門
- 升級指南
- 貢獻指南
- API文檔
- 安裝配置
- 目錄結構
- Homestead
- Valet
- 部署
- 核心概念
- 請求生命周期
- 服務容器
- 服務提供者
- 門面(Facades)
- 契約(Contracts)
- 框架基礎
- 路由
- 中間件
- CSRF 保護
- 控制器
- 請求
- 響應
- 視圖
- 生成 URL
- Session
- 驗證
- 錯誤處理
- 日志
- 前端開發
- Blade 模板
- 本地化
- 前端腳手架
- 編譯前端資源(Laravel Mix)
- 安全系列
- 登錄認證
- API 認證
- 授權
- 加密
- 哈希
- 重置密碼
- 進階系列
- Artisan 控制臺
- 集合
- 廣播
- 緩存
- 事件
- 文件存儲
- 輔助函數
- 郵件
- 通知
- 擴展包開發
- 隊列
- 任務調度
- 數據庫操作
- 快速入門
- 查詢構建器
- 分頁
- 遷移
- 數據填充
- Redis
- Eloquent ORM
- 快速入門
- 關聯關系
- 集合
- 訪問器 & 修改器
- API 資源類
- 序列化
- 應用測試
- 快速入門
- HTTP 測試
- 瀏覽器測試
- 數據庫測試
- 模擬
- 官方擴展包
- Cashier(訂閱支付解決方案)
- Envoy(遠程操作解決方案)
- Horizon(隊列系統解決方案)
- Passport(API 認證解決方案)
- Scout(全文搜索解決方案)
- Socialite(第三方登錄解決方案)
- 相關下載
- Laravel 5.6 中文文檔離線版
- Laravel 5.6 一鍵安裝包