The Messenger Component
Edit this pageWarning: You are browsing the documentation for Symfony 4.1, which is no longer maintained.
Read the updated version of this page for Symfony 6.3 (the current stable version).
The Messenger Component
The Messenger component helps applications send and receive messages to/from other applications or via message queues.
The component is greatly inspired by Matthias Noback's series of blog posts about command buses and the SimpleBus project.
See also
This article explains how to use the Messenger features as an independent component in any PHP application. Read the How to Use the Messenger article to learn about how to use it in Symfony applications.
Installation
1
$ composer require symfony/messenger
Alternatively, you can clone the https://github.com/symfony/messenger repository.
Note
If you install this component outside of a Symfony application, you must
require the vendor/autoload.php
file in your code to enable the class
autoloading mechanism provided by Composer. Read
this article for more details.
Concepts
- Sender:
- Responsible for serializing and sending messages to something. This something can be a message broker or a third party API for example.
- Receiver:
- Responsible for retrieving, deserializing and forwarding messages to handler(s). This can be a message queue puller or an API endpoint for example.
- Handler:
-
Responsible for handling messages using the business logic applicable to the messages.
Handlers are called by the
HandleMessageMiddleware
middleware. - Middleware:
- Middleware can access the message and its wrapper (the envelope) while it is dispatched through the bus. Literally "the software in the middle", those are not about core concerns (business logic) of an application. Instead, they are cross cutting concerns applicable throughout the application and affecting the entire message bus. For instance: logging, validating a message, starting a transaction, ... They are also responsible for calling the next middleware in the chain, which means they can tweak the envelope, by adding items to it or even replacing it, as well as interrupt the middleware chain.
- Envelope
- Messenger specific concept, it gives full flexibility inside the message bus, by wrapping the messages into it, allowing to add useful information inside through envelope items.
- Envelope Items
- Piece of information you need to attach to your message: serializer context to use for transport, markers identifying a received message or any sort of metadata your middleware or transport layer may use.
Bus
The bus is used to dispatch messages. The behavior of the bus is in its ordered middleware stack. The component comes with a set of middleware that you can use.
When using the message bus with Symfony's FrameworkBundle, the following middleware are configured for you:
- LoggingMiddleware (logs the processing of your messages)
- SendMessageMiddleware (enables asynchronous processing)
- HandleMessageMiddleware (calls the registered handler(s))
Example:
1 2 3 4 5 6 7 8 9 10 11 12
use App\Message\MyMessage;
use Symfony\Component\Messenger\MessageBus;
use Symfony\Component\Messenger\Handler\Locator\HandlerLocator;
use Symfony\Component\Messenger\Middleware\HandleMessageMiddleware;
$bus = new MessageBus([
new HandleMessageMiddleware(new HandlerLocator([
MyMessage::class => $handler,
])),
]);
$bus->dispatch(new MyMessage(/* ... */));
Note
Every middleware needs to implement the MiddlewareInterface.
Handlers
Once dispatched to the bus, messages will be handled by a "message handler". A message handler is a PHP callable (i.e. a function or an instance of a class) that will do the required processing for your message:
1 2 3 4 5 6 7 8 9 10 11
namespace App\MessageHandler;
use App\Message\MyMessage;
class MyMessageHandler
{
public function __invoke(MyMessage $message)
{
// Message processing...
}
}
Adding Metadata to Messages (Envelopes)
If you need to add metadata or some configuration to a message, wrap it with the
Envelope class. For example, to set the
serialization groups used when the message goes through the transport layer, use
the SerializerConfiguration
envelope:
1 2 3 4 5 6 7 8
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Transport\Serialization\SerializerConfiguration;
$bus->dispatch(
(new Envelope($message))->with(new SerializerConfiguration([
'groups' => ['my_serialization_groups'],
]))
);
At the moment, the Symfony Messenger has the following built-in envelope items:
- SerializerConfiguration, to configure the serialization groups used by the transport.
- ValidationConfiguration, to configure the validation groups used when the validation middleware is enabled.
- ReceivedMessage, an internal item that marks the message as received from a transport.
Instead of dealing directly with the messages in the middleware you can receive the envelope by implementing the EnvelopeAwareInterface marker, like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
use Symfony\Component\Messenger\Asynchronous\Transport\ReceivedMessage;
use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
use Symfony\Component\Messenger\EnvelopeAwareInterface;
class MyOwnMiddleware implements MiddlewareInterface, EnvelopeAwareInterface
{
public function handle($envelope, callable $next)
{
// $envelope here is an `Envelope` object, because this middleware
// implements the EnvelopeAwareInterface interface.
if (null !== $envelope->get(ReceivedMessage::class)) {
// Message just has been received...
// You could for example add another item.
$envelope = $envelope->with(new AnotherEnvelopeItem(/* ... */));
}
return $next($envelope);
}
}
The above example will forward the message to the next middleware with an additional envelope item if the message has just been received (i.e. has the `ReceivedMessage` item). You can create your own items by implementing EnvelopeAwareInterface.
Note
Any envelope item must be php serializable if going through transport using the Serializer base serializer.
Transports
In order to send and receive messages, you will have to configure a transport. A transport will be responsible for communicating with your message broker or 3rd parties.
Your own Sender
Using the SenderInterface,
you can create your own message sender.
Imagine that you already have an ImportantAction
message going through the
message bus and being handled by a handler. Now, you also want to send this
message as an email.
First, create your sender:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
namespace App\MessageSender;
use App\Message\ImportantAction;
use Symfony\Component\Messenger\Transport\SenderInterface;
use Symfony\Component\Messenger\Envelope;
class ImportantActionToEmailSender implements SenderInterface
{
private $mailer;
private $toEmail;
public function __construct(\Swift_Mailer $mailer, string $toEmail)
{
$this->mailer = $mailer;
$this->toEmail = $toEmail;
}
public function send(Envelope $envelope)
{
$message = $envelope->getMessage();
if (!$message instanceof ImportantAction) {
throw new \InvalidArgumentException(sprintf('This transport only supports "%s" messages.', ImportantAction::class));
}
$this->mailer->send(
(new \Swift_Message('Important action made'))
->setTo($this->toEmail)
->setBody(
'<h1>Important action</h1><p>Made by '.$message->getUsername().'</p>',
'text/html'
)
);
}
}
Your own Receiver
A receiver is responsible for getting messages from a source and dispatching them to the application.
Imagine you already processed some "orders" in your application using a
NewOrder
message. Now you want to integrate with a 3rd party or a legacy
application but you can't use an API and need to use a shared CSV file with new
orders.
You will read this CSV file and dispatch a NewOrder
message. All you need to
do is to write your custom CSV receiver and Symfony will do the rest.
First, create your receiver:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
namespace App\MessageReceiver;
use App\Message\NewOrder;
use Symfony\Component\Messenger\Transport\ReceiverInterface;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Messenger\Envelope;
class NewOrdersFromCsvFileReceiver implements ReceiverInterface
{
private $serializer;
private $filePath;
public function __construct(SerializerInterface $serializer, string $filePath)
{
$this->serializer = $serializer;
$this->filePath = $filePath;
}
public function receive(callable $handler): void
{
$ordersFromCsv = $this->serializer->deserialize(file_get_contents($this->filePath), 'csv');
foreach ($ordersFromCsv as $orderFromCsv) {
$order = new NewOrder($orderFromCsv['id'], $orderFromCsv['account_id'], $orderFromCsv['amount']);
$handler(new Envelope($order));
}
}
public function stop(): void
{
// noop
}
}
Receiver and Sender on the same Bus
To allow sending and receiving messages on the same bus and prevent an infinite loop, the message bus will add a ReceivedMessage envelope item to the message envelopes and the SendMessageMiddleware middleware will know it should not route these messages again to a transport.