How to handle a Symfony Messenger message

Symfony Messenger keeps application work behind a small message object and a handler that reacts when the message is dispatched. It is useful when a controller, command, or service should request work without carrying the implementation details inline.

A synchronous Messenger message is the smallest version of the pattern. With no transport routing, Symfony calls the handler in the same process, which makes handler discovery and payload shape easy to verify before moving the message onto a queue.

MakerBundle can generate the message and handler skeletons, and an application console command gives a repeatable dispatch trigger. Returning a short value from the handler keeps the proof local to the command output before any asynchronous transport is introduced.

Steps to handle a Symfony Messenger message:

  1. Create the message and handler skeleton.
    $ php bin/console make:message WelcomeEmail
    
     Which transport do you want to route your message to? [[no transport]]:
      [0] [no transport]
      [1] async
      [2] failed
     > 0
    
     created: src/Message/WelcomeEmail.php
     created: src/MessageHandler/WelcomeEmailHandler.php
    
     Success!

    Choose [no transport] for the first synchronous handler proof. A routed transport belongs in the queue configuration after the handler works locally.
    Related: How to configure a Symfony Messenger queue

  2. Add the data that the message carries.
    WelcomeEmail.php
    <?php
     
    namespace App\Message;
     
    final class WelcomeEmail
    {
        public function __construct(
            public readonly string $email,
            public readonly string $name,
        ) {
        }
    }
  3. Add the handler logic.
    WelcomeEmailHandler.php
    <?php
     
    namespace App\MessageHandler;
     
    use App\Message\WelcomeEmail;
    use Symfony\Component\Messenger\Attribute\AsMessageHandler;
     
    #[AsMessageHandler]
    final class WelcomeEmailHandler
    {
        public function __invoke(WelcomeEmail $message): string
        {
            return sprintf('Handled welcome email for %s <%s>', $message->name, $message->email);
        }
    }

    The AsMessageHandler attribute and the WelcomeEmail type hint let autoconfiguration register the handler for that message class.

  4. Create a console command that dispatches the message.
    WelcomeEmailCommand.php
    <?php
     
    namespace App\Command;
     
    use App\Message\WelcomeEmail;
    use Symfony\Component\Console\Attribute\AsCommand;
    use Symfony\Component\Console\Command\Command;
    use Symfony\Component\Console\Input\InputArgument;
    use Symfony\Component\Console\Input\InputInterface;
    use Symfony\Component\Console\Output\OutputInterface;
    use Symfony\Component\Console\Style\SymfonyStyle;
    use Symfony\Component\Messenger\MessageBusInterface;
    use Symfony\Component\Messenger\Stamp\HandledStamp;
     
    #[AsCommand(
        name: 'app:welcome-email',
        description: 'Dispatch a welcome email message',
    )]
    final class WelcomeEmailCommand extends Command
    {
        public function __construct(
            private MessageBusInterface $bus,
        ) {
            parent::__construct();
        }
     
        protected function configure(): void
        {
            $this
                ->addArgument('email', InputArgument::REQUIRED, 'Recipient email address')
                ->addArgument('name', InputArgument::REQUIRED, 'Recipient display name')
            ;
        }
     
        protected function execute(InputInterface $input, OutputInterface $output): int
        {
            $io = new SymfonyStyle($input, $output);
            $envelope = $this->bus->dispatch(new WelcomeEmail(
                $input->getArgument('email'),
                $input->getArgument('name'),
            ));
     
            $handled = $envelope->last(HandledStamp::class);
            if (!$handled instanceof HandledStamp) {
                $io->error('The WelcomeEmail message was dispatched, but no handler returned a result.');
     
                return Command::FAILURE;
            }
     
            $io->success($handled->getResult());
     
            return Command::SUCCESS;
        }
    }
  5. Clear the dev cache so the new services are rediscovered.
    $ php bin/console cache:clear
    
     // Clearing the cache for the dev environment with debug true
    
     [OK] Cache for the "dev" environment (debug=true) was successfully cleared.
  6. Confirm that Messenger registered the handler on the default bus.
    $ php bin/console debug:messenger messenger.bus.default
    
    Messenger
    =========
    
    messenger.bus.default
    ---------------------
    
     The following messages can be dispatched:
    
     ------------------------------------------------------------------------
      App\Message\WelcomeEmail
          handled by App\MessageHandler\WelcomeEmailHandler
    
    ##### snipped #####
  7. Dispatch the message from the console command.
    $ php bin/console app:welcome-email a@example.com Alex
    
     [OK] Handled welcome email for Alex <a@example.com>