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);
});
}]);