diff --git a/components/messenger.rst b/components/messenger.rst index ce2ab655153..d8a21b4e0f5 100644 --- a/components/messenger.rst +++ b/components/messenger.rst @@ -38,8 +38,8 @@ Concepts 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. + 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. @@ -55,7 +55,7 @@ are configured for you: #. ``LoggingMiddleware`` (logs the processing of your messages) #. ``SendMessageMiddleware`` (enables asynchronous processing) -#. ``HandleMessageMiddleware`` (calls the registered handle) +#. ``HandleMessageMiddleware`` (calls the registered handler) Example:: @@ -99,57 +99,54 @@ Adding Metadata to Messages (Envelopes) --------------------------------------- If you need to add metadata or some configuration to a message, wrap it with the -:class:`Symfony\\Component\\Messenger\\Envelope` class. For example, to set the -serialization groups used when the message goes through the transport layer, use -the ``SerializerConfiguration`` envelope:: +:class:`Symfony\\Component\\Messenger\\Envelope` class and add stamps. +For example, to set the serialization groups used when the message goes +through the transport layer, use the ``SerializerStamp`` stamp:: use Symfony\Component\Messenger\Envelope; - use Symfony\Component\Messenger\Transport\Serialization\SerializerConfiguration; + use Symfony\Component\Messenger\Stamp\SerializerStamp; $bus->dispatch( - (new Envelope($message))->with(new SerializerConfiguration([ + (new Envelope($message))->with(new SerializerStamp([ 'groups' => ['my_serialization_groups'], ])) ); -At the moment, the Symfony Messenger has the following built-in envelopes: +At the moment, the Symfony Messenger has the following built-in envelope stamps: -#. :class:`Symfony\\Component\\Messenger\\Transport\\Serialization\\SerializerConfiguration`, +#. :class:`Symfony\\Component\\Messenger\\Stamp\\SerializerStamp`, to configure the serialization groups used by the transport. -#. :class:`Symfony\\Component\\Messenger\\Middleware\\Configuration\\ValidationConfiguration`, +#. :class:`Symfony\\Component\\Messenger\\Stamp\\ValidationStamp`, to configure the validation groups used when the validation middleware is enabled. -#. :class:`Symfony\\Component\\Messenger\\Asynchronous\\Transport\\ReceivedMessage`, +#. :class:`Symfony\\Component\\Messenger\\Stamp\\ReceivedStamp`, 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 :class:`Symfony\\Component\\Messenger\\EnvelopeAwareInterface` -marker, like this:: +Instead of dealing directly with the messages in the middleware you receive the envelope. +Hence you can inspect the envelope content and its stamps, or add any:: - use Symfony\Component\Messenger\Asynchronous\Transport\ReceivedMessage; + use App\Message\Stamp\AnotherStamp; + use Symfony\Component\Messenger\Stamp\ReceivedStamp; use Symfony\Component\Messenger\Middleware\MiddlewareInterface; - use Symfony\Component\Messenger\EnvelopeAwareInterface; + use Symfony\Component\Messenger\Middleware\StackInterface; - class MyOwnMiddleware implements MiddlewareInterface, EnvelopeAwareInterface + class MyOwnMiddleware implements MiddlewareInterface { - public function handle($envelope, callable $next) + public function handle(Envelope $envelope, StackInterface $stack): Envelope { - // $envelope here is an `Envelope` object, because this middleware - // implements the EnvelopeAwareInterface interface. - - if (null !== $envelope->get(ReceivedMessage::class)) { + if (null !== $envelope->get(ReceivedStamp::class)) { // Message just has been received... // You could for example add another item. - $envelope = $envelope->with(new AnotherEnvelopeItem(/* ... */)); + $envelope = $envelope->with(new AnotherStamp(/* ... */)); } - return $next($envelope); + return $stack->next()->handle($envelope, $stack); } } 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 :class:`Symfony\\Component\\Messenger\\EnvelopeAwareInterface`. +stamp *if* the message has just been received (i.e. has the `ReceivedStamp` stamp). +You can create your own items by implementing :class:`Symfony\\Component\\Messenger\\Stamp\\StampInterface`. Transports ---------- @@ -170,7 +167,7 @@ First, create your sender:: namespace App\MessageSender; use App\Message\ImportantAction; - use Symfony\Component\Messenger\Transport\SenderInterface; + use Symfony\Component\Messenger\Transport\Sender\SenderInterface; use Symfony\Component\Messenger\Envelope; class ImportantActionToEmailSender implements SenderInterface @@ -184,7 +181,7 @@ First, create your sender:: $this->toEmail = $toEmail; } - public function send(Envelope $envelope) + public function send(Envelope $envelope): Envelope { $message = $envelope->getMessage(); @@ -200,13 +197,15 @@ First, create your sender:: 'text/html' ) ); + + return $envelope; } } Your own Receiver ~~~~~~~~~~~~~~~~~ -A receiver is responsible for receiving messages from a source and dispatching +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 @@ -222,11 +221,11 @@ First, create your receiver:: namespace App\MessageReceiver; use App\Message\NewOrder; - use Symfony\Component\Messenger\Transport\ReceiverInterface; + use Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface; use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\Messenger\Envelope; - class NewOrdersFromCsvFile implements ReceiverInterface + class NewOrdersFromCsvFileReceiver implements ReceiverInterface { private $serializer; private $filePath; @@ -258,8 +257,8 @@ 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 :class:`Symfony\\Component\\Messenger\\Asynchronous\\Transport\\ReceivedMessage` -envelope item to the message envelopes and the :class:`Symfony\\Component\\Messenger\\Asynchronous\\Middleware\\SendMessageMiddleware` +loop, the message bus will add a :class:`Symfony\\Component\\Messenger\\Stamp\\ReceivedStamp` +stamp to the message envelopes and the :class:`Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware` middleware will know it should not route these messages again to a transport. .. _blog posts about command buses: https://matthiasnoback.nl/tags/command%20bus/ diff --git a/messenger.rst b/messenger.rst index 22e23fbecee..51d37bcbe44 100644 --- a/messenger.rst +++ b/messenger.rst @@ -61,7 +61,9 @@ message handler. It's a class with an ``__invoke`` method:: Message handlers must be registered as services and :doc:`tagged ` with the ``messenger.message_handler`` tag. If you're using the -:ref:`default services.yaml configuration `, +:ref:`default services.yaml configuration ` and implement +:class:`Symfony\\Component\\Messenger\\Handler\\MessageHandlerInterface` +or :class:`Symfony\\Component\\Messenger\\Handler\\MessageSubscriberInterface`, this is already done for you, thanks to :ref:`autoconfiguration `. If you're not using service autoconfiguration, then you need to add this config: @@ -395,6 +397,8 @@ like this: The first argument is the receiver's service name. It might have been created by your ``transports`` configuration or it can be your own receiver. +It also requires a ``--bus`` option in case you have multiple buses configured, +which is the name of the bus to which received messages should be dispatched. Multiple Buses -------------- @@ -514,17 +518,22 @@ for each bus looks like this: #. _Your own collection of middleware_; -#. ``route_messages`` middleware. Will route the messages you configured to their +#. ``send_message`` middleware. Will route the messages you configured to their corresponding sender and stop the middleware chain; -#. ``call_message_handler`` middleware. Will call the message handler(s) for the +#. ``handle_message`` middleware. Will call the message handler(s) for the given message. -Adding your own Middleware -~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. note:: -As described in the component documentation, you can add your own middleware -within the buses to add some extra capabilities like this: + These middleware names are actually shortcuts working by convention. + The real service ids are prefixed with the ``messenger.middleware.`` namespace. + +Disabling default Middleware +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you don't want the default collection of middleware to be present on your bus, +you can disable them like this: .. configuration-block:: @@ -535,9 +544,7 @@ within the buses to add some extra capabilities like this: messenger: buses: messenger.bus.default: - middleware: - - 'App\Middleware\MyMiddleware' - - 'App\Middleware\AnotherMiddleware' + default_middleware: false .. code-block:: xml @@ -553,10 +560,7 @@ within the buses to add some extra capabilities like this: - - - - + @@ -568,23 +572,17 @@ within the buses to add some extra capabilities like this: 'messenger' => array( 'buses' => array( 'messenger.bus.default' => array( - 'middleware' => array( - 'App\Middleware\MyMiddleware', - 'App\Middleware\AnotherMiddleware', - ), + 'default_middleware' => false, ), ), ), )); -Note that if the service is abstract, a different instance of service will be -created per bus. - -Disabling default Middleware -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Adding your own Middleware +~~~~~~~~~~~~~~~~~~~~~~~~~~ -If you don't want the default collection of middleware to be present on your bus, -you can disable them like this: +As described in the component documentation, you can add your own middleware +within the buses to add some extra capabilities like this: .. configuration-block:: @@ -595,7 +593,9 @@ you can disable them like this: messenger: buses: messenger.bus.default: - default_middleware: false + middleware: + - 'App\Middleware\MyMiddleware' + - 'App\Middleware\AnotherMiddleware' .. code-block:: xml @@ -611,7 +611,10 @@ you can disable them like this: - + + + + @@ -623,103 +626,28 @@ you can disable them like this: 'messenger' => array( 'buses' => array( 'messenger.bus.default' => array( - 'default_middleware' => false, + 'middleware' => array( + 'App\Middleware\MyMiddleware', + 'App\Middleware\AnotherMiddleware', + ), ), ), ), )); +Note that if the service is abstract, a different instance of service will be +created per bus. + Using Middleware Factories ~~~~~~~~~~~~~~~~~~~~~~~~~~ Some third-party bundles and libraries provide configurable middleware via -factories. Defining such requires a two-step configuration based on Symfony's -:doc:`dependency injection ` features: - -.. configuration-block:: - - .. code-block:: yaml - - # config/services.yaml - services: - - # Step 1: a factory class is registered as a service with the required - # dependencies to instantiate a middleware - doctrine.orm.messenger.middleware_factory.transaction: - class: Symfony\Bridge\Doctrine\Messenger\DoctrineTransactionMiddlewareFactory - arguments: ['@doctrine'] - - # Step 2: an abstract definition that will call the factory with default - # arguments or the ones provided in the middleware config - messenger.middleware.doctrine_transaction_middleware: - class: Symfony\Bridge\Doctrine\Messenger\DoctrineTransactionMiddleware - factory: 'doctrine.orm.messenger.middleware_factory.transaction:createMiddleware' - abstract: true - # the default arguments to use when none provided from config. Example: - # middleware: - # - doctrine_transaction_middleware: ~ - arguments: ['default'] - - .. code-block:: xml - - - - - - - - - - - +factories. - - - - - default - - - - - .. code-block:: php - - // config/services.php - use Symfony\Bridge\Doctrine\Messenger\DoctrineTransactionMiddleware; - use Symfony\Bridge\Doctrine\Messenger\DoctrineTransactionMiddlewareFactory; - use Symfony\Component\DependencyInjection\Reference; - - // Step 1: a factory class is registered as a service with the required - // dependencies to instantiate a middleware - $container - ->register('doctrine.orm.messenger.middleware_factory.transaction', DoctrineTransactionMiddlewareFactory::class) - ->setArguments(array(new Reference('doctrine'))); - - // Step 2: an abstract definition that will call the factory with default - // arguments or the ones provided in the middleware config - $container->register('messenger.middleware.doctrine_transaction_middleware', DoctrineTransactionMiddleware::class) - ->setFactory(array( - new Reference('doctrine.orm.messenger.middleware_factory.transaction'), - 'createMiddleware' - )) - ->setAbstract(true) - ->setArguments(array('default')); - -The "default" value in this example is the name of the entity manager to use, -which is the argument expected by the -``Symfony\Bridge\Doctrine\Messenger\DoctrineTransactionMiddlewareFactory::createMiddleware`` method. - -Then you can reference and configure the -``messenger.middleware.doctrine_transaction_middleware`` service as a middleware: +For instance, the ``messenger.middleware.doctrine_transaction`` is a +built-in middleware wired automatically when the DoctrineBundle and the Messenger +component are installed and enabled. +This middleware can be configured to define the entity manager to use: .. configuration-block:: @@ -731,7 +659,7 @@ Then you can reference and configure the buses: command_bus: middleware: - # Using defaults + # Using the default configured entity manager name - doctrine_transaction_middleware # Using another entity manager - doctrine_transaction_middleware: ['custom'] @@ -751,7 +679,7 @@ Then you can reference and configure the - + @@ -770,7 +698,7 @@ Then you can reference and configure the 'buses' => array( 'command_bus' => array( 'middleware' => array( - // Using defaults + // Using the default configured entity manager name 'doctrine_transaction_middleware', // Using another entity manager array('id' => 'doctrine_transaction_middleware', 'arguments' => array('custom')), @@ -780,22 +708,63 @@ Then you can reference and configure the ), )); -.. note:: - The ``doctrine_transaction_middleware`` shortcut is a convention. The real - service id is prefixed with the ``messenger.middleware.`` namespace. +Defining such configurable middleware is based on Symfony's +:doc:`dependency injection ` features: -.. note:: +.. configuration-block:: - Middleware factories only allow scalar and array arguments in config (no - references to other services). For most advanced use-cases, register a - concrete definition of the middleware manually and use its id. + .. code-block:: yaml -.. tip:: + # config/services.yaml + services: + messenger.middleware.doctrine_transaction: + class: Symfony\Bridge\Doctrine\Messenger\DoctrineTransactionMiddleware + # Definition is abstract, so a child definition will be created, per bus + abstract: true + # Main dependencies are defined by the parent definitions. + # Arguments provided in the middleware config will be appended on the child definition. + arguments: ['@doctrine'] + + .. code-block:: xml + + + + + + + + abstract="true"> + + + + + + - The ``doctrine_transaction_middleware`` is a built-in middleware wired - automatically when the DoctrineBundle and the Messenger component are - installed and enabled. + .. code-block:: php + + // config/services.php + use Symfony\Bridge\Doctrine\Messenger\DoctrineTransactionMiddleware; + use Symfony\Component\DependencyInjection\Reference; + + $container->register('messenger.middleware.doctrine_transaction', DoctrineTransactionMiddleware::class) + // Definition is abstract, so a child definition will be created, per bus + ->setAbstract(true) + // Main dependencies are defined by the parent definitions. + // Arguments provided in the middleware config will be appended on the child definition. + ->setArguments(array(new Reference('doctrine'))); + +.. note:: + + Middleware factories only allow appending scalar and array arguments in config + (no references to other services). For most advanced use-cases, register a + concrete definition of the middleware manually and use its id. Your own Transport ------------------ @@ -811,8 +780,8 @@ DSN. You will need a transport factory:: use Symfony\Component\Messenger\Transport\TransportFactoryInterface; use Symfony\Component\Messenger\Transport\TransportInterface; - use Symfony\Component\Messenger\Transport\ReceiverInterface; - use Symfony\Component\Messenger\Transport\SenderInterface; + use Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface; + use Symfony\Component\Messenger\Transport\Sender\SenderInterface; class YourTransportFactory implements TransportFactoryInterface { @@ -832,7 +801,7 @@ the ``SenderInterface`` and ``ReceiverInterface``). It will look like this:: class YourTransport implements TransportInterface { - public function send($message): void + public function send(Envelope $envelope): Envelope { // ... }