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.
$ 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
<?php namespace App\Message; final class WelcomeEmail { public function __construct( public readonly string $email, public readonly string $name, ) { } }
<?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.
<?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; } }
$ 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.
$ 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 #####
$ php bin/console app:welcome-email a@example.com Alex [OK] Handled welcome email for Alex <a@example.com>