Command Bus
Introduction
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.
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.
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
The Artisan CLI can generate new command classes using the make:command
command:
php artisan make:command PurchasePodcast
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; /** * 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)); } }
The handle
method may also type-hint dependencies, and they will be automatically injected by the service container. For example:
/** * Execute the command. * * @return void */public function handle(BillingGateway $billing){ // Handle the logic to purchase the podcast...}
Dispatching Commands
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.
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)) );}
The command bus will take care of executing the command and calling the IoC container to inject any needed dependencies into the handle
method.
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
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);
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.
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
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
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.
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.
Command Pipeline
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.
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']);
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); }); } }
Command pipe classes are resolved through the IoC 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); });}]);