Laravel 5.0 コマンドバス

イントロダクション

Laravelコマンドバスは、シンプルで分かりやすい「コマンド」として実行する必要のあるアプリケーションのタスクをカプセル化する便利なメソッドです。コマンドの目的を理解するために、ポッドキャストをユーザーに購入してもらうアプリケーションを構築しているとしましょう。

ユーザーがポッドキャストを購入すると、色々な処理を実行する必要が起きます。例えば、ユーザーのクレジットカードに課金したり、領収を表すデータベースへレコードを追加したり、購入の確認メールを送ったりです。多分、そのユーザーがポッドキャストを購入できるのか、様々なバリデーションを行う必要もあるでしょう。

コントローラーメソッドへ、こうしたロジックを全部突っ込むこともできます。しかし、多くの点で良くありません。多分、コントローラは要求がされるだろう別のHTTPアクションもたくさん処理し、各コントローラーメソッドには複雑なロジックがつめ込まれ、膨れ上がり、結果として読みづらくなってしまうのが第一の欠点です。第二に、コントローラー外でポッドキャスト購入のロジックを再利用するのが難しいことです。第三に、ポッドキャスト購入ロジックをテストするため、HTTPリクエストのスタブを生成しなくてはなりませんし、アプリケーションへ送る完全なリクエストを作成しなくてはならないため、コントローラーのユニットテストが困難になることです。

ロジックをコントローラーに詰め込む代わりに、例えばPurchasePodcastコマンドのように、「コマンド」オブジェクトとしてカプセル化することも選択できます。

コマンド作成

新しいコマンドクラスは、make:command Artisan CLIで生成できます。

php artisan make:command PurchasePodcast

生成されたクラスは、app/Commandsディレクトリーへ設置されます。生成されたコマンドには、デフォルトで2つのメソッドがあります。コンストラクターとhandleメソッドです。もちろんコンストラクターはコマンドへ適切なオブジェクトを渡すために使えます。一方のhandleメソッドは、コマンドを実行します。例をご覧ください。

class PurchasePodcast extends Command implements SelfHandling {

    protected $user, $podcast;

    /**
     * 新しいコマンドインスタンス生成
     *
     * @return void
     */
    public function __construct(User $user, Podcast $podcast)
    {
        $this->user = $user;
        $this->podcast = $podcast;
    }

    /**
     * コマンド実行
     *
     * @return void
     */
    public function handle()
    {
        // ポッドキャスト購入ロジックを処理する…

        event(new PodcastWasPurchased($this->user, $this->podcast));
    }

}

handleメソッドでも依存をタイプヒントで指定でき、サービスコンテナにより自動的に注入されます。例えば:

    /**
     * コマンド実行
     *
     * @return void
     */
    public function handle(BillingGateway $billing)
    {
        // ポッドキャスト購入ロジックを処理する…
    }

コマンドデスパッチ

コマンドはできました。ではどうやってデスパッチするのでしょう?もちろん、handleメソッドを直接呼び出すこともできます。しかし、コマンドをLaravel「コマンドバス」を通して実行する方法には、後ほど説明する多くの利点があります。

アプリケーションのベースコントローラーを覗いてみれば、DispatchesCommandsトレイトが見つかります。このトレイトは、コントローラーからdispatchメソッドを呼び出せるようにします。以下のようにです。

public function purchasePodcast($podcastId)
{
    $this->dispatch(
        new PurchasePodcast(Auth::user(), Podcast::findOrFail($podcastId))
    );
}

コマンドバスはコマンド実行の面倒を見ます。そして、呼びだされたIoCコンテナがhandleメソッドへ必要な依存を注入します。

お望みであれば、どんなクラスへでもIlluminate\Foundation\Bus\DispatchesCommandsトレイトを付け加えることができます。クラスのコンストラクターを通して、コマンドバスのインスタンスを受け取りたいのであれば、Illuminate\Contracts\Bus\Dispatcherインターフェイスをタイプヒントに指定してください。最後に、Busファサードを使い、簡単にコマンドをデスパッチする方法を紹介します。

    Bus::dispatch(
        new PurchasePodcast(Auth::user(), Podcast::findOrFail($podcastId))
    );

リクエストからコマンドのプロパティーをマップする

HTTPリクエストの変数をコマンドへマップしたいと考えるのは当然でしょう。それぞれのリクエストを手動で無理やりマップする代わりに、Laravelでは簡単に実現できるヘルパメソッドを用意しています。DispatchesCommandsトレイトで使用できる、dispatchFromメソッドを取り上げてみてみましょう。

$this->dispatchFrom('Command\Class\Name', $request);

このメソッドは指定されたコマンドクラスのコンストラクターを調べ、それからHTTPリクエスト(もしくは他のArrayAccessオブジェクト)から変数を取り出し、必要なコマンドのコンストラクター引数を埋めます。ですから、もしコマンドクラスがコンストラクターで$firstName変数を取る場合、コマンドバスはHTTPリクエストからfirstNameパラメーターを取り出そうとします。

dispatchFromメソッドはさらに第3引数に配列を指定できます。この配列はリクエストからは埋められないコンストラクター引数を埋めるために使用されます。

$this->dispatchFrom('Command\Class\Name', $request, [
    'firstName' => 'Taylor',
]);

コマンドのキュー投入

コマンドバスは現在のリクエストサイクル中で、同期的にジョブを実行するだけではなく、Laravelのキュージョブとして非同期に実行する重要な手法も提供しています。では同期的に実行する代わりに、どうやってコマンドバスへ、バックグラウンド処理を行うためにジョブをキューに入れろと指示するのでしょうか。簡単です。最初に新しいコマンドを生成するときに、--queuedフラグをコマンドへ付けるだけです。

php artisan make:command PurchasePodcast --queued

すぐ気がつくでしょうが、これによりいくつかの機能がコマンドに追加されます。すなわちIlluminate\Contracts\Queue\ShouldBeQueuedインターフェイスとSerializesModelsトレイトです。これらは、コマンドバスにコマンドをキューに投入するように指示し、同時にコマンドのプロパティーとしてストアされたEloquentモデルを優雅にシリアライズ、非シリアライズします。

もし、既に存在するコマンドをキュー実行のコマンドに変更したければ、単にIlluminate\Contracts\Queue\ShouldBeQueuedインターフェイスを手動でクラスへ実装してください。これはメソッドを含んでおらず、主にデスパッチャーがコマンドへ投入する「目印のインターフェイス」として使用しています。

それから、コマンドを通常通り書いてください。それがバスへデスパッチされると、バックグラウンドで処理するため自動的にキューへ投入します。

キューされるコマンドのインターフェイスの詳細は、キューのドキュメントを参照してください。

コマンドパイプライン

ハンドラにディスパッチされる前に、「パイプライン」中の他のクラスへ、コマンドを通すことができます。コマンドパイプラインは、実行するのがコマンドという違いだけで、まるでHTTPミドルウェアのように動作します!例えば、コマンド全体の操作をデータベーストランザクションでラップしたり、実行をログしたりできます。

パイプをバスに追加するには、App\Providers\BusServiceProvider::bootメソッドから、ディスパッチャーのpipeThroughメソッドを呼び出します。

$dispatcher->pipeThrough(['UseDatabaseTransactions', 'LogCommand']);

コマンドのパイプは、ミドルウェアと同様に、handleメソッドで定義します。

class UseDatabaseTransactions {

    public function handle($command, $next)
    {
        return DB::transaction(function() use ($command, $next)
        {
            return $next($command);
        });
    }

}

コマンドパイプのクラスは、IoCコンテナを通じて依存解決されるため、コンストラクターでタイプヒントによる依存を指定することもできます。

コマンドパイプを「クロージャー」で定義することもできます。

$dispatcher->pipeThrough([function($command, $next)
{
    return DB::transaction(function() use ($command, $next)
    {
        return $next($command);
    });
}]);

ドキュメント章別ページ

Artisan CLI

ヘッダー項目移動

注目:アイコン:ページ内リンク設置(リンクがないヘッダーへの移動では、リンクがある以前のヘッダーのハッシュをURLへ付加します。

移動

クリックで即時移動します。

設定

適用ボタンクリック後に、全項目まとめて適用されます。

カラーテーマ
和文指定 Pagination
和文指定 Scaffold
Largeスクリーン表示幅
インデント
本文フォント
コードフォント
フォント適用確認

フォントの指定フィールドから、フォーカスが外れると、当ブロックの内容に反映されます。EnglishのDisplayもPreviewしてください。

フォント設定時、表示に不具合が出た場合、当サイトのクッキーを削除してください。

バックスラッシュを含むインライン\Code\Blockの例です。

以下はコードブロックの例です。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * ユーザに関連する電話レコードを取得
     */
    public function phone()
    {
        return $this->hasOne('App\Phone');
    }
}

設定を保存する前に、表示が乱れないか必ず確認してください。CSSによるフォントファミリー指定の知識がない場合は、フォントを変更しないほうが良いでしょう。

キーボード・ショートカット

オープン操作

PDC

ページ(章)移動の左オフキャンバスオープン

HA

ヘッダー移動モーダルオープン

MS

移動/設定の右オフキャンバスオープン

ヘッダー移動

T

最初のヘッダーへ移動

E

最後のヘッダーへ移動

NJ

次ヘッダー(H2〜H4)へ移動

BK

前ヘッダー(H2〜H4)へ移動

その他

?

このヘルプページ表示
閉じる