diff --git a/_images/components/messenger/overview.png b/_images/components/messenger/overview.png new file mode 100644 index 00000000000..074255b4667 Binary files /dev/null and b/_images/components/messenger/overview.png differ diff --git a/components/messenger.rst b/components/messenger.rst new file mode 100644 index 00000000000..31f73bf8c37 --- /dev/null +++ b/components/messenger.rst @@ -0,0 +1,192 @@ +.. index:: + single: Messenger + single: Components; Messenger + +The Messenger Component +======================= + + The Messenger component helps applications send and receive messages to/from other applications or via message queues. + +Installation +------------ + +.. code-block:: terminal + + $ composer require symfony/messenger + +Alternatively, you can clone the ``_ repository. + +.. include:: /components/require_autoload.rst.inc + +Concepts +-------- + +.. image:: /_images/components/messenger/overview.png + +**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 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. + +Bus +--- + +The bus is used to dispatch messages. The behaviour 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 handle) + +Example:: + + use App\Message\MyMessage; + use Symfony\Component\Messenger\MessageBus; + use Symfony\Component\Messenger\HandlerLocator; + use Symfony\Component\Messenger\Middleware\HandleMessageMiddleware; + + $bus = new MessageBus([ + new HandleMessageMiddleware(new HandlerLocator([ + MyMessage::class => $handler, + ])), + ]); + + $result = $bus->handle(new MyMessage(/* ... */)); + +.. note: + + Every middleware needs to implement the ``MiddlewareInterface`` interface. + +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:: + + namespace App\MessageHandler; + + use App\Message\MyMessage; + + class MyMessageHandler + { + public function __invoke(MyMessage $message) + { + // Message processing... + } + } + +Adapters +-------- + +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 +~~~~~~~~~~~~~~~ + +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 App\Message\ImportantAction; + use Symfony\Component\Message\SenderInterface; + + 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( + '

Important action

Made by '.$message->getUsername().'

', + 'text/html' + ) + ); + } + } + +Your own receiver +~~~~~~~~~~~~~~~~~ + +A receiver is responsible for receiving messages from a source and dispatching +them to the application. + +Let's say 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:: + + namespace App\MessageReceiver; + + use App\Message\NewOrder; + use Symfony\Component\Message\ReceiverInterface; + use Symfony\Component\Serializer\SerializerInterface; + + class NewOrdersFromCsvFile implements ReceiverInterface + { + private $serializer; + private $filePath; + + public function __construct(SerializerInteface $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) { + $handler(new NewOrder($orderFromCsv['id'], $orderFromCsv['account_id'], $orderFromCsv['amount'])); + } + } + + public function stop(): void + { + // noop + } + } + +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. +It will wrap the received messages into ``ReceivedMessage`` objects and the +``SendMessageMiddleware`` middleware will know it should not route these +messages again to an adapter. diff --git a/index.rst b/index.rst index b9e4a8d316b..ab5212eff54 100644 --- a/index.rst +++ b/index.rst @@ -41,6 +41,7 @@ Topics frontend http_cache logging + messenger performance profiler routing diff --git a/messenger.rst b/messenger.rst new file mode 100644 index 00000000000..0dbd7c15e9f --- /dev/null +++ b/messenger.rst @@ -0,0 +1,233 @@ +.. index:: + single: Messenger + +How to Use the Messenger +======================== + +Symfony's Messenger provide a message bus and some routing capabilities to send +messages within your application and through adapaters such as message queues. +Before using it, read the :doc:`Messenger component docs ` +to get familiar with its concepts. + +Installation +------------ + +In applications using :doc:`Symfony Flex `, run this command to +install messenger before using it: + +.. code-block:: terminal + + $ composer require messenger + +Using the Messenger Service +--------------------------- + +Once enabled, the ``message_bus`` service can be injected in any service where +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; + + class DefaultController extends Controller + { + public function index(MessageBusInterface $bus) + { + $bus->dispatch(new SendNotification('A string to be sent...')); + } + } + +Registering Handlers +-------------------- + +In order to do something when your message is dispatched, you need to create a +message handler. It's a class with an `__invoke` method:: + + // src/MessageHandler/MyMessageHandler.php + namespace App\MessageHandler; + + class MyMessageHandler + { + public function __invoke(MyMessage $message) + { + // do something with it. + } + } + +Once you've created your handler, you need to register it: + +.. code-block:: xml + + + + + +.. note:: + + If the message cannot be guessed from the handler's type-hint, use the + ``handles`` attribute on the tag. + +Adapters +-------- + +The communication with queuing system or third parties is delegated to +libraries for now. The built-in AMQP adapter allows you to communicate with +most of the AMQP brokers such as RabbitMQ. + +.. note:: + + If you need more message brokers, you should have a look to `Enqueue's adapter`_ + which supports things like Kafka, Amazon SQS or Google Pub/Sub. + +An adapter is registered using a "DSN", which is a string that represents the +connection credentials and configuration. By default, when you've installed +the messenger component, the following configuration should have been created: + +.. code-block:: yaml + + # config/packages/messenger.yaml + framework: + messenger: + adapters: + amqp: "%env(MESSENGER_DSN)%" + +.. code-block:: bash + + # .env + ###> symfony/messenger ### + MESSENGER_DSN=amqp://guest:guest@localhost:5672/%2f/messages + ###< symfony/messenger ### + +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.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 for sending your message somewhere. +You can configure which message is routed to which sender with the following +configuration: + +.. code-block:: yaml + + framework: + messenger: + routing: + '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. + +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': another_adapter + '*': amqp + +A class of message can also be routed to multiple senders by specifying a list: + +.. code-block:: yaml + + framework: + messenger: + routing: + 'My\Message\ToBeSentToTwoSenders': [amqp, audit] + +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': [amqp, ~] + +Consuming messages +------------------ + +Once your messages have been routed, you will like to consume your messages in most +of the cases. Do to so, you can use the ``messenger:consume-messages`` command +like this: + +.. code-block:: terminal + + $ 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. + +Your own Adapters +----------------- + +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\AdapterFactoryInterface; + use Symfony\Component\Messenger\Transport\ReceiverInterface; + use Symfony\Component\Messenger\Transport\SenderInterface; + + class YourAdapterFactory implements AdapterFactoryInterface + { + public function createReceiver(string $dsn, array $options): ReceiverInterface + { + return new YourReceiver(/* ... */); + } + + 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 +~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: xml + + + + + +Use your adapter +~~~~~~~~~~~~~~~~ + +Within the ``framework.messenger.adapters.*`` configuration, create your +named adapter using your own DSN: + +.. code-block:: yaml + + framework: + messenger: + adapters: + yours: 'my-adapter://...' + +In addition of being able to route your messages to the ``yours`` sender, this +will give you access to the following services: + +1. ``messenger.sender.hours``: the sender. +2. ``messenger.receiver.hours``: the receiver. + +.. _`enqueue's adapter`: https://github.com/sroze/enqueue-bridge pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy