TonyDev - Blog

PHP, JS, WEB, Software, Development

Laravel 5 Command-Oriented Approach

| Comments

A lot of shiny things around the Laravel world that I would like to talk, so I chose the new CommandBus that Laravel 5 brings by default. To start I’d like to say that it’s pretty damn cool.

Command-Oriented Architecture

I’m not gonna explain in details this approach, but I’m going to give a brief introduction on the topic and link a few more content at the bottom.

Basically, we should describe our application into commands to make our intentions explicit. Let’s say you have a subscription system on your app, you’d have a “SubscribeUserCommand” class, or something similar, that maps to its own handler, in this case “SubscribeUserCommandHandler”. Doing so you actually decouple your application from the transport layer (HTTP, cli, queue job, event handler, etc..). It means that you can dispatch this command from a controller or a console command (cli) with no trouble.

Laravel way

We used to implement this approach using some packages (see laracasts/commander) which is actually pretty neat and works like a charm. However, Laravel 5 brings it’s own CommandBus with a plus: it can handle commands (AND EVENTS!!!) in background (queues).

A typical command looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// file: app/Commands/SubscribeUserCommand.php
<?php namespace App\Commands;

use App\Subscriptions\MembershipType;

class SubscribeUserCommand extends Command
{
    public $userId;
    public $membershipType;

    public function __construct($userId, MembershipType $membershipType)
    {
        $this->userId = $userId;
        $this->membershipType = $membershipType;
    }
}

Then you should have a handler like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// file: app/Handlers/Commands/SubscribeUserCommandHandler.php
<?php namespace App\Handlers\Commands;

use App\Commands\SubscribeUserCommand;
use Illuminate\Contracts\Events\Dispatcher;
use App\Payment\PaymentInterface;

class SubscribeUserCommandHandler
{
    private $userRepository;
    private $events;
    private $payment;

    public function __construct(UserRepository $userRepository, Dispatcher $events, PaymentInterface $payment)
    {
        $this->userRepository = $userRepository;
        $this->events = $events;
        $this->payment = $payment;
    }

    public function handle(SubscribeUserCommand $command)
    {
        $user = $this->userRepository->find($command->userId);

        $user->subscribe($command->membershipType, $this->payment);

        $this->dispatchEvents($user->releaseEvents());
    }


    /**
     * @param array $events
     * @return void
     */
    private function dispatchEvents(array $events)
    {
        foreach ($events as $event)
            $this->events->fire($event);
    }
}

Which you can dispatch, let’s say, from your controller like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// file: app/Http/Controllers/SubscriptionsController.php
<?php namespace App\Http\Controllers;

use Illuminate\Contracts\Auth\Guard;
use App\Commands\SubscribeUserCommand;

class SubscriptionControllers extends Controller
{
    private $auth;

    public function __construct(Guard $auth)
    {
        $this->middleware('auth');
        $this->auth = $auth;
    }

    public function subscribe(SubscribeUserRequest $request)
    {
        $command = new SubscribeUserCommand(
            $this->auth->user()->id,
            MembershipType::build($request->get("membership_type"))
        );

        $this->dispatch($command);

        return redirect()->route("home");
    }
}

The dispatch method is inherited from the Controller class (which uses the Illuminate\Foundation\Bus\DispatchesCommands) and it maps commands to handlers. Cool stuff. This example works synchronously. If you need to handle the command in background (queue jobs) you just have to implement the Illuminate\Contracts\Queue\ShouldBeQueued interface on your command, like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// file: app/Commands/SubscribeUserCommand.php
<?php namespace App\Commands;

use App\Subscriptions\MembershipType;
use Illuminate\Contracts\Queue\ShouldBeQueued;

class SubscribeUserCommand extends Command implements ShouldBeQueued
{
    public $userId;
    public $membershipType;

    public function __construct($userId, MembershipType $membershipType)
    {
        $this->userId = $userId;
        $this->membershipType = $membershipType;
    }
}

That is it! Well, actually you have to setup the queue config on config/queue.php, but I’m making a point here.

Handling Events in background

As I said, it is also possible to handle events in background, let’s see an example. Let’s assume your User you have a subscribe named constructor on your model that builds the user instance and saves it (Eloquent/ActiveRecord). Your model should look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// folder: app/User.php
<?php namespace App;

use Illuminate\Database\Eloquent\Model;
use App\Events\UserSubscribedEvent;
use App\Subscriptions\MembershipType;
use App\Payment\PaymentInterface;

class User extends Model
{
    // ...
    public function subscribe(MembershipType $membershipType, PaymentInterface $payment)
    {
        $payment->purchaseSubscription($this, $membershipType);

        $this->subscription()->create($membershipType->toArray());

        $this->raise( new UserSubscribedEvent($this->id, $membershipType) );

        return $this;
    }
    // ...
}

Your event class is just a DTO and looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// file: app/Events/UserSubscribedEvent.php
<?php namespace App\Events;

use Illuminate\Queue\SerializesModels;
use App\Subscriptions\MembershipType;

class UserSubscribedEvent extends Event
{
    use SerializesModels;

    public $userId;
    public $membershipType;

    public function __construct($userId, MembershipType $membershipType)
    {
        $this->userId = $userId;
        $this->membershipType = $membershipType;
    }
}

Then you have a handler like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// file: app/Handlers/Events/UserSubscribedEventHandler;
<?php namespace App\Handlers\Events;

use App\Events\UserSubscribedEvent;
use App\Mailers\UserMailer;

class UserSubscribedEventHandler
{
    public function __construct(UserMailer $mailer, UserRepository $userRepository)
    {
        $this->mailer = $mailer;
        $this->userRepository = $userRepository;
    }

    public function handle(UserSubscribedEvent $event)
    {
        $user = $this->userRepository->find($event->userId);
        $this->mailer->sendTo($user, $this->buildMessage($event->membershipType));
    }

    // ... the buildMessage should be private or protected
}

To register your handler just go to app/Providers/EventServiceProvider.php and add your listener to the $listen property, like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php namespace App\Providers;

use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

class EventServiceProvider extends ServiceProvider {

    /**
     * The event handler mappings for the application.
     *
     * @var array
     */
    protected $listen = [
        \App\Events\UserSubscribedEvent::class => [
            \App\Handlers\Events\UserSubscribedEventHandler::class
        ]
    ];

}

This action is executed synchronously, it means that your user is waiting for the event handler to act before being redirected to the application.

To handle the event in background you just have to implement the same Illuminate\Contracts\Queue\ShouldBeQueued interface on your event handler class, like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// file: app/Handlers/Events/UserSubscribedEventHandler;
<?php namespace App\Handlers\Events;

use App\Events\UserSubscribedEvent;
use App\Mailers\UserMailer;
use Illuminate\Contracts\Queue\ShouldBeQueued;

class UserSubscribedEventHandler implements ShouldBeQueued
{
    public function __construct(UserMailer $mailer, UserRepository $userRepository)
    {
        $this->mailer = $mailer;
        $this->userRepository = $userRepository;
    }

    public function handle(UserSubscribedEvent $event)
    {
        $user = $this->userRepository->find($event->userId);
        $this->mailer->sendTo($user, $this->buildMessage($event->membershipType));
    }

    // ... the buildMessage should be private or protected
}

Oh, by the way, you just give the event class to the event dispatcher, like so (using the Facade):

1
2
3
4
5
6
<?php

// somewhere in your application
use App\Events\UserSubscribedEvent;

Event::fire(new UserSubscribedEvent($userId, $membershipType));

Conclusion

That is it. To sum up, I like to think that Commands can change state, while Events just react to these state changes and if an Event handler has to change anything it MUST do it through Commands.

This command bus looks pretty cool. Fun fact: you can have multiple event listeners/handlers where some of them executes synchronously and others execute asynchronously. I loved it, to make it work before we had to have an event listener that add a job to the queue and then handle the event on the job handler. Now it’s pretty damn simple.

Useful resources

Comments