イントロダクションIntroduction
Laravelコマンドバスは、シンプルで分かりやすい「コマンド」として実行する必要のあるアプリケーションのタスクをカプセル化する便利なメソッドです。コマンドの目的を理解するために、ポッドキャストをユーザーに購入してもらうアプリケーションを構築しているとしましょう。The Laravel command bus provides a convenient method of encapsulating tasks your application needs to perform into simple, easy to understand "commands". To help us understand the purpose of commands, let's pretend we are building an application that allows users to purchase podcasts.
ユーザーがポッドキャストを購入すると、色々な処理を実行する必要が起きます。例えば、ユーザーのクレジットカードに課金したり、領収を表すデータベースへレコードを追加したり、購入の確認メールを送ったりです。多分、そのユーザーがポッドキャストを購入できるのか、様々なバリデーションを行う必要もあるでしょう。When a user purchases a podcast, there are a variety of things that need to happen. For example, we may need to charge the user's credit card, add a record to our database that represents the purchase, and send a confirmation e-mail of the purchase. Perhaps we also need to perform some kind of validation as to whether the user is allowed to purchase podcasts.
コントローラーメソッドへ、こうしたロジックを全部突っ込むこともできます。しかし、多くの点で良くありません。多分、コントローラは要求がされるだろう別のHTTPアクションもたくさん処理し、各コントローラーメソッドには複雑なロジックがつめ込まれ、膨れ上がり、結果として読みづらくなってしまうのが第一の欠点です。第二に、コントローラー外でポッドキャスト購入のロジックを再利用するのが難しいことです。第三に、ポッドキャスト購入ロジックをテストするため、HTTPリクエストのスタブを生成しなくてはなりませんし、アプリケーションへ送る完全なリクエストを作成しなくてはならないため、コントローラーのユニットテストが困難になることです。We could put all of this logic inside a controller method; however, this has several disadvantages. The first disadvantage is that our controller probably handles several other incoming HTTP actions, and including complicated logic in each controller method will soon bloat our controller and make it harder to read. Secondly, it is difficult to re-use the purchase podcast logic outside of the controller context. Thirdly, it is more difficult to unit-test the command as we must also generate a stub HTTP request and make a full request to the application to test the purchase podcast logic.
ロジックをコントローラーに詰め込む代わりに、例えばPurchasePodcast
コマンドのように、「コマンド」オブジェクトとしてカプセル化することも選択できます。Instead of putting this logic in the controller, we may choose to encapsulate it within a "command" object, such as a PurchasePodcast
command.
コマンド作成Creating Commands
新しいコマンドクラスは、make:command
Artisan CLIで生成できます。The Artisan CLI can generate new command classes using the make:command
command:
php artisan make:command PurchasePodcast
生成されたクラスは、app/Commands
ディレクトリーへ設置されます。生成されたコマンドには、デフォルトで2つのメソッドがあります。コンストラクターとhandle
メソッドです。もちろんコンストラクターはコマンドへ適切なオブジェクトを渡すために使えます。一方のhandle
メソッドは、コマンドを実行します。例をご覧ください。The newly generated class will be placed in the app/Commands
directory. By default, the command contains two methods: the constructor and the handle
method. Of course, the constructor allows you to pass any relevant objects to the command, while the handle
method executes the command. For example:
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
メソッドでも依存をタイプヒントで指定でき、サービスコンテナにより自動的に注入されます。例えば:The handle
method may also type-hint dependencies, and they will be automatically injected by the service container[/docs/{{version}}/container]. For example:
/**
* コマンド実行
*
* @return void
*/
public function handle(BillingGateway $billing)
{
// ポッドキャスト購入ロジックを処理する…
}
コマンドデスパッチDispatching Commands
コマンドはできました。ではどうやってデスパッチするのでしょう?もちろん、handle
メソッドを直接呼び出すこともできます。しかし、コマンドをLaravel「コマンドバス」を通して実行する方法には、後ほど説明する多くの利点があります。So, once we have created a command, how do we dispatch it? Of course, we could call the handle
method directly; however, dispatching the command through the Laravel "command bus" has several advantages which we will discuss later.
アプリケーションのベースコントローラーを覗いてみれば、DispatchesCommands
トレイトが見つかります。このトレイトは、コントローラーからdispatch
メソッドを呼び出せるようにします。以下のようにです。If you glance at your application's base controller, you will see the DispatchesCommands
trait. This trait allows us to call the dispatch
method from any of our controllers. For example:
public function purchasePodcast($podcastId)
{
$this->dispatch(
new PurchasePodcast(Auth::user(), Podcast::findOrFail($podcastId))
);
}
コマンドバスはコマンド実行の面倒を見ます。そして、呼びだされたIoCコンテナがhandle
メソッドへ必要な依存を注入します。The command bus will take care of executing the command and calling the IoC container to inject any needed dependencies into the handle
method.
お望みであれば、どんなクラスへでもIlluminate\Foundation\Bus\DispatchesCommands
トレイトを付け加えることができます。クラスのコンストラクターを通して、コマンドバスのインスタンスを受け取りたいのであれば、Illuminate\Contracts\Bus\Dispatcher
インターフェイスをタイプヒントに指定してください。最後に、Bus
ファサードを使い、簡単にコマンドをデスパッチする方法を紹介します。You may add the Illuminate\Foundation\Bus\DispatchesCommands
trait to any class you wish. If you would like to receive a command bus instance through the constructor of any of your classes, you may type-hint the Illuminate\Contracts\Bus\Dispatcher
interface. Finally, you may also use the Bus
facade to quickly dispatch commands:
Bus::dispatch(
new PurchasePodcast(Auth::user(), Podcast::findOrFail($podcastId))
);
リクエストからコマンドのプロパティーをマップするMapping Command Properties From Requests
HTTPリクエストの変数をコマンドへマップしたいと考えるのは当然でしょう。それぞれのリクエストを手動で無理やりマップする代わりに、Laravelでは簡単に実現できるヘルパメソッドを用意しています。DispatchesCommands
トレイトで使用できる、dispatchFrom
メソッドを取り上げてみてみましょう。It is very common to map HTTP request variables into commands. So, instead of forcing you to do this manually for each request, Laravel provides some helper methods to make it a cinch. Let's take a look at the dispatchFrom
method available on the DispatchesCommands
trait:
$this->dispatchFrom('Command\Class\Name', $request);
このメソッドは指定されたコマンドクラスのコンストラクターを調べ、それからHTTPリクエスト(もしくは他のArrayAccess
オブジェクト)から変数を取り出し、必要なコマンドのコンストラクター引数を埋めます。ですから、もしコマンドクラスがコンストラクターで$firstName
変数を取る場合、コマンドバスはHTTPリクエストからfirstName
パラメーターを取り出そうとします。This method will examine the constructor of the command class it is given, and then extract variables from the HTTP request (or any other ArrayAccess
object) to fill the needed constructor parameters of the command. So, if our command class accepts a firstName
variable in its constructor, the command bus will attempt to pull the firstName
parameter from the HTTP request.
dispatchFrom
メソッドはさらに第3引数に配列を指定できます。この配列はリクエストからは埋められないコンストラクター引数を埋めるために使用されます。You may also pass an array as the third argument to the dispatchFrom
method. This array will be used to fill any constructor parameters that are not available on the request:
$this->dispatchFrom('Command\Class\Name', $request, [
'firstName' => 'Taylor',
]);
コマンドのキュー投入Queued Commands
コマンドバスは現在のリクエストサイクル中で、同期的にジョブを実行するだけではなく、Laravelのキュージョブとして非同期に実行する重要な手法も提供しています。では同期的に実行する代わりに、どうやってコマンドバスへ、バックグラウンド処理を行うためにジョブをキューに入れろと指示するのでしょうか。簡単です。最初に新しいコマンドを生成するときに、--queued
フラグをコマンドへ付けるだけです。The command bus is not just for synchronous jobs that run during the current request cycle, but also serves as the primary way to build queued jobs in Laravel. So, how do we instruct command bus to queue our job for background processing instead of running it synchronously? It's easy. Firstly, when generating a new command, just add the --queued
flag to the command:
php artisan make:command PurchasePodcast --queued
すぐ気がつくでしょうが、これによりいくつかの機能がコマンドに追加されます。すなわちIlluminate\Contracts\Queue\ShouldBeQueued
インターフェイスとSerializesModels
トレイトです。これらは、コマンドバスにコマンドをキューに投入するように指示し、同時にコマンドのプロパティーとしてストアされたEloquentモデルを優雅にシリアライズ、非シリアライズします。As you will see, this adds a few more features to the command, namely the Illuminate\Contracts\Queue\ShouldBeQueued
interface and the SerializesModels
trait. These instruct the command bus to queue the command, as well as gracefully serialize and deserialize any Eloquent models your command stores as properties.
もし、既に存在するコマンドをキュー実行のコマンドに変更したければ、単にIlluminate\Contracts\Queue\ShouldBeQueued
インターフェイスを手動でクラスへ実装してください。これはメソッドを含んでおらず、主にデスパッチャーがコマンドへ投入する「目印のインターフェイス」として使用しています。If you would like to convert an existing command into a queued command, simply implement the Illuminate\Contracts\Queue\ShouldBeQueued
interface on the class manually. It contains no methods, and merely serves as a "marker interface" for the dispatcher.
それから、コマンドを通常通り書いてください。それがバスへデスパッチされると、バックグラウンドで処理するため自動的にキューへ投入します。Then, just write your command normally. When you dispatch it to the bus that bus will automatically queue the command for background processing. It doesn't get any easier than that.
キューされるコマンドのインターフェイスの詳細は、キューのドキュメントを参照してください。For more information on interacting with queued commands, view the full queue documentation[/docs/{{version}}/queues].
コマンドパイプラインCommand Pipeline
ハンドラにディスパッチされる前に、「パイプライン」中の他のクラスへ、コマンドを通すことができます。コマンドパイプラインは、実行するのがコマンドという違いだけで、まるでHTTPミドルウェアのように動作します!例えば、コマンド全体の操作をデータベーストランザクションでラップしたり、実行をログしたりできます。Before a command is dispatched to a handler, you may pass it through other classes in a "pipeline". Command pipes work just like HTTP middleware, except for your commands! For example, a command pipe could wrap the entire command operation within a database transaction, or simply log its execution.
パイプをバスに追加するには、App\Providers\BusServiceProvider::boot
メソッドから、ディスパッチャーのpipeThrough
メソッドを呼び出します。To add a pipe to your bus, call the pipeThrough
method of the dispatcher from your App\Providers\BusServiceProvider::boot
method:
$dispatcher->pipeThrough(['UseDatabaseTransactions', 'LogCommand']);
コマンドのパイプは、ミドルウェアと同様に、handle
メソッドで定義します。A command pipe is defined with a handle
method, just like a middleware:
class UseDatabaseTransactions {
public function handle($command, $next)
{
return DB::transaction(function() use ($command, $next)
{
return $next($command);
});
}
}
コマンドパイプのクラスは、IoCコンテナを通じて依存解決されるため、コンストラクターでタイプヒントによる依存を指定することもできます。Command pipe classes are resolved through the IoC container[/docs/{{version}}/container], so feel free to type-hint any dependencies you need within their constructors.
コマンドパイプを「クロージャー」で定義することもできます。You may even define a Closure
as a command pipe:
$dispatcher->pipeThrough([function($command, $next)
{
return DB::transaction(function() use ($command, $next)
{
return $next($command);
});
}]);