-
-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Symfony Messenger component documentation #9437
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
585491d
b40bc71
b26de80
88ba8fe
5c828e4
31a56ee
2493c90
bcfae23
25c0b6e
a15752b
fb88abc
509e149
3fb973c
32403ea
c5306b8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,284 @@ | ||
.. index:: | ||
single: Message | ||
single: Components; Message | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Messenger? |
||
|
||
The Message Component | ||
===================== | ||
|
||
The Message component helps application to send and receive messages | ||
to/from other applications or via | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This phrase looks unfinished. Also, it's not a very descriptive description. Should we mention at least "message bus" or some key concept so the readers know undoubtely what problem does this component solve? Thanks! |
||
|
||
Installation | ||
------------ | ||
|
||
.. code-block:: terminal | ||
|
||
$ composer require symfony/message | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be |
||
|
||
Alternatively, you can clone the `<https://github.com/symfony/message>`_ repository. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be |
||
|
||
.. include:: /components/require_autoload.rst.inc | ||
|
||
Concepts | ||
-------- | ||
|
||
.. image:: /_images/components/message/overview.png | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. messenger |
||
|
||
**Sender**: | ||
Responsible for serializing and sending the message to _something_. This | ||
something can be a message broker or a third party API for example. | ||
|
||
**Receiver**: | ||
Responsible for deserializing and forwarding the messages to handler(s). This | ||
can be a message queue puller or an API endpoint for example. | ||
|
||
**Handler**: | ||
Given a received message, contains the user business logic related to the | ||
message. In practice, that is just a PHP callable. | ||
|
||
Bus | ||
--- | ||
|
||
The bus is used to dispatch messages. MessageBus' behavior is in its ordered | ||
middleware stack. When using the message bus with Symfony's FrameworkBundle, the | ||
following middlewares are configured for you: | ||
|
||
#. ``LoggingMiddleware`` (logs the processing of your messages) | ||
#. ``SendMessageMiddleware`` (enables asynchronous processing) | ||
#. ``HandleMessageMiddleware`` (calls the registered handle) | ||
|
||
Example:: | ||
|
||
use App\Message\MyMessage; | ||
|
||
$result = $this->get('message_bus')->handle(new MyMessage(/* ... */)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe use DI to get the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added an example of constructing it manually. |
||
|
||
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. It _might_ return a | ||
result:: | ||
|
||
namespace App\MessageHandler; | ||
|
||
use App\Message\MyMessage; | ||
|
||
class MyMessageHandler | ||
{ | ||
public function __invoke(MyMessage $message) | ||
{ | ||
// Message processing... | ||
} | ||
} | ||
|
||
.. code-block:: xml | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you should add yaml config format, too There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This has to be moved within the FrameworkBundle's documentation actually. |
||
|
||
<service id="App\Handler\MyMessageHandler"> | ||
<tag name="message_handler" /> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should be |
||
</service> | ||
|
||
.. note:: | ||
|
||
If the message cannot be guessed from the handler's type-hint, use the | ||
``handles`` attribute on the tag. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think best would be an example. Also mention that the guessing is done on the |
||
|
||
Asynchronous messages | ||
~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
Using the Message Component is useful to decouple your application but it also | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Messenger |
||
very useful when you want to do some asynchronous processing. This means that | ||
your application will produce a message to a queuing system and consume this | ||
message later in the background, using a _worker_. | ||
|
||
Adapters | ||
~~~~~~~~ | ||
|
||
The communication with queuing system or third parties is delegated to | ||
libraries for now. You can use one of the following adapters: | ||
|
||
#. `PHP Enqueue bridge`_ to use one of their 10+ compatible queues such as | ||
RabbitMq, Amazon SQS or Google Pub/Sub. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
Routing | ||
------- | ||
|
||
When doing asynchronous processing, the key is to route the message to the right | ||
sender. As the routing is application-specific and not message-specific, the | ||
configuration can be made within the ``framework.yaml`` configuration file as | ||
well: | ||
|
||
.. code-block:: yaml | ||
|
||
framework: | ||
message: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
routing: | ||
'My\Message\MessageAboutDoingOperationalWork': my_operations_queue_sender | ||
|
||
Such configuration would only route the ``MessageAboutDoingOperationalWork`` | ||
message to be asynchronous, the rest of the messages would still be directly | ||
handled. | ||
|
||
If you want to do route all the messages to a queue by default, you can use such | ||
configuration: | ||
|
||
.. code-block:: yaml | ||
|
||
framework: | ||
message: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. messenger |
||
routing: | ||
'My\Message\MessageAboutDoingOperationalWork': my_operations_queue_sender | ||
'*': my_default_sender | ||
|
||
Note that you can also route a message to multiple senders at the same time: | ||
|
||
.. code-block:: yaml | ||
|
||
framework: | ||
message: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. messenger |
||
routing: | ||
'My\Message\AnImportantMessage': [my_default_sender, my_audit_sender] | ||
|
||
Same bus received and sender | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
To allow us to receive and send messages on the same bus and prevent a loop, the | ||
message bus is equipped with the ``WrapIntoReceivedMessage`` received. It will | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm having a hard time parsing this sentence, especially the last word 😕 . Either it should be a noun, or it should be "the received/obtained WrapIntoReceivedMessage object" |
||
wrap the received messages into ``ReceivedMessage`` objects and the | ||
``SendMessageMiddleware`` middleware will know it should not send these messages. | ||
|
||
Your own sender | ||
--------------- | ||
|
||
Using the ``SenderInterface``, you can easily create your own message sender. | ||
Let's say you already have an ``ImportantAction`` message going through the | ||
message bus and handled by a handler. Now, you also want to send this message as | ||
an email. | ||
|
||
First, create your sender:: | ||
|
||
namespace App\MessageSender; | ||
|
||
use Symfony\Component\Message\SenderInterface; | ||
use App\Message\ImportantAction; | ||
|
||
class ImportantActionToEmailSender implements SenderInterface | ||
{ | ||
private $toEmail; | ||
private $mailer; | ||
|
||
public function __construct(\Swift_Mailer $mailer, string $toEmail) | ||
{ | ||
$this->mailer = $mailer; | ||
$this->toEmail = $toEmail; | ||
} | ||
|
||
public function send($message) | ||
{ | ||
if (!$message instanceof ImportantAction) { | ||
throw new \InvalidArgumentException(sprintf('Producer 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' | ||
) | ||
); | ||
} | ||
} | ||
|
||
Then, register your sender service: | ||
|
||
.. code-block:: yaml | ||
|
||
services: | ||
App\MessageSender\ImportantActionToEmailSender: | ||
arguments: | ||
- "@mailer" | ||
- "%to_email%" | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please remove blank line here |
||
tags: | ||
- message.sender | ||
|
||
Finally, route your important message to the sender: | ||
|
||
.. code-block:: yaml | ||
|
||
framework: | ||
message: | ||
routing: | ||
'App\Message\ImportantAction': [App\MessageSender\ImportantActionToEmailSender, ~] | ||
|
||
.. note:: | ||
|
||
This example shows you how you can at the same time send your message and | ||
directly handle it using a ``null`` (``~``) sender. | ||
|
||
Your own receiver | ||
----------------- | ||
|
||
A consumer is responsible for receiving messages from a source and dispatching | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. consumer -> receiver ? |
||
them to the application. | ||
|
||
Let's say you already proceed some "orders" on your application using a | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. processed != proceed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "in" your application? |
||
``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 your custom CSV consumer and Symfony will do the rest. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "to do" => "to do is create your…" or "to write is your…" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. consumer -> receiver? |
||
|
||
First, create your receiver:: | ||
|
||
namespace App\MessageReceiver; | ||
|
||
use Symfony\Component\Message\ReceiverInterface; | ||
use Symfony\Component\Serializer\SerializerInterface; | ||
|
||
use App\Message\NewOrder; | ||
|
||
class NewOrdersFromCsvFile implements ReceiverInterface | ||
{ | ||
private $serializer; | ||
private $filePath; | ||
|
||
public function __construct(SerializerInteface $serializer, string $filePath) | ||
{ | ||
$this->serializer = $serializer; | ||
$this->filePath = $filePath; | ||
} | ||
|
||
public function receive() : \Generator | ||
{ | ||
$ordersFromCsv = $this->serializer->deserialize(file_get_contents($this->filePath), 'csv'); | ||
|
||
foreach ($ordersFromCsv as $orderFromCsv) { | ||
yield new NewOrder($orderFromCsv['id'], $orderFromCsv['account_id'], $orderFromCsv['amount']); | ||
} | ||
} | ||
} | ||
|
||
Then, register your receiver service: | ||
|
||
.. code-block:: yaml | ||
|
||
services: | ||
App\MessageReceiver\NewOrdersFromCsvFile: | ||
arguments: | ||
- "@serializer" | ||
- "%new_orders_csv_file_path%" | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please remove blank line here |
||
tags: | ||
- message.receiver | ||
|
||
Finally, use your consumer: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. consumer -> receiver? |
||
|
||
.. code-block:: terminal | ||
|
||
$ bin/console message:consume App\MessageReceived\NewOrdersFromCsvFile | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typo here, should be |
||
|
||
.. _`PHP Enqueue bridge`: https://github.com/sroze/enqueue-bridge |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Messenger?