1. 程式人生 > 其它 >[譯]Laravel 5.0 之命令及處理程式

[譯]Laravel 5.0 之命令及處理程式

本文譯自 Matt Stauffer 的系列文章.


本文中涉及的新功能都是關於 Commands 的,這些特性在 Laravel 舊版本中已經有了,但是在 Laravel 5.0 中變得更加好用了。

本文中將會用到例子來自於我正在開發的一個叫做 SaveMyProposals 的新應用。

什麼是 Command, Command handler 和 Command bus?

Shawn McCool 的這篇文章 中,你可以深入瞭解 command, command handler, command bus 的概念。但總的來說:

Command 是一個代表資訊的簡單物件。它只包含你打算做某件事時需要用到的資訊。在我們接下來的例子中,它就是 "複製談話命令(Duplicate Talk Command)", 任何時候當用戶要複製一條談話建議時,我們的系統就會呼叫它。 這個 "重複談話命令" 會包含複製一個談話所需要的全部屬性集——比如一個序列化的 Talk 物件或者是 TaldId.

Command Handler 則是用於對 Command 做出響應的一個類。Command 可以在一個或多個 Handlers 之間傳遞, 每個 Handler 從 Command 中取出重要的資訊並做某些操作來響應。

Command bus 是一套用於排程 Commands 的系統。它把 commands 與對應的 Handlers 進行匹配,並使它們能夠一起工作。一般情況下,人們需要編寫自己的 command bus, 但 Laravel 內建了一個開箱即用的 Command bus, 所以至少在本文涉及的範圍內我們不用擔心這個問題。

在 Laravel 中使用 Command

在開始介紹 Laravel 5.0 中使用 Command 的整個結構之前,我們先看看最終的用例是怎樣的。假設一個使用者訪問了系統的某個路由,比如 savemyproposals.com/talks/12345/duplicate

, 該路由被解析到 TalkController@duplicate(12345).

下面是處理這個請求的路由和方法:

// HttpControllersTalkController
...
    public function duplicate($talkId)
    {
        $talk = Talk::findOrFail($talkId);

        $this->dispatch(new DuplicateTalkCommand($talk));

        // 取決於具體的實現,這兩行程式碼也可能簡化為一行程式碼:
        // $this->dispatch(new DuplicateTalkCommand($talkId));
    }

接下來是 Command 的程式碼:

// CommandsDuplicateTalkCommand
...

class DuplicateTalkCommand extends Command
{
    public $talk;

    public function __construct(Talk $talk)
    {
        $this->talk = $talk;
    }
}

然後是 Command handler:

// HandlersCommandsDuplicateTalkCommandHandler
...

class DuplicateTalkCommandHandler
{
    public function handle(DuplicateTalkCommand $command)
    {
        // 對 $command 變數進行某些操作
        dd($command);
    }
}

就如上面的程式碼所展示的,控制器通過一些必要的資訊建立了一個 DuplicateTalkCommand 物件,通過內建的 command bus dispatcher 對齊進行排程,於是該命令的處理程式自動對其進行處理。

體系結構

接下來,我們先來看看這些命令和處理程式存放在什麼位置,然後再說說如何生成它們。

資料夾

在 Laravel 5.0 的應用框架中,app/ 目錄下有兩個新的資料夾:CommandsHandlers, Handlers 目錄下還有兩個子目錄:CommandsEvents(這個目錄說明我們還可以期待事件處理的特性).

app/
    Commands/
    Handlers/
        Commands/
        Events/

相信看到目錄結構你就能猜到,Commands 的程式碼存放在 app/Commands 目錄下,而 Command handlers 則存放在 app/Handlers/Commands 目錄下—— Handler 的檔名與其對應的 Command 保持一致,但是要加上 Handler 字尾。

Artisan

非常值得慶幸的是,你不用自己動手來建立 Command 和 Command Handler。新版本提供了一個全新的 Artisan 生成工具,通過它可以快速生成這些檔案:

$ php artisan make:command DuplicateTalkCommand

預設情況下,這條命令會生成一個自處理的命令(不生成單獨的 Command handler),並且該命令不新增到佇列。加上 --handler 引數可以同時生成 handler, 加上 --queued 引數可以將其加入到佇列。

執行這個 artisan 命令會生成兩個檔案: 命令檔案(app/Commands/DuplicateTalkCommand.php) 和 處理程式檔案(app/Handlers/Commands/DuplicateTalkCommandHandler.php) (假設使用了 --handler 引數),並且生成的處理程式中的 handle 方法會自動加上與其匹配的命令的型別約束。

基本工作流程

綜上所述,要建立一個新的 DuplicateTalkCommand, 你需要執行以下工作:

  1. php artisan make:command DuplicateTalkCommand
  2. 編輯 DuplicateTalkCommand, 增加一個 public 屬性 $talk 並在建構函式中初始化這個屬性。
  3. 編輯 DuplicateTalkCommandHandler, 在 handle() 方法中編寫具體程式碼,完成你需要執行的操作。
  4. 在控制器或者 Artisan 命令中排程(呼叫)這個命令。

佇列

把命令加入佇列

如果希望某個命令在每次被呼叫時加入到佇列中以便非同步執行,你需要做的是讓該命令實現 ShouldBeQueued 介面。 Laravel 會發現這個介面並把其加入佇列等候執行,而不是立即執行。

...
class DuplicateTalkCommand extends Command implements ShouldBeQueued
{
    //...
}

InteractsWithQueue trait

在你的 Command 類中加上這個 trait, 會讓你的 Command 具有在以前版本中用慣了的佇列命令(queue commands)所具有的全部特性:$command->release(), $command->delete(), $command->attempts() 等等。

...
class DuplicateTalkCommand extends Command implements ShouldBeQueued, InteractsWithQueue
{
    //...
}

SerializesModels trait

如果你傳入一個 Eloquent 模型作為屬性,就像前面的例子中那樣,並且希望命令放入佇列中執行而不是同步執行,那麼必須要考慮到 Eloquent 模型的序列化,這可能會給你帶來一些麻煩。不過在 Laravel 5.0 版本中,你可以給你的 Command 加一個 名為 SerializesModels 的 trait 來解決這個問題。方法很簡單,在類的程式碼塊頂部加上即可:

...
class DuplicateTalkCommand extends Command implements ShouldBeQueued
{ 
    use SerializesModels;
    // ...
}

Dispatcher

DispatchesCommands Trait

你可能注意到,在前面的例子中,我們可以直接在控制器中使用 $this->dispatch() 方法。這是控制器的一個語法糖。這個語法糖實際上是通過名為 DispathesCommands 的 trait 來實現的。你可以在控制器之外的任何地方使用這個 trait.

比如,你希望某個服務類可以在方法中使用 $this->dispatch(), 你只要在你的服務類的程式碼塊頂部使用 DispatchesCommands 這個 trait 即可:

...
class MyServiceClass {

    use DispatchesCommands;

    //...
}

注入 bus

如果你希望更直接、更清楚地呼叫 Command bus 而不是藉助於 Laravel 系統提供的 trait, 你可以直接向你的類的建構函式或者是方法注入 IlluminateContractsBusDispatcher 例項(參見 Laravel 5.0 之方法注入)。

...
    public function __construct(IlluminateContractsBusDispatcher $bus)
    {
        $this->bus = $bus;
    }

    public function doSomething()
    {
        $this->bus->dispatch(new Command);
    }

dispatchFrom(command::class, $request 或任何 arrayAccessible 物件)

在之前的例子中,我們已經看到了呼叫命令的最簡單方式,就是 $bus->dispatch(new Command(params...)). 但有時候由於新建命令的引數列表變得越來越大——比如,當你的命令用於來處理表單輸入的時候:

...
class CreateTalkCommand extends Command
{
    public function __construct($title, $description, $outline, $organizer_notes, $length, $type, $level) {
        // ...
    }
}

這時,如果還用之前的方式來例項化命令,程式碼會變得很難看:

$this->dispatch(new CreateTalkCommand($input['title'], $input['description'],
        $input['outline'], $input['organizer_notes'], $input['length'],
        $input['type'], $input['level']));

通常我們的表單請求會傳遞與屬性相同 key 的陣列,從陣列或者請求物件中獲得具體的值。幸虧 Laravel 5.0 有針對這種情況的解決方案:

$this->dispatch('NameOfCommand', $objectThatImplementsPHPArrayAccessible);

因此我們可以用下面的程式碼替換上面那長長的一串:

$this->dispatchFrom(CreateTalkCommand::class, $input);

或者這樣:

public function doSomethingInController(Request $request)
    {
        $this->dispatchFrom(CreateTalkCommand::class, $request);
        // ...
    }

Laravel 會自動在傳入的陣列或者 arrayAccessible 物件中去尋找與屬性名相同的 key, 取出對應的值來呼叫命令的建構函式。

自處理命令

如果你嫌每個命令都要建立一個 Command 類和一個 Command handler 類太麻煩的話,你可以建立一個“自處理”的 Command. 這種情況下 Command 只有單一的處理程式,且該處理程式就是 Command 自己。要讓一個 Command 變成自處理,只需要給該 command 類加一個 handle() 方法,並讓它實現 SelfHandling 介面:

...
class DuplicateTalkCommand extends Command implements SelfHandling
{
    //...
    public function handle()
    {
        //...
    }

其它注意事項

  • 命令處理程式會由 IOC 容器進行解析,因此你可以注入 repositories, service classes 或者任何其它型別到你的命令處理程式的建構函式中,然後在 handle() 方法中呼叫它們。
  • 幾乎所有相關的 trait 和 介面都在 IlluminateContractsBus 或者 IlluminateContractsQueue 名稱空間下。比如,IlluminateContractsBusSelfHandling.
  • 如果你的命令是放入佇列執行,不需要在處理程式的最後執行 $command->delete() 方法。只要你的處理程式沒有丟擲任何異常,Laravel 會假定它已經正確完成,並自動將其從佇列中移除。

寫在最後

就這麼多了,如果我遺漏了什麼,或者某個問題講得不夠清楚,請讓我知道。本文涉及到的點還有一些需要補充和替換的地方。暫時來說,我希望本文可以幫助你瞭解新版 Laravel 中的 Command 的執行機制。此外,Taylor 在 Laracasts 上的視訊 涵蓋了本文的全部內容並且講得更多。