Jan 2, 2015 | laravel, 5.0, commands, laravel 5

Laravel 5.0 - Commands & Handlers

Series

This is a series of posts on New Features in Laravel 5.0.

!
Warning: This post is over a year old. I don't always update old posts with new information, so some of this information may be out of date.

As you’ve probably read me mention on Twitter, I’ve paused on blogging new Laravel 5 features to try to give Taylor some space to develop with a little less of “this Git commit happened; therefore this is how it’ll be forever!” But he’s announced a new feature today, so I’d consider this one pretty likely to stick around.

The new feature set is all around Commands, which already exist in Laravel, but are getting a lot of new love in Laravel 5.0.

I’ll be using examples in this blog post from a new application I’m working on called SaveMyProposals, which allows conference speakers to save talk proposals.

What is a command? A command handler? A command bus?

You can learn about the concept of a command, a command handler, and a command bus in more depth from Shawn McCool, but essentially:

A command is a simple object that’s meant to be a message. It contains only the information you need in order to do something. Our example here will be “Duplicate Talk Command”, which is an imaginary command that our system (a controller or an Artisan command, likely) will dispatch any time a user has chosen to duplicate a talk proposal. The duplicate talk command will have all of the properties set on it that we need to duplicate a talk—likely either a serialized Talk object or a TalkId.

A command handler is a class tasked with doing something in response to the command. A command can be passed through one or many handlers; each pull out important information from the command and do something in response.

A command bus is the system that allows you to dispatch (create and send off) commands, that matches commands to their handlers, and that makes everything play together. Often folks write their own command busses, but Laravel is providing one out of the box so we don't need to worry about this in this article.

Using Commands in Laravel

Before we get into the entire structure of how to use commands in Laravel 5, let’s look at what the end use case will look like. Imagine a user visits a route something like savemyproposals.com/talks/12345/duplicate, which routes them to TalkController@duplicate(12345).

We'll have a controller method to handle it:

// Http\Controllers\TalkController
...
    public function duplicate($talkId)
    {
        $talk = Talk::findOrFail($talkId);

        $this->dispatch(new DuplicateTalkCommand($talk));

        // Depending on implementation, this could also just be:
        // $this->dispatch(new DuplicateTalkCommand($talkId));
    }

Then we'll have a command:

// Commands\DuplicateTalkCommand
...

class DuplicateTalkCommand extends Command
{
    public $talk;

    public function __construct(Talk $talk)
    {
        $this->talk = $talk;
    }
}

And a command handler:

// Handlers\Commands\DuplicateTalkCommandHandler
...

class DuplicateTalkCommandHandler
{
    public function handle(DuplicateTalkCommand $command)
    {
        // Do something with $command
        dd($command);
    }
}

As you can see, our controller creates a DuplicateTalkCommand with the necessary information, dispatches it using the built-in command bus dispatcher, and then it’s handled (automatically) by its handler.

Architecture

OK, so let’s look first at where those commands and handlers live, and then how we generate them.

Folders

There are two new folders in app/: Commands and Handlers, and Handlers has two subfolders: Commands and Events (which shows us we can look forward to Event handling, too.)

    app/
        Commands/
        Handlers/
            Commands/
            Events/

As you can guess, Commands go in the app/Commands folder, and Command Handlers go in the app/Handlers/Commands/ folder—with the exact same name as their Command, but with Handler appended to the end.

Artisan

Thankfully, you don’t have to do this on your own. There’s a new Artisan generator that’ll make it simple to create your own command:

$ php artisan make:command DuplicateTalkCommand

By default, this creates a self-handling command that isn't pushed to the queue. Pass this the --handler flag to generate a handler, and the --queued flag to make it queued.

This generates two files: a Command (app\Commands\DuplicateTalkCommand.php) and a Handler (app\Handlers\Commands\DuplicateTalkCommandHandler.php) (if you passed the --handler flag), and the Handler’s handle method is generated automatically typehinted for its paired Command.

Basic workflow

So, in order to create a new DuplicateTalkCommand, you'd do the following:

  1. php artisan make:command DuplicateTalkCommand
  2. Edit DuplicateTalkCommand and give it a public property of $talk and set it to be injected via the constructor
  3. Edit DuplicateTalkCommandHandler and write its handle() method to do whatever you actually want to have happen--likely using a repository or other database access layer to duplicate the talk and save the duplicate.
  4. Dispatch the command, likely in your controller or an Artisan command.

That's it! You're now using commands in Laravel 5.0! Everything from here on out are just nitty gritty details about queues, traits, interfaces, and other special considerations and tricks.

Queues

Queueing Commands

If you want any command to be queued every time you dispatch it (instead of operating synchronously), all you need to do is have it implement the ShouldBeQueued interface. Laravel will read that as a signal to queue it, and it’ll be pushed onto whichever queue you’re using instead of running it inline.

...
class DuplicateTalkCommand extends Command implements ShouldBeQueued
{

This means it’s now even easier than ever to integrate queues into your normal workflow.

InteractsWithQueue trait

Adding this trait to your command will give you all of the features on your command that you’re used to in traditional queue commands: $command->release(), $command->delete(), $command->attempts(), etc.

...
class DuplicateTalkCommand extends Command implements ShouldBeQueued, InteractsWithQueue
{

SerializesModels trait

If you pass an Eloquent model in as a property, like I did in the example above, and you want to queue your commands (instead of just letting them run synchronously), it might cause you some trouble because of how Eloquent models serialize. But there’s a trait you can add to the command named SerializesModels that will smooth out any of those problems. Just use it at the top of your command:

...
class DuplicateTalkCommand extends Command implements ShouldBeQueued
{
    use SerializesModels;

The Dispatcher

DispatchesCommands trait

You’ll notice that, in the example above, we were able to just use $this->dispatch() in the controller. This is controller magic, but it’s magic that’s accessible via a DispatchesCommands trait, which you can apply to anything other than a controller.

So, if you want a service class, for example, to be able to use $this->dispatch() in its methods, just use the DispatchesCommands trait on your service class and you’re good to go.

Injecting the bus

If you’d rather be more direct and clear with your use of the bus, instead of using the trait, you can actually inject the bus into your constructor or method. Just inject Illuminate\Contracts\Bus\Dispatcher and you’ll have a bus ready to dispatch from.

    ...
    public function __construct(\Illuminate\Contracts\Bus\Dispatcher $bus)
    {
        $this->bus = $bus;
    }

    public function doSomething()
    {
        $this->bus->dispatch(new Command);
    }

dispatchFrom(command::class, $request or any arrayAccessible)

We’ve already seen that $bus->dispatch(new Command(params...)) is the simplest way to dispatch a command. But sometimes the parameter list for a new command can get larger and larger—for example, when your command is handling a Form Request.

...
class CreateTalkCommand extends Command
{
    public function __construct($title, $description, $outline, $organizer_notes, $length, $type, $level)
    {

Keeping up the instantiation call for this could get crazy.

    $this->dispatch(new CreateTalkCommand($input['title'], $input['description'], $input['outline'], $input['organizer_notes'], $input['length'], $input['type'], $input['level']));

Hm, take a look at that. Often we’re just passing in properties with the same key, accessed from an array or a Request object, right? Thankfully, there’s a workaround to make that very easy:

    $this->dispatchFrom(‘NameOfCommand’, $objectThatImplementsPHPArrayAccessible);

That’s it! So you could do this:

    $this->dispatchFrom(CreateTalkCommand::class, $input);

... or even this:

    public function doSomethingInController(Request $request)
    {
        $this->dispatchFrom(CreateTalkCommand::class, $request);

Laravel will auto-map the keys on that array or arrayAccessible object to the same property names in your command constructor.

Self-handling commands

If you’d rather avoid the hassle of a Command and a CommandHandler, you can make a Command “self-handling”, which just means that there’s only a single handler for it, and that handler is the command itself. Just add a handle() method on that command, and have the command implement the SelfHandling interface:

...
class DuplicateTalkCommand extends Command implements SelfHandling
{
...
    public function handle()
    {
        // Do stuff with $this->talk
    }

Miscellaneous Notes

  • Command handlers are resolved out of the IOC container, which means you can inject repositories, service classes, or whatever else into the constructor of your command handler in order to use them in the handle() method.
  • Almost all of these traits and interfaces live in the Illuminate\Contracts\Bus or Illuminate\Contracts\Queue namespaces. E.g. Illuminate\Contracts\Bus\SelfHandling.
  • If your command is queued, you don’t have to perform $command->delete() at the end of your handler. As long as your handler doesn’t throw any exceptions, Laravel will assume it completed properly and will delete the item off the queue.

Whew.

That was a lot. If I missed anything or wasn’t particularly clear, please let me know—there’s a lot to cover in here, and I’m on vacation so I’m doing it in fits and spurts. But I hope this gives you a good idea of how it’s all going to work—and like I said, Taylor’s video on Laracasts covers all this and more, and there’s plenty more to come.


Comments? I'm @stauffermatt on Twitter


Tags: laravel  •  5.0  •  commands  •  laravel 5


This is part of a series of posts on New Features in Laravel 5.0:

  1. Sep 10, 2014 | laravel, 5.0, laravel 5
  2. Sep 10, 2014 | laravel, 5.0, laravel 5
  3. Sep 12, 2014 | laravel, laravel 5, 5.0
  4. Sep 20, 2014 | laravel, 5.0, laravel 5
  5. Sep 28, 2014 | laravel, laravel 5, 5.0
  6. Sep 30, 2014 | laravel, 5.0, laravel 5
  7. Oct 9, 2014 | laravel, 5.0, laravel 5
  8. Oct 10, 2014 | laravel, 5.0, laravel 5
  9. Oct 10, 2014 | laravel, 5.0, laravel 5
  10. Nov 20, 2014 | laravel, 5.0, laravel 5
  11. Jan 2, 2015 | laravel, 5.0, commands, laravel 5
  12. Jan 16, 2015 | laravel, laravel 5
  13. Jan 19, 2015 | laravel 5, laravel
  14. Jan 21, 2015 | laravel, events, 5.0, laravel 5
  15. Jan 26, 2015 | laravel, laravel 5
  16. Feb 1, 2015 | laravel, laravel 5
  17. Feb 14, 2015 | laravel 5, laravel, eloquent

Subscribe

For quick links to fresh content, and for more thoughts that don't make it to the blog.