鍍金池/ 教程/ PHP/ Command Bus
Laravel Cashier
Eloquent ORM
HTTP 響應(yīng)
發(fā)行說(shuō)明
擴(kuò)展包開(kāi)發(fā)
HTTP 控制器
事件
擴(kuò)展框架
Contracts
開(kāi)發(fā)
配置
表單驗(yàn)證
錯(cuò)誤與日志
Hashing
貢獻(xiàn)指南
郵件
Session
遷移與數(shù)據(jù)填充
查詢構(gòu)造器
Redis
升級(jí)向?qū)?/span>
概覽
緩存
服務(wù)提供者
Envoy 任務(wù)執(zhí)行器
隊(duì)列
單元測(cè)試
服務(wù)容器
文件系統(tǒng) / 云存儲(chǔ)
認(rèn)證
請(qǐng)求的生命周期
加密
模板
視圖 (View)
Laravel Homestead
Laravel 安裝指南
介紹
Command Bus
分頁(yè)
輔助方法
應(yīng)用程序結(jié)構(gòu)
HTTP 路由
HTTP 請(qǐng)求
基本用法
本地化
HTTP 中間件
結(jié)構(gòu)生成器
Facades
Laravel Elixir

Command Bus

簡(jiǎn)介

Command bus 提供一個(gè)簡(jiǎn)便的方法來(lái)封裝任務(wù),使你的程序更加容易閱讀與執(zhí)行,為了幫助我們更加了解使用「命令」的目的,讓我們來(lái)模擬建立一個(gè)可以購(gòu)買 podcast 的網(wǎng)站。

用戶購(gòu)買 podcasts 的過(guò)程中需要做很多事。例如,我們需要從用戶的信用卡扣款,將紀(jì)錄添加到數(shù)據(jù)庫(kù)以表示購(gòu)買,并發(fā)送購(gòu)買確認(rèn)的電子郵件,或許,我們還需要進(jìn)行許多驗(yàn)證來(lái)確認(rèn)用戶是否可以購(gòu)買。

我們可以將這些邏輯通通放在控制器的方法內(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)買 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)用命令

所以,我們建立的命令該如何調(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))
    );

從請(qǐng)求映射要注入命令的屬性

映射 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',
]);

命令隊(duì)列

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)記接口"。

然后,一如往常撰寫你的命令,當(dāng)你將命令派發(fā)到 bus,它將會(huì)自動(dòng)將命令丟到背景隊(duì)列執(zhí)行,沒(méi)有比這個(gè)更容易的方法了。

想了解更多關(guān)于隊(duì)列命令的方法,請(qǐng)見(jiàn)隊(duì)列文檔.

命令管道

在命令被派發(fā)到處理器之前,你也可以將它通過(guò)"命令管道"傳遞到其他類去。命令管道操作上如 HTTP 中間件,除了是專門來(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);
    }
}]);