-
- Notifications
You must be signed in to change notification settings - Fork 5.3k
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 1 commit
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
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| | @@ -24,16 +24,15 @@ Concepts | |
| .. image:: /_images/components/messenger/overview.png | ||
| | ||
| **Sender**: | ||
| Responsible for serializing and sending the message to _something_. This | ||
| 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 deserializing and forwarding the messages to handler(s). This | ||
| Responsible for deserializing and forwarding 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. | ||
| Responsible for handling messages using the business logic applicable to the messages. | ||
| | ||
| Bus | ||
| --- | ||
| | @@ -65,15 +64,14 @@ Example:: | |
| | ||
| .. note: | ||
| | ||
| Every middleware need to implement the ``MiddlewareInterface`` interface. | ||
| Every middleware needs to implement the ``MiddlewareInterface`` interface. | ||
| | ||
| Handlers | ||
| -------- | ||
| | ||
| Once dispatched to the bus, messages will be handled by a "message handler". 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. "dispatched" is confusing here: according to the Bus section, the bus is responsible for dispatching messages; and the method used to get a message on to the bus is 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. No, it's | ||
| message handler is a PHP callable (i.e. a function or an instance of a class) | ||
| 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. we could add a link to the official doc here:
| ||
| that will do the required processing for your message. It _might_ return a | ||
| result:: | ||
| that will do the required processing for your message:: | ||
| | ||
| namespace App\MessageHandler; | ||
| | ||
| | @@ -90,8 +88,8 @@ result:: | |
| Adapters | ||
| -------- | ||
| | ||
| The communication with queuing system or third parties is delegated to | ||
| libraries for now. | ||
| In order to send and receive messages, you will have to configure an adapter. An | ||
| adapter will be responsible of communicating with your message broker or 3rd parties. | ||
| | ||
| Your own sender | ||
| ~~~~~~~~~~~~~~~ | ||
| | @@ -169,18 +167,23 @@ First, create your receiver:: | |
| $this->filePath = $filePath; | ||
| } | ||
| | ||
| public function receive() : \Generator | ||
| public function receive(callable $handler) : void | ||
| { | ||
| $ordersFromCsv = $this->serializer->deserialize(file_get_contents($this->filePath), 'csv'); | ||
| | ||
| foreach ($ordersFromCsv as $orderFromCsv) { | ||
| yield new NewOrder($orderFromCsv['id'], $orderFromCsv['account_id'], $orderFromCsv['amount']); | ||
| $handler(new NewOrder($orderFromCsv['id'], $orderFromCsv['account_id'], $orderFromCsv['amount'])); | ||
| } | ||
| } | ||
| | ||
| public function stop(): void | ||
| { | ||
| // noop | ||
| } | ||
| } | ||
| | ||
| Same bus received and sender | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
| Receiver and Sender on the same bus | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
| | ||
| To allow us to receive and send messages on the same bus and prevent an infinite | ||
| loop, the message bus is equipped with the ``WrapIntoReceivedMessage`` middleware. | ||
| | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| | @@ -28,6 +28,7 @@ you need it, like in a controller:: | |
| // src/Controller/DefaultController.php | ||
| namespace App\Controller; | ||
| | ||
| use App\Message\SendNotification; | ||
| use Symfony\Bundle\FrameworkBundle\Controller\Controller; | ||
| use Symfony\Component\Messenger\MessageBusInterface; | ||
| | ||
| 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 add something like 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 👍 | ||
| | @@ -91,7 +92,7 @@ the messenger component, the following configuration should have been created: | |
| framework: | ||
| messenger: | ||
| adapters: | ||
| default: "%env(MESSENGER_DSN)%" | ||
| amqp: "%env(MESSENGER_DSN)%" | ||
| | ||
| .. code-block:: bash | ||
| | ||
| | @@ -100,17 +101,17 @@ the messenger component, the following configuration should have been created: | |
| MESSENGER_DSN=amqp://guest:guest@localhost:5672/%2f/messages | ||
| ###< symfony/messenger ### | ||
| | ||
| This is enough to allow you to route your message to the ``messenger.default_adapter`` | ||
| adapter. This will also configure the following for you: | ||
| This is enough to allow you to route your message to the ``amqp``. This will also | ||
| configure the following services for you: | ||
| | ||
| 1. A ``messenger.default_sender`` sender to be used when routing messages | ||
| 2. A ``messenger.default_receiver`` receiver to be used when consuming messages. | ||
| 1. A ``messenger.sender.amqp`` sender to be used when routing messages. | ||
| 2. A ``messenger.receiver.amqp`` receiver to be used when consuming messages. | ||
| | ||
| Routing | ||
| ------- | ||
| | ||
| Instead of calling a handler, you have the option to route your message(s) to a | ||
| sender. Part of an adapter, it is responsible of sending your message somewhere. | ||
| sender. Part of an adapter, it is responsible for sending your message somewhere. | ||
| You can configure which message is routed to which sender with the following | ||
| configuration: | ||
| | ||
| | @@ -119,40 +120,39 @@ configuration: | |
| framework: | ||
| messenger: | ||
| routing: | ||
| 'My\Message\Message': messenger.default_sender # Or another sender service name | ||
| 'My\Message\Message': amqp # The name of the defined adapter | ||
| | ||
| Such configuration would only route the ``My\Message\Message`` message to be | ||
| asynchronous, the rest of the messages would still be directly handled. | ||
| 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. Instead of two sentences, one before the config example and one after, how about just one:- "The following configuration shows how you can route a class of messages to a sender, leaving other classes of messages to be passed to their respective handlers." 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. Also, routing to It should be 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. It's less clear with a single example IMHO. I've updated the sender configuration 👍 | ||
| | ||
| If you want to do route all the messages to a queue by default, you can use such | ||
| configuration: | ||
| You can route all classes of message to a sender using an asterisk instead of a class name: | ||
| | ||
| .. code-block:: yaml | ||
| | ||
| framework: | ||
| messenger: | ||
| routing: | ||
| 'My\Message\MessageAboutDoingOperationalWork': messenger.operations_sender | ||
| '*': messenger.default_sender | ||
| 'My\Message\MessageAboutDoingOperationalWork': another_adapter | ||
| '*': amqp | ||
| | ||
| Note that you can also route a message to multiple senders at the same time: | ||
| A class of message can also be routed to a multiple senders by specifying a list: | ||
| ||
| | ||
| .. code-block:: yaml | ||
| | ||
| framework: | ||
| messenger: | ||
| routing: | ||
| 'My\Message\ToBeSentToTwoSenders': [messenger.default_sender, messenger.audit_sender] | ||
| 'My\Message\ToBeSentToTwoSenders': [amqp, audit] | ||
| | ||
| Last but not least you can also route a message while still calling the handler | ||
| on your application by having a ``null`` sender: | ||
| By specifying a ``null`` sender, you can also route a class of messages to a sender | ||
| while still having them passed to their respective handler: | ||
| | ||
| .. code-block:: yaml | ||
| | ||
| framework: | ||
| messenger: | ||
| routing: | ||
| 'My\Message\ThatIsGoingToBeSentAndHandledLocally': [messenger.default_sender, ~] | ||
| 'My\Message\ThatIsGoingToBeSentAndHandledLocally': [amqp, ~] | ||
| | ||
| Consuming messages | ||
| ------------------ | ||
| | @@ -163,81 +163,51 @@ like this: | |
| | ||
| .. code-block:: terminal | ||
| | ||
| $ bin/console messenger:consume-messages messenger.default_receiver | ||
| $ bin/console messenger:consume-messages amqp | ||
| | ||
| The first argument is the receiver's service name. It might have been created by | ||
| your ``adapters`` configuration or it can be your own receiver. | ||
| | ||
| Registering your middleware | ||
| --------------------------- | ||
| | ||
| The message bus is based on a set of middleware. If you are un-familiar with the concept, | ||
| look at the :doc:`Messenger component docs </components/messenger>`. | ||
| | ||
| To register your middleware, use the ``messenger.middleware`` tag as in the | ||
| following example: | ||
| | ||
| .. code-block:: xml | ||
| | ||
| <service id="Your\Own\Middleware"> | ||
| <tag name="messenger.middleware" /> | ||
| </service> | ||
| | ||
| Your own Adapters | ||
| ----------------- | ||
| | ||
| Learn how to build your own adapters within the Component's documentation. Once | ||
| you have built your classes, you can register your adapter factory to be able to | ||
| use it via a DSN in the Symfony application. | ||
| Once you have written your adapter's sender and receiver, you can register your | ||
| adapter factory to be able to use it via a DSN in the Symfony application. | ||
| | ||
| Create your adapter Factory | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
| | ||
| You need to give FrameworkBundle the opportunity to create your adapter from a | ||
| DSN. You will need an adapter factory:: | ||
| | ||
| use Symfony\Component\Messenger\Adapter\Factory\AdapterInterface; | ||
| use Symfony\Component\Messenger\Adapter\Factory\AdapterFactoryInterface; | ||
| | ||
| class YourAdapterFactory implements AdapterFactoryInterface | ||
| { | ||
| public function create(string $dsn): AdapterInterface | ||
| { | ||
| return new YourAdapter(/* ... */); | ||
| } | ||
| | ||
| public function supports(string $dsn): bool | ||
| { | ||
| return 0 === strpos($dsn, 'my-adapter://'); | ||
| } | ||
| } | ||
| | ||
| The ``YourAdaper`` class need to implement the ``AdapterInterface``. It | ||
| will like the following example:: | ||
| | ||
| use Symfony\Component\Messenger\Adapter\Factory\AdapterInterface; | ||
| use Symfony\Component\Messenger\Transport\ReceiverInterface; | ||
| use Symfony\Component\Messenger\Transport\SenderInterface; | ||
| | ||
| class YourAdapter implements AdapterInterface | ||
| class YourAdapterFactory implements AdapterFactoryInterface | ||
| { | ||
| public function receiver(): ReceiverInterface | ||
| public function createReceiver(string $dsn, array $options): ReceiverInterface | ||
| { | ||
| return new YourReceiver(/* ... */); | ||
| } | ||
| | ||
| public function sender(): SenderInterface | ||
| public function createSender(string $dsn, array $options): SenderInterface | ||
| { | ||
| return new YourSender(/* ... */); | ||
| } | ||
| | ||
| public function supports(string $dsn, array $options): bool | ||
| { | ||
| return 0 === strpos($dsn, 'my-adapter://'); | ||
| } | ||
| } | ||
| | ||
| Register your factory | ||
| 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.
| ||
| ~~~~~~~~~~~~~~~~~~~~~ | ||
| | ||
| .. code-block:: xml | ||
| | ||
| <service id="Your\Adapter\Factory"> | ||
| <service id="Your\Adapter\YourAdapterFactory"> | ||
| <tag name="messenger.adapter_factory" /> | ||
| </service> | ||
| | ||
| | @@ -254,10 +224,10 @@ named adapter using your own DSN: | |
| adapters: | ||
| yours: 'my-adapter://...' | ||
| | ||
| This will give you access to the following services: | ||
| In addition of being able to route your messages to the ``yours`` sender, this | ||
| will give you access to the following services: | ||
| | ||
| 1. ``messenger.yours_adapter``: the instance of your adapter. | ||
| 2. ``messenger.yours_receiver`` and ``messenger.yours_sender``, the | ||
| receiver and sender created by the adapter. | ||
| 1. ``messenger.sender.hours``: the sender. | ||
| 2. ``messenger.receiver.hours``: the receiver. | ||
| | ||
| .. _`enqueue's adapter`: 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.
a messager handler, broker or a third-party API.Reasons:
message handlerwhich is likely to be more commonly known will make it more accessible