Today i want to show you how to use The Messenger Component of Symfony.

Very useful when your project implements the CQRS pattern.

With an example, simplicity of use and practicality will be evident.

Let’s assume that we have a service like this one:

<?php namespace App\Domain\Service\Customer; use App\Domain\Command\Customer\DeleteCustomerCommand; use App\Domain\CommandHandler\Customer\DeleteCustomerCommandHandlerInterface; use App\Domain\Exception\Customer\CriteriaNotAllowedException; use App\Domain\Query\Customer\GetCustomerListQuery; use App\Domain\Query\Customer\GetCustomerQuery; use App\Domain\QueryHandler\Customer\GetCustomerListQueryHandlerInterface; use App\Domain\QueryHandler\Customer\GetCustomerQueryHandlerInterface; class CustomerService implements CustomerServiceInterface { /** * @var GetCustomerListQueryHandlerInterface */ private $customerListQueryHandler; /** * @var GetCustomerQueryHandlerInterface */ private $customerQueryHandler; /** * @var DeleteCustomerCommandHandlerInterface */ private $deleteCustomerHandler; public function __construct( GetCustomerListQueryHandlerInterface $customerListQueryHandler, GetCustomerQueryHandlerInterface $customerQueryHandler, DeleteCustomerCommandHandlerInterface $deleteCustomerHandler ) { $this->customerListQueryHandler = $customerListQueryHandler; $this->customerQueryHandler = $customerQueryHandler; $this->deleteCustomerHandler = $deleteCustomerHandler; } /** * @param array $criteria * * @return mixed * * @throws CriteriaNotAllowedException */ public function getByCriteria(array $criteria) { $getCustomerQueryList = new GetCustomerListQuery(); foreach ($criteria as $key => $value) { $method = 'set' . ucfirst($key); if (!method_exists($getCustomerQueryList, $method)) { throw new CriteriaNotAllowedException(sprintf('Parameter %s not allowed', $key)); } $getCustomerQueryList->$method($value); } return $this->customerListQueryHandler->handle($getCustomerQueryList); } public function get(string $id) { return $this->customerQueryHandler->handle( new GetCustomerQuery($id) ); } public function delete(string $id) { $this->deleteCustomerHandler->handle( new DeleteCustomerCommand($id) ); } }

A very simple service that is used to search users by criteria and to get or remove a specific user.

To correctly apply the CQRS pattern, I had to inject in the service the Query and Command classes, and the interfaces that are implemented by the handlers (yes, because we also apply the concept of hexagonal architecture).

Now, if we use the Messenger Component of Symfony

Install component: composer require symfony/messenger Configure component into config/services.yaml: App\Infrastructure\QueryHandler\: resource: '../src/Infrastructure/QueryHandler/*' public: true tags: [messenger.message_handler] App\Infrastructure\CommandHandler\: resource: '../src/Infrastructure/CommandHandler/*' public: true tags: [messenger.message_handler] Be careful not to forget the tag messenger.message_handler Change the service as follows: <?php namespace App\Domain\Service\Customer; use App\Domain\Command\Customer\DeleteCustomerCommand; use App\Domain\Exception\Customer\CriteriaNotAllowedException; use App\Domain\Query\Customer\GetCustomerListQuery; use App\Domain\Query\Customer\GetCustomerQuery; use Symfony\Component\Messenger\MessageBusInterface; class CustomerService implements CustomerServiceInterface { /** @var MessageBusInterface */ private $messageBus; public function __construct( MessageBusInterface $messageBus ) { $this->messageBus = $messageBus; } /** * @param array $criteria * * @return mixed * * @throws CriteriaNotAllowedException */ public function getByCriteria(array $criteria) { $getCustomerQueryList = new GetCustomerListQuery(); foreach ($criteria as $key => $value) { $method = 'set' . ucfirst($key); if (!method_exists($getCustomerQueryList, $method)) { throw new CriteriaNotAllowedException(sprintf('Parameter %s not allowed', $key)); } $getCustomerQueryList->$method($value); } return $this->messageBus->dispatch($getCustomerQueryList); } public function get(string $id) { return $this->messageBus->dispatch( new GetCustomerQuery($id) ); } public function delete(string $id) { $this->messageBus->dispatch( new DeleteCustomerCommand($id) ); } }

That’s all, Handlers and Handler interfaces they don’t change. Below is an example of how they are implemented:

Command:

<?php namespace App\Domain\Command\Customer; class DeleteCustomerCommand { /** * @var string */ private $id; public function __construct(string $id) { $this->id = $id; } /** * @return string */ public function getId() : string { return $this->id; } }

Handler Interface:

<?php namespace App\Domain\CommandHandler\Customer; use App\Domain\Command\Customer\DeleteCustomerCommand; interface DeleteCustomerCommandHandlerInterface { public function __invoke(DeleteCustomerCommand $deleteCustomerCommand); }

Handler:

<?php namespace App\Infrastructure\CommandHandler\Orm\Customer; use App\Domain\Command\Customer\DeleteCustomerCommand; use App\Domain\CommandHandler\Customer\DeleteCustomerCommandHandlerInterface; use App\Infrastructure\Exception\Customer\CustomerNotFoundException; use App\Infrastructure\Orm\Repository\CustomerRepository; class DeleteCustomerCommandHandler implements DeleteCustomerCommandHandlerInterface { /** * @var CustomerRepository */ private $customerRepository; public function __construct(CustomerRepository $customerRepository) { $this->customerRepository = $customerRepository; } /** * @param DeleteCustomerCommand $deleteCustomerCommand * * @throws CustomerNotFoundException * @throws \Doctrine\ORM\ORMException * @throws \Doctrine\ORM\OptimisticLockException */ public function __invoke(DeleteCustomerCommand $deleteCustomerCommand) { $customer = $this->customerRepository->findOneBy(['id' => $deleteCustomerCommand->getId()]); if (!$customer) { throw new CustomerNotFoundException('Customer not found'); } $this->customerRepository->remove($customer); } }