前陣子看了點Laravel源碼,越看越亂,網(wǎng)上大部分中文文檔都是直譯,比較生澀難懂,還是決定看英文文檔順便就我的理解做下翻譯整理記錄下來

簡介
Laravel的服務(wù)容器是一個管理類依賴和執(zhí)行依賴注入的強大工具。依賴注入是一個簡稱,它的意思是:類依賴通過構(gòu)造函數(shù)或者"setter"方法“注入”到類中。
看一個簡單的例子:
<?php
namespace App\Jobs;
use App\User;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Contracts\Bus\SelfHandling;
class PurchasePodcast implements SelfHandling
{
/**
* The mailer implementation.
*/
protected $mailer;
/**
* Create a new instance.
*
* @param Mailer $mailer
* @return void
*/
public function __construct(Mailer $mailer)
{
$this->mailer = $mailer;
}
/**
* Purchase a podcast.
*
* @return void
*/
public function handle()
{
//
}
}
在這個案例中,PurchasePodcast需要在padcast被購買的時候發(fā)送電子郵件。所以,我們將注入一個可以發(fā)送郵件的服務(wù)。由于服務(wù)是被注入的,我們可以方便的用其他實現(xiàn)來替換。我們也可以在測試我們應(yīng)用的時候輕松模擬,或者創(chuàng)建一個郵件的偽實現(xiàn)。
深入理解Laravel服務(wù)容器是創(chuàng)建一個大型應(yīng)用基礎(chǔ),除此之外對為Laravel核心做貢獻也很有幫助。
綁定
服務(wù)容器的綁定基本上都是在服務(wù)提供者中注冊的,所以所有這些例子都將在這個背景下演示如何使用容器。然而,如果類本身不依賴于任何接口就沒有綁定的必要。容器不需要被告之如何創(chuàng)建這些對象,因為它可以用PHP反射服務(wù)來自動處理這些實例對象。
在一個服務(wù)提供者里,我們始終通過$this->app實例變量來訪問容器。我們可以通過bind方法注冊綁定,傳入兩個參數(shù),一個是我們要注冊的類名或者接口名,還有一個就是返回類實例的閉包。
$this->app->bind('HelpSpot\API', function ($app) {
return new HelpSpot\API($app['HttpClient']);
});
注意我們獲取容器本身作為參數(shù)傳給解析器。然后我們可以用容器獲得要創(chuàng)建的對象的子依賴。
綁定一個單例
singleton方法把類或接口綁定到容器中,它們只能被解析一次,然后之后進入對容器的請求都會返回同樣的實例。
$this->app->singleton('FooBar', function ($app) {
return new FooBar($app['SomethingElse']);
});
綁定實例
你也可以用instance方法把一個存在的對象綁定到容器。之后的請求總是會返回這個實例對象。
$fooBar = new FooBar(new SomethingElse);
$this->app->instance('FooBar', $fooBar);
綁定接口到實現(xiàn)
服務(wù)容器的一個非常強大的特性就是它可以把接口綁定到給定實現(xiàn)。例如,我們假設(shè)我們有一個EventPusher接口和一個RedisEventPusher實現(xiàn)。我們?yōu)檫@個接口編寫完RedisEventPusher實現(xiàn)后,我們可以這樣把它注冊到服務(wù)容器:
$this->app->bind('App\Contracts\EventPusher', 'App\Services\RedisEventPusher');
這就告訴容器當(dāng)一個類需要EventPusher實現(xiàn)的時候,應(yīng)該注入RedisEventPusher?,F(xiàn)在我們在構(gòu)造函數(shù)或者任何地方用類型提示,服務(wù)容器就會在那里把依賴注入進來:
use App\Contracts\EventPusher;
/**
* Create a new class instance.
*
* @param EventPusher $pusher
* @return void
*/
public function __construct(EventPusher $pusher)
{
$this->pusher = $pusher;
}
上下文綁定
有時候你可能有兩個類用到同一個接口,但你想要給它們分別注入不同的實現(xiàn)。例如,
當(dāng)我們系統(tǒng)受到一個新指令,我們可能希望通過PubNub來發(fā)送事件而不是用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...
});
綁定原始值
有時候我們可能有一個類,它獲取了一些被注入的類,但同時需要注入一個原始變量比如Integer,你可以用上下文綁定輕松注入任何你類需要的值。
$this->app->when('App\Handlers\Commands\CreateOrderHandler')
->needs('$maxOrderCount')
->give(10);
標(biāo)簽
偶爾,你可能需要解析所有特定類別的綁定。例如,或許你正在構(gòu)建一個報告整合器,它接收一個由很多不同的Report接口實現(xiàn)組成的數(shù)組,你可以用tag方法給他們分配一個標(biāo)簽。
$this->app->bind('SpeedReport', function () {
//
});
$this->app->bind('MemoryReport', function () {
//
});
$this->app->tag(['SpeedReport', 'MemoryReport'], 'reports');
當(dāng)這些服務(wù)被標(biāo)記了,你可以通過tagged方法輕松解析它們:
$this->app->bind('ReportAggregator', function ($app) {
return new ReportAggregator($app->tagged('reports'));
});
解析
有很多方法可以從容器中解析對象。第一種方法,你可以用make方法,它接受你要解析的類或者接口名:
$fooBar = $this->app->make('FooBar');
第一種,你可以像數(shù)組一樣訪問容器,因為它實現(xiàn)了PHP的ArrayAccess接口:
$fooBar = $this->app['FooBar'];
最后,也是最重要的,你可以簡單地在被容器解析的類構(gòu)造函數(shù)里加上依賴的“類型提示”,這些類包括controllers,event listeners,queue jobs,middleware等等。實際上,大部分對象都是這么被容器解析的。
容器會自動注入類依賴提供類來解析。例如,你可以在控制器的構(gòu)造函數(shù)中類型提示一個被應(yīng)用定義的倉庫。這個倉庫會自動解析和注入到這個類中。
<?php
namespace App\Http\Controllers;
use App\Users\Repository as UserRepository;
class UserController extends Controller
{
/**
* The user repository instance.
*/
protected $users;
/**
* Create a new controller instance.
*
* @param UserRepository $users
* @return void
*/
public function __construct(UserRepository $users)
{
$this->users = $users;
}
/**
* Show the user with the given ID.
*
* @param int $id
* @return Response
*/
public function show($id)
{
//
}
}
容器事件
服務(wù)容器每解析一個對象都會觸發(fā)一次事件。你可以用resolving方法來監(jiān)聽這個事件:
$this->app->resolving(function ($object, $app) {
// Called when container resolves object of any type...
});
$this->app->resolving(FooBar::class, function (FooBar $fooBar, $app) {
// Called when container resolves objects of type "FooBar"...
});
你可以看到,被解析的對象會傳遞一個回調(diào),你可以在對象傳遞給用戶前在它上面設(shè)置任何額外屬性。