Command bus 提供一個(gè)簡(jiǎn)便的方法來(lái)封裝任務(wù),使你的程序更加容易閱讀與執(zhí)行,為了幫助我們更加了解使用「命令」的目的,讓我們來(lái)模擬建立一個(gè)可以購(gòu)買(mǎi) podcast 的網(wǎng)站。
用戶購(gòu)買(mǎi) podcasts 的過(guò)程中需要做很多事。例如,我們需要從用戶的信用卡扣款,將紀(jì)錄添加到數(shù)據(jù)庫(kù)以表示購(gòu)買(mǎi),并發(fā)送購(gòu)買(mǎi)確認(rèn)的電子郵件,或許,我們還需要進(jìn)行許多驗(yàn)證來(lái)確認(rèn)用戶是否可以購(gòu)買(mǎi)。
我們可以將這些邏輯通通放在控制器的方法內(nèi),然而,這樣做會(huì)有一些缺點(diǎn),首先,控制器可能還需要處理許多其他的 HTTP 請(qǐng)求,包含復(fù)雜的邏輯,這會(huì)讓控制器變得很臃腫且難易閱讀,第二點(diǎn),這些邏輯無(wú)法在這個(gè)控制器以外被重復(fù)使用,第三,這些命令無(wú)法被單元測(cè)試,為此我們還 得額外產(chǎn)生一個(gè) HTTP 請(qǐng)求,并向網(wǎng)站進(jìn)行完整購(gòu)買(mǎi) podcast 的流程。
比起將邏輯放在控制器內(nèi),我們可以選擇使用一個(gè)「命令」對(duì)象來(lái)封裝它,如 PurchasePodcast
命令。
使用 make:command
這個(gè) Artisan 命令可以產(chǎn)生一個(gè)新的命令類 :
php artisan make:command PurchasePodcast
新產(chǎn)生的類會(huì)被放在 app/Commands
目錄中,命令默認(rèn)包含了兩個(gè)方法:構(gòu)造器和 handle
。當(dāng)然,handle
方法執(zhí)行命令時(shí),你可以使用構(gòu)造器傳入相關(guān)的對(duì)象到這個(gè)命令中。例如:
class PurchasePodcast extends Command implements SelfHandling { protected $user, $podcast; /** * Create a new command instance. * * @return void */ public function __construct(User $user, Podcast $podcast) { $this->user = $user; $this->podcast = $podcast; } /** * Execute the command. * * @return void */ public function handle() { // Handle the logic to purchase the podcast... event(new PodcastWasPurchased($this->user, $this->podcast)); }}
handle
方法也可以使用類型提示依賴,并且通過(guò) 服務(wù)容器 機(jī)制自動(dòng)進(jìn)行依賴注入。例如:
/** * Execute the command. * * @return void */ public function handle(BillingGateway $billing) { // Handle the logic to purchase the podcast... }
所以,我們建立的命令該如何調(diào)用它呢?當(dāng)然,我們可以直接調(diào)用 handle
方法,然而使用 Laravel 的 "command bus" 來(lái)調(diào)用命令將會(huì)有許多優(yōu)點(diǎn),待會(huì)我們會(huì)討論這個(gè)部分。
如果你有瀏覽過(guò)內(nèi)置的基本控制器,將會(huì)發(fā)現(xiàn) DispatchesCommands
trait ,它將允許我們?cè)诳刂破鲀?nèi)調(diào)用 dispatch
方法,例如:
public function purchasePodcast($podcastId){ $this->dispatch( new PurchasePodcast(Auth::user(), Podcast::findOrFail($podcastId)) );}
Command bus 將會(huì)負(fù)責(zé)執(zhí)行命令和調(diào)用 IoC 容器來(lái)將所需的依賴注入到 handle
方法。
你也可以將 Illuminate\Foundation\Bus\DispatchesCommands
trait 加入任何要使用的類內(nèi)。若你想要在任何類的構(gòu)造器內(nèi)接收 command bus 的實(shí)體 ,你可以使用類型提示 Illuminate\Contracts\Bus\Dispatcher
這個(gè)接口。 最后,你也可以使用 Bus
facade 來(lái)快速派發(fā)命令:
Bus::dispatch( new PurchasePodcast(Auth::user(), Podcast::findOrFail($podcastId)) );
映射 HTTP 請(qǐng)求到命令是很常見(jiàn)的,所以,與其要你針對(duì)每個(gè)請(qǐng)求苦命地進(jìn)行手動(dòng)對(duì)應(yīng),Laravel 則提供一些有用的方法來(lái)輕松達(dá)到,讓我們來(lái)看一下 DispatchesCommands
trait 提供的 dispatchFrom
方法:
$this->dispatchFrom('Command\Class\Name', $request);
這個(gè)方法將會(huì)檢查這個(gè)被傳入的命令類的構(gòu)造器,并取出來(lái)自于 HTTP 請(qǐng)求的變量(或其他任何的 ArrayAccess
對(duì)象) 并將其填入構(gòu)造器,所以,若命令類在構(gòu)造器接受 firstName
參數(shù),command bus 將會(huì)試圖從 HTTP 請(qǐng)求取出 firstName
參數(shù)。
dispatchFrom
方法的第三個(gè)參數(shù)允許你傳入數(shù)組,那些不在 HTTP 請(qǐng)求內(nèi)的參數(shù)可用這個(gè)數(shù)組來(lái)填入構(gòu)造器:
$this->dispatchFrom('Command\Class\Name', $request, [ 'firstName' => 'Taylor',]);
Command bus 不僅僅作為當(dāng)下請(qǐng)求的同步作業(yè),也可以作為 Laravel 隊(duì)列任務(wù)的主要方法,所以,我們要如何指示 command bus 在背景作業(yè)而不是同步處理呢?非常簡(jiǎn)單,首先,在建立新的命令時(shí)加上 --queued
參數(shù):
php artisan make:command PurchasePodcast --queued
正如你所見(jiàn)的,這讓命令增加了一點(diǎn)功能,即 Illuminate\Contracts\Queue\ShouldBeQueued
接口和SerializesModels
trait 。 他們指示 command bus 使用隊(duì)列來(lái)執(zhí)行命令,以及優(yōu)雅的序列化和反序列化任何在命令內(nèi)被保存的 Eloquent 模型。
若你想將已存在的命令轉(zhuǎn)換為隊(duì)列命令,只需手動(dòng)修改讓命令類實(shí)現(xiàn) Illuminate\Contracts\Queue\ShouldBeQueued
接口,它不包含方法,而是僅僅給調(diào)用員作為"標(biāo)記接口"。
然后,一如往常撰寫(xiě)你的命令,當(dāng)你將命令派發(fā)到 bus,它將會(huì)自動(dòng)將命令丟到背景隊(duì)列執(zhí)行,沒(méi)有比這個(gè)更容易的方法了。
想了解更多關(guān)于隊(duì)列命令的方法,請(qǐng)見(jiàn)隊(duì)列文檔.
在命令被派發(fā)到處理器之前,你也可以將它通過(guò)"命令管道"傳遞到其他類去。命令管道操作上如 HTTP 中間件,除了是專門(mén)來(lái)給命令用的,例如,一個(gè)命令管道能夠在數(shù)據(jù)庫(kù)事務(wù)處理期間包裝全部的命令操作,或者僅作為執(zhí)行紀(jì)錄。
要將管道添加到 bus,只要從App\Providers\BusServiceProvider::boot
方法調(diào)用調(diào)用員的pipeThrough
方法:
$dispatcher->pipeThrough(['UseDatabaseTransactions', 'LogCommand']);
一個(gè)命令管道被定義在 handle
方法,就如個(gè)中間件:
class UseDatabaseTransactions { public function handle($command, $next) { return DB::transaction(function() use ($command, $next) { return $next($command); }); }}
命令管道是通過(guò) IoC 容器來(lái)達(dá)成,所以請(qǐng)自行在構(gòu)造器類型提示所需的依賴。
你甚至可以定義一個(gè) 閉包
來(lái)作為命令管道:
$dispatcher->pipeThrough([function($command, $next){ return DB::transaction(function() use ($command, $next) { return $next($command); });}]);
更多建議: