Using the Command pattern in Drupal

Implementing the Command pattern in Drupal using the Symfony Messenger component.

# drupal # symfony # design patterns # webprofiler

In object-oriented programming, the Command pattern is a behavioral design pattern that allows you to encapsulate all the information needed to perform a task in a single object (called the command). The command is then dispatched to a bus that will find the appropriate handler to execute the action.

To implement the Command pattern in our PHP applications we can leverage the Symfony Messenger component.

Drupal includes a large set of Symfony components, unfortunately the Messenger component is not one of them. However, we can easily add it to our Drupal project using a contrib module.

Just type the following commands in your terminal:

composer require drupal/messenger
drush pm:enable symfony_messenger

At the time of writing the Messenger module requires a patch to be applied to Drupal core. You can find the patch here, and you can apply it with Composer (more information here).

Adding a command and a handler

A command is a Plain Old PHP Object (POPO) that contains all the information needed to perform an action. In this example we will create a command that will carry a set of parameters required to send an email.

<?php

namespace Drupal\my_module\Command;

class SendEmailCommand {

    /**
     * @param string $to
     * @param string $subject
     * @param string $body
     */
    public function __construct(
        private readonly string $to,
        private readonly string $subject,
        private readonly string $body
    ) {
    }

    /**
     * @return string
     */
    public function getTo(): string {
        return $this->to;
    }

    /**
     * @return string
     */
    public function getSubject(): string {
        return $this->subject;
    }

    /**
     * @return string
     */
    public function getBody(): string {
        return $this->body;
    }
}

An instance of the command object should never be modified, so we can use the readonly keyword to make the properties immutable. The constructor will be used to set the properties, and the getters will be used to retrieve the properties.

To execute the command we need to create a handler.

Continuing with our example, we’ll create a handler that will send an email to a user.

<?php

namespace Drupal\my_module\Command;

use Drupal\Core\Mail\MailManagerInterface;
use Drupal\my_module\Command\SendEmailCommand;

class SendEmailCommandHandler {

    /**
     * @param MailManagerInterface $mailManager
     */
    public function __construct(
        private readonly MailManagerInterface $mailManager
    ) {
    }

    /**
     * @param SendEmailCommand $command
     */
    public function __invoke(SendEmailCommand $command) {
        $params = [
            'subject' => $command->getSubject(),
            'body' => $command->getBody(),
        ];

        $this->mailManager->mail('my_module', 'send_email', $command->getTo(), 'en', $params);
    }
}

A command handler is a class that implements the __invoke method. The __invoke method will be called by the Messenger module when a command is dispatched, passing the command as a parameter. The type hint on the $command parameter will tell the Messenger module that this handler will handle SendEmailCommand commands.

The last step is to register the handler in the services.yml file:

services:
  my_module.command_handler.send_email:
    class: Drupal\my_module\Command\SendEmailCommandHandler
    arguments: ['@plugin.manager.mail']
    tags:
      - { name: messenger.message_handler }

Notice that the handler is tagged with messenger.message_handler, this is how the Messenger module will register the handler to the bus.

Finally, we can dispatch the command.

Dispatching a command

To dispatch a command we need to retrieve a MessageBus, maybe in a controller:

<?php

namespace Drupal\my_module\Controller;

use Drupal\Core\Controller\ControllerBase;
use Drupal\my_module\Command\SendEmailCommand;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Messenger\MessageBusInterface;

class MyController extends ControllerBase {

    /**
     * @param MessageBusInterface $messageBus
     */
    public function __construct(
        private readonly MessageBusInterface $messageBus
    ) {
    }

    /**
     * @param ContainerInterface $container
     *
     * @return static
     */
    public static function create(ContainerInterface $container) {
        return new static(
            $container->get('symfony_messenger.bus.default')
        );
    }

    /**
     * @param Request $request
     *
     * @return array
     */
    public function sendEmail(Request $request): array {
        $this->messageBus->dispatch(
            new SendEmailCommand(
                'test@example.com',
                'Hello world',
                'This is a test email',
            );
        );

        return [
            '#markup' => $this->t('Email sent'),
        ];
    }
}

In the previous code we’ve retrieved the MessageBus service (symfony_messenger.bus.default), and we’ve used it to dispatch a SendEmailCommand. The Messenger module will find the appropriate handler (SendEmailCommandHandler) and execute it.

We’ve successfully decoupled the code that creates and dispatches the command to the code that sends the email. If you need to perform the same task from multiple places (maybe in a bulk operation and in a Drush command), you can simply create an instance of a command class and dispatch it to the bus. This will make your code more maintainable and testable.

Retrieving values from the command

In our example we’ve used the SendEmailCommand to carry the parameters needed to send an email. However, we may need to retrieve some values from the command after it has been dispatched. For example, we may need to retrieve the ID of the newly created entity after a command has been executed. In that case we must retrieve the Envelope object, returned by the dispatch method, and then extract the response from the it:

<?php

use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Stamp\HandledStamp;

public function sendEmail(Request $request): array {
    $envelope = $this->messageBus->dispatch(
        new CreateEntityCommand(
            'Example title',
            'Lorem ipsum...',
        );
    );

    $stamp = $envelope->last(HandledStamp::class);
    $response = $stamp->getResult();

    return [
        '#markup' => $this->t('Entity created, ID: @id', ['@id' => $response->id()]),
    ];
}

The Messenger module provide an handful Trait to simplify this process, the ResponseTrait with its getHandlerResult method.

Differences between commands and events

Events are occurrences that have already taken place and are irreversible. In contrast, commands represents intentions or desired actions in the future, which can be declined or rejected. Events typically have multiple recipients or observers, whereas a command is directed to a singular recipient. It’s common that, after a command has been executed, one or more events are dispatched to notify other parts of the system that something has happened.

Take a look to this video from Martin Fowler, where he explains the differences between commands and events: https://www.youtube.com/watch?v=STKCRSUsyP0.

WebProfiler integration

The Messenger module integrates with WebProfiler, so you can see all the messages that have been dispatched in the “Messenger” widget on the WebProfiler toolbar. All the details about dispatched messages are then available in the “Messenger” panel on the WebProfiler dashboard.

Conclusion

The Symfony Messenger component is a powerful tool, and it has many more features that we haven’t covered in this article, some of them are not available in Drupal yet (for example, the ability to handle commands asynchronously). We’re working on adding more features to the Messenger module, and we plan to release a stable version in the next months. Stay tuned!