Skip to content

Latest commit

 

History

History
154 lines (105 loc) · 7.57 KB

bus.md

File metadata and controls

154 lines (105 loc) · 7.57 KB

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