イントロダクション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);
});
}]);