DEV Community

Mateusz Cholewka
Mateusz Cholewka

Posted on

Restore missing Command Bus in new Laravel versions

Command Bus is a very powerful pattern, which can help you in decoupling your application modules. You can in an easy way separate small actions, and trigger them using a single dependency called CommandBus.

The cause why this pattern is able to decouple your modules is that actually, the module that is dispatching the command doesn’t have to care about the actual implementation of the action. No matter if that’s a simple DB query, complicated calculations, or calling another remote service. Using this pattern will help you also if you want to implement one of CQS and CQRS patterns.

The CommandBus pattern

This pattern requires three elements:

Command - is a small class that name should describe the action that you want to call. I should store all data required to execute actual action.

class ReserveResource
{
    public function __construct(
        private ResourceId $resourceId,
        private Period $period
    ) {}

    public function getId(): ResourceId
    {
        return $this->resourceId;
    }

    public function getPeriod(): Period
    {
        return $this->period;
    }
}
Enter fullscreen mode Exit fullscreen mode

Handler - it's the actual implementation of the action. It should implement handle or __invoke method that is handling a Command.

class ReserveResourceHandler
{
    public function __construct(
        private ResourceRepository $resourceRepository
    ) {}

    public function handle(ReserveResource $reserveResource): void
    {
        $resource = $this->resourceRepository->find($reserveResource->getId());
        $resource->reserve($reserveResource->getPeriod());
        $this->resourceRepository->save($resource);
    }
}
Enter fullscreen mode Exit fullscreen mode

Bus - it’s the last element of this puzzle. This is the element that keeps all definitions of relations between Commands and Handlers, and it’s also able to run right Handler. That’s the part that is generic and you can use ready implementations, so implementing that on your own doesn’t make sense.

Example usage of CommandBus:

$bus = new CommandBus(); // Create new CommandBus instance
$bus->map([ReserveResource::class, ReserveResourceHandler::class]); // Map command and handler

// Create command
$reserveResource = new ReserveResource(
    'affa2136-5b90-4f7c-933e-73cffc39f1d9',
    new Period('2020-12-06 15:30', '2020-12-06 16:30')
);

$bus->dispatch($reserveResource); // Dispatch command
Enter fullscreen mode Exit fullscreen mode

Bus implementation

There are some PHP, ready implementations of CommandBus. One of them is the component of the popular Symfony framework, it’s called Messenger. You can also use one of The PHP League packages called Tactician. But if you’re programming in Laravel and you need simple as possible CommandBus implementation, there is an option to use the existed part of the framework.

Older versions of Laravel had an implementation of CommandBus out of the box. Today the name of this feature was changed, and it’s split to Events and Jobs / Queues, but the event can be queued too…??? I do not fully understand this separation, but I know it may be helpful when you need to implement something quickly.

Ok, never mind let's take a look at Laravel 5 documentation:

Bus::dispatch(
    new PurchasePodcast(Auth::user(), Podcast::findOrFail($podcastId))
);
Enter fullscreen mode Exit fullscreen mode

Ok, this is a usage of Facade called Bus. Let's try to find out if this class is still available in Laravel code. Here it is still available.
Ok, but I don't like facades, I think that the name of them is badly used, and Laravel facades are like hidden dependencies of your classes. It's always better to inject the dependency from the DI container. So I was investigating the code, and I got to the Illuminate\Bus\Dispatcher class. And that's the part of Laravel framework that is implemented like Command Bus functionality.

I’m not recommending using this class directly. This class is not specified in documentation and it could be removed or replaced one day in Laravel (it probably won't). Also, you may want to improve the functionalities of your command bus, so using this one directly, will cause the problem in replacing it in all the places of usage.

Let's create the Adapter that will be also our safe proxy. But also do not include our Adapter directly. Let's use an interface that will allow us to juggle with implementations.

interface CommandBus
{
    public function dispatch($command): void;
    public function map(array $map): void;
}
Enter fullscreen mode Exit fullscreen mode

And next the implementation:

use Illuminate\Bus\Dispatcher;

class IlluminateCommandBus implements CommandBus
{
    public function __construct(private Dispatcher $bus) {}

    public function dispatch($command): void
    {
        $this->bus->dispatch($command);
    }

    public function map(array $map): void
    {
        $this->bus->map($map);
    }
}
Enter fullscreen mode Exit fullscreen mode

Next, we can declare it in the DI container in the service provider. Remember to register it as a Singleton. It's important to get the same instance of the bus in every place of the application.

public function register()
{
    $this->app->singleton(CommandBus::class, IlluminateCommandBus::class);
}
Enter fullscreen mode Exit fullscreen mode

Now you can define the Commands and Handlers map, and the Command Bus it's ready to use.

So first register

public function register()
{
    /** @var CommandBus $bus */
    $bus = $this->app->make(CommandBus::class);
    $bus->map([
        ReserveResource::class => ReserveResourceHandler::class,
        TurnOnResource::class => TurnOnResourceHandler::class,
        WithdrawResource::class => WithdrawResourceHandler::class,
        CreateResource::class => CreateResourceHandler::class,
    ]);
}
Enter fullscreen mode Exit fullscreen mode

and use

class ReservationController
{
    public function __construct(private CommandBus $bus) {}

    public function reserve(int $id, ReserveRequest $request): void
    {
        $command = ReserveResource::fromRaw(
            $id,
            $request->get('from'),
            $request->get('to')
        );

        $this->bus->dispatch($command);
    }
}
Enter fullscreen mode Exit fullscreen mode

Discussion (1)

Collapse
carlosvazquez profile image
Carlos Vazquez

Why there's not comments?! Command Bus is an amazing pattern and should be implemented as a common in many projects.
Thanks for your post.