* * * * *
[TOC]
## 簡介
Laravel 服務容器是用于管理類的依賴和執行依賴注入的工具。依賴注入這個花俏名詞實質上是指:類的依賴項通過構造函數,或者某些情況下通過「setter」方法「注入」到類中。
來看一個簡單的例子:
~~~
<?php
namespace App\Http\Controllers;
use App\User;
use App\Repositories\UserRepository;
use App\Http\Controllers\Controller;
class UserController extends Controller
{
/**
* 用戶存儲庫的實現。
*
* @var UserRepository
*/
protected $users;
/**
* 創建新的控制器實例。
*
* @param UserRepository $users
* @return void
*/
public function __construct(UserRepository $users)
{
$this->users = $users;
}
/**
* 顯示指定用戶的 profile。
*
* @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](http://www.hmoore.net/tonyyu/laravel_5_6/786272)?從數據庫中獲取 user 信息。因為 Repository 是通過?`UserRepository`?注入的,所以我們可以輕易地將其切換為另一個實現。這種注入方式的便利之處還體現在當我們為應用編寫測試時,我們還可以輕松地「模擬」或創建?`UserRepository`?的虛擬實現。
想要構建強大的大型應用,至關重要的一件事是:要深刻的理解 Laravel 服務容器。當然,為 Laravel 的核心代碼做出貢獻也一樣。
## 綁定
### 綁定基礎
因為幾乎所有服務容器綁定操作都是在?[服務提供器](http://www.hmoore.net/tonyyu/laravel_5_6/786057)?中注冊的,所以文檔中大多數例子都是使用了在服務提供器中綁定的容器。
> {tip} 如果類沒有依賴任何接口,就沒有必要將類綁定到容器中。容器不需要指定如何構建這些對象,因為它可以使用反射自動解析這些對象。
#### 簡單綁定
在服務提供器中,你可以通過?`$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`?[契約](http://www.hmoore.net/tonyyu/laravel_5_6/786059)?實現。 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'));
});
~~~
### 擴展綁定
`extend`?方法可以修改解析的服務。例如,當一個服務被解析后,你可以添加額外的代碼去修飾或配置這個服務。?`extend`?方法接受一個閉包,閉包的唯一參數和返回值都是一個服務:
~~~
$this->app->extend(Service::class, function($service) {
return new DecoratedService($service);
});
~~~
## 解析實例
#### `make`?方法
你可以使用?`make`?方法將容器中的類實例解析出來。`make`?方法接受要解析的類或接口的名稱:
~~~
$api = $this->app->make('HelpSpot\API');
~~~
如果你的代碼處于不能訪問?`$app`?變量的位置,你可以使用全局的輔助函數?`resolve`:
~~~
$api = resolve('HelpSpot\API');
~~~
如果你的某些類的依賴項不能通過容器去解析,那你可以通過將它們作為關聯數組傳遞到?`makeWith`?方法來注入它們。
~~~
$api = $this->app->makeWith('HelpSpot\API', ['id' => 1]);
~~~
#### 自動注入
你可以簡單地使用「類型提示」的方式在由容器解析的類的構造函數中添加依賴項,包括?[控制器](http://www.hmoore.net/tonyyu/laravel_5_6/786156)、[監聽事件](http://www.hmoore.net/tonyyu/laravel_5_6/786242)、[隊列任務](http://www.hmoore.net/tonyyu/laravel_5_6/786248)、[中間件](http://www.hmoore.net/tonyyu/laravel_5_6/786089)?等。 事實上,這是你的大多數對象也應該由容器解析。
例如,你可以在控制器的構造函數中對應用程序定義的 Repository 使用類型提示。Repository 會被自動解析并注入到類中:
~~~
<?php
namespace App\Http\Controllers;
use App\Users\Repository as UserRepository;
class UserController extends Controller
{
/**
* UserRepository 的實例對象
*/
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](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-11-container.md)?接口。 因此,你可以使用 PSR-11容器『接口類型提示』來獲取 Laravel 容器的實例:
~~~
use Psr\Container\ContainerInterface;
Route::get('/', function (ContainerInterface $container) {
$service = $container->get('Service');
//
});
~~~
> {note} 如果標簽沒有明確綁定到容器中,那么調用?`get`?方法時會拋出異常。
- 前言
- 翻譯說明
- 發行說明
- 升級指南
- 貢獻導引
- 入門指南
- 安裝
- 配置信息
- 文件夾結構
- Homestead
- Valet
- 部署
- 核心架構
- 請求周期
- 服務容器
- 服務提供者
- Facades
- Contracts
- 基礎功能
- 路由
- 中間件
- CSRF 保護
- 控制器
- 請求
- 響應
- 視圖
- URL
- Session
- 表單驗證
- 錯誤
- 日志
- 前端開發
- Blade 模板
- 本地化
- 前端指南
- 編輯資源 Mix
- 安全相關
- 用戶認證
- Passport OAuth 認證
- 用戶授權
- 加密解密
- 哈希
- 重置密碼
- 綜合話題
- Artisan 命令行
- 廣播系統
- 緩存系統
- 集合
- 事件系統
- 文件存儲
- 輔助函數
- 郵件發送
- 消息通知
- 擴展包開發
- 隊列
- 任務調度
- 數據庫
- 快速入門
- 查詢構造器
- 分頁
- 數據庫遷移
- 數據填充
- Redis
- Eloquent ORM
- 快速入門
- 模型關聯
- Eloquent 集合
- 修改器
- API 資源
- 序列化
- 測試相關
- 快速入門
- HTTP 測試
- 瀏覽器測試 Dusk
- 數據庫測試
- 測試模擬器
- 官方擴展包
- Cashier 交易工具包
- Envoy 部署工具
- Horizon
- Scout 全文搜索
- Socialite 社會化登錄