Laravel服務(wù)容器是一個(gè)用于管理類依賴和執(zhí)行依賴注入的強(qiáng)大工具。依賴注入聽上去很花哨,其實(shí)質(zhì)是通過構(gòu)造函數(shù)或者某些情況下通過set方法將類依賴注入到類中。
讓我們看一個(gè)簡(jiǎn)單的例子:
<?php
namespace App\Jobs;
use App\User;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Contracts\Bus\SelfHandling;
class PurchasePodcast implements SelfHandling{
/**
*?郵件實(shí)現(xiàn)
*/
protected $mailer;
/**
*?創(chuàng)建一個(gè)新的實(shí)例
*
* @param Mailer $mailer
* @return void
*/
public function __construct(Mailer $mailer)
{
$this->mailer = $mailer;
}
/**
*?購(gòu)買一個(gè)播客
*
* @return void
*/
public function handle()
{
//
}
}
在本例中,當(dāng)播客被購(gòu)買后PurchasePodcast
任務(wù)需要發(fā)送郵件,因此,你需要注入一個(gè)可以發(fā)送郵件的服務(wù)。由于該服務(wù)是被注入的,我們可以方便的使用其另一個(gè)實(shí)現(xiàn)來替換它,在測(cè)試的時(shí)候我們還可以”模擬“或創(chuàng)建一個(gè)假的郵件實(shí)現(xiàn)。
深入理解Laravel服務(wù)容器對(duì)于構(gòu)建功能強(qiáng)大的大型Laravel應(yīng)用而言至關(guān)重要,對(duì)于貢獻(xiàn)代碼到Laravel核心也很有幫助。
幾乎所有的服務(wù)容器綁定都是在服務(wù)提供者中完成。因此本章節(jié)的演示例子用到的容器都是在這種上下文環(huán)境中,如果一個(gè)類沒有基于任何接口那么就沒有必要將其綁定到容器。容器并不需要被告知如何構(gòu)建對(duì)象,因?yàn)樗鼤?huì)使用PHP的反射服務(wù)自動(dòng)解析出具體的對(duì)象。
在一個(gè)服務(wù)提供者中,可以通過$this->app
變量訪問容器,然后使用bind方法注冊(cè)一個(gè)綁定,該方法需要兩個(gè)參數(shù),第一個(gè)參數(shù)是我們想要注冊(cè)的類名或接口名稱,第二個(gè)參數(shù)是返回類的實(shí)例的閉包:
$this->app->bind('HelpSpot\API', function ($app) {
return new HelpSpot\API($app['HttpClient']);
});
注意到我們接受容器本身作為解析器的一個(gè)參數(shù),然后我們可以使用該容器來解析我們正在構(gòu)建的對(duì)象的子依賴。
綁定一個(gè)單例
singleton
方法綁定一個(gè)只需要解析一次的類或接口到容器,然后接下來對(duì)容器的調(diào)用將會(huì)返回同一個(gè)實(shí)例:
$this->app->singleton('FooBar', function ($app) {
return new FooBar($app['SomethingElse']);
});
綁定實(shí)例
你還可以使用instance
方法綁定一個(gè)已存在的對(duì)象實(shí)例到容器,隨后對(duì)容器的調(diào)用將總是返回給定的實(shí)例:
$fooBar = new FooBar(new SomethingElse);
$this->app->instance('FooBar', $fooBar);
服務(wù)容器的一個(gè)非常強(qiáng)大的特性是其綁定接口到實(shí)現(xiàn)的能力。我們假設(shè)有一個(gè)EventPusher
接口及其RedisEventPusher
實(shí)現(xiàn),編寫完該接口的RedisEventPusher
實(shí)現(xiàn)后,就可以將其注冊(cè)到服務(wù)容器:
$this->app->bind('App\Contracts\EventPusher', 'App\Services\RedisEventPusher');
這段代碼告訴容器當(dāng)一個(gè)類需要EventPusher
的實(shí)現(xiàn)時(shí)將會(huì)注入RedisEventPusher
,現(xiàn)在我們可以在構(gòu)造器或者任何其它通過服務(wù)容器注入依賴的地方進(jìn)行EventPusher接口的類型提示:
use App\Contracts\EventPusher;
/**
*?創(chuàng)建一個(gè)新的類實(shí)例
*
* @param EventPusher $pusher
* @return void
*/
public function __construct(EventPusher $pusher){
$this->pusher = $pusher;
}
有時(shí)侯我們可能有兩個(gè)類使用同一個(gè)接口,但我們希望在每個(gè)類中注入不同實(shí)現(xiàn),例如,當(dāng)系統(tǒng)接到一個(gè)新的訂單的時(shí)候,我們想要通過PubNub而不是Pusher發(fā)送一個(gè)事件。Laravel定義了一個(gè)簡(jiǎn)單、平滑的方式來定義這種行為:
$this->app->when('App\Handlers\Commands\CreateOrderHandler')
->needs('App\Contracts\EventPusher')
->give('App\Services\PubNubEventPusher');
你甚至還可以傳遞一個(gè)閉包到give方法:
$this->app->when('App\Handlers\Commands\CreateOrderHandler')
->needs('App\Contracts\EventPusher')
->give(function () {
// Resolve dependency...
});
少數(shù)情況下我們需要解析特定分類下的所有綁定,比如,也許你正在構(gòu)建一個(gè)接收多個(gè)不同Report
接口實(shí)現(xiàn)的報(bào)告聚合器,在注冊(cè)完Report
實(shí)現(xiàn)之后,可以通過tag方法給它們分配一個(gè)標(biāo)簽:
$this->app->bind('SpeedReport', function () {
//
});
$this->app->bind('MemoryReport', function () {
//
});
$this->app->tag(['SpeedReport', 'MemoryReport'], 'reports');
這些服務(wù)被打上標(biāo)簽后,可以通過tagged
方法來輕松解析它們:
$this->app->bind('ReportAggregator', function ($app) {
return new ReportAggregator($app->tagged('reports'));
});
有很多方式可以從容器中解析對(duì)象,首先,你可以使用make
方法,該方法接收你想要解析的類名或接口名作為參數(shù):
$fooBar = $this->app->make('FooBar');
其次,你可以以數(shù)組方式訪問容器,因?yàn)槠鋵?shí)現(xiàn)了PHP的ArrayAccess
接口:
$fooBar = $this->app['FooBar'];
最后,也是最常用的,你可以簡(jiǎn)單的通過在類的構(gòu)造函數(shù)中對(duì)依賴進(jìn)行類型提示來從容器中解析對(duì)象,包括控制器、事件監(jiān)聽器、隊(duì)列任務(wù)、中間件等都是通過這種方式。在實(shí)踐中,這是大多數(shù)對(duì)象從容器中解析的方式。
容器會(huì)自動(dòng)為其解析類注入依賴,比如,你可以在控制器的構(gòu)造函數(shù)中為應(yīng)用定義的倉(cāng)庫(kù)進(jìn)行類型提示,該倉(cāng)庫(kù)會(huì)自動(dòng)解析并注入該類:
<?php
namespace App\Http\Controllers;
use Illuminate\Routing\Controller;
use App\Users\Repository as UserRepository;
class UserController extends Controller{
/**
*?用戶倉(cāng)庫(kù)實(shí)例
*/
protected $users;
/**
*?創(chuàng)建一個(gè)控制器實(shí)例
*
* @param UserRepository $users
* @return void
*/
public function __construct(UserRepository $users)
{
$this->users = $users;
}
/**
*?通過指定ID顯示用戶
*
* @param int $id
* @return Response
*/
public function show($id)
{
//
}
}
服務(wù)容器在每一次解析對(duì)象時(shí)都會(huì)觸發(fā)一個(gè)事件,可以使用resolving
方法監(jiān)聽該事件:
$this->app->resolving(function ($object, $app) {
//?容器解析所有類型對(duì)象時(shí)調(diào)用
});
$this->app->resolving(function (FooBar $fooBar, $app) {
//?容器解析“FooBar”對(duì)象時(shí)調(diào)用
});
正如你所看到的,被解析的對(duì)象將會(huì)傳遞給回調(diào),從而允許你在對(duì)象被傳遞給消費(fèi)者之前為其設(shè)置額外屬性。
擴(kuò)展閱讀:實(shí)例教程——深入理解控制反轉(zhuǎn)和依賴注入
更多建議: