Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Middleware

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Internamente, cuando envías un mensaje al bus... ¿qué ocurre? ¿Qué aspecto tiene el código dentro del bus? La respuesta es... ¡básicamente no hay código dentro del bus! Todo se hace a través del middleware.

Conceptos básicos del middleware

El bus no es más que una colección de "middleware". Y cada middleware es sólo una función que recibe el mensaje y puede hacer algo con él.

El proceso es así. Pasamos un mensaje al método dispatch(), luego el bus lo pasa al primer middleware. El middleware ejecuta entonces algún código y finalmente llama al segundo middleware. Ejecuta algo de código y acaba llamando al tercer middleware... hasta que finalmente el último middleware -digamos que es el cuarto middleware- no tiene a quién llamar. En ese momento, la función del cuarto middleware termina, luego la del tercero, luego la del segundo y luego la del primero. Gracias a este diseño, cada middleware puede ejecutar código antes de llamar al siguiente middleware o después.

Este concepto de "middleware" no es exclusivo de Messenger, ni siquiera de PHP: es un patrón. Puede ser a la vez superútil... y un poco confuso... ya que es un gran círculo. La cuestión es la siguiente: con Messenger, si quieres engancharte al proceso de envío -como registrar lo que está ocurriendo- lo harás con un middleware. Incluso la funcionalidad principal de Messenger -ejecutar manejadores y enviar mensajes a los transportes- se hace con un middleware Estos se llaman HandleMessageMiddleware ySendMessageMiddleware, por si quieres ponerte friki y ver cómo funcionan.

Así que éste es nuestro objetivo: cada vez que enviemos un mensaje... desde cualquier lugar, quiero adjuntar un identificador único a ese mensaje y luego utilizarlo para registrar lo que ocurre a lo largo del tiempo con el mensaje: cuándo se envía inicialmente, cuándo se envía al transporte y cuándo se recibe del transporte y se maneja. Incluso podrías utilizarlo para registrar el tiempo que tarda un mensaje individual en ser procesado o cuántas veces se reintenta.

Crear un middleware

Crear un middleware es en realidad bastante sencillo. Crea un nuevo directorio dentro de src/ llamado Messenger/... aunque... como con casi todo en Symfony, este directorio podría llamarse como sea. Dentro, añade una clase llamada, qué tal, AuditMiddleware.

... lines 1 - 2
namespace App\Messenger;
... lines 4 - 5
use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
... lines 7 - 8
class AuditMiddleware implements MiddlewareInterface
{
... lines 11 - 14
}

La única regla para los middleware es que deben implementar -¡sorpresa! - MiddlewareInterface. Iré a "Código -> Generar" -o Comando+N en un Mac- y seleccionaré "Implementar métodos". Esta interfaz sólo requiere uno: handle(). Hablaremos de lo de la "pila" en un segundo... pero sobre todo... la firma de este método tiene sentido: recibimos el Envelope y devolvemos un Envelope.

... lines 1 - 2
namespace App\Messenger;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
use Symfony\Component\Messenger\Middleware\StackInterface;
class AuditMiddleware implements MiddlewareInterface
{
public function handle(Envelope $envelope, StackInterface $stack): Envelope
{
// TODO
}
}

La única línea que tu middleware necesitará casi seguro es ésta:return $stack->next()->handle($envelope, $stack).

... lines 1 - 7
class AuditMiddleware implements MiddlewareInterface
{
public function handle(Envelope $envelope, StackInterface $stack): Envelope
{
... line 13
return $stack->next()->handle($envelope, $stack);
}
}

Esta es la línea que básicamente dice

Quiero ejecutar el siguiente middleware y luego devolver su valor.

Sin esta línea, cualquier middleware posterior a nosotros nunca sería llamado... que no suele ser lo que quieres.

Registrar el middleware

Y... para empezar... es suficiente: ¡esta clase ya es un middleware funcional! Pero, a diferencia de muchas cosas en Symfony, Messenger no encontrará y empezará a usar este middleware automáticamente. Busca tu terminal abierto y, una vez más, ejecuta:

php bin/console debug:config framework messenger

Veamos... en algún lugar de aquí hay una clave llamada buses. En ella se definen todos los servicios del bus de mensajes que tienes en tu sistema. Ahora mismo, tenemos uno: el bus por defecto llamado messenger.bus.default. Ese nombre puede ser cualquier cosa y se convierte en el identificador del servicio. Debajo de esto, podemos utilizar la clave middleware para definir cualquier nuevo middleware que queramos añadir, además de los básicos que se añaden por defecto.

Vamos a copiar esa configuración. Luego, abre config/packages/messenger.yaml y, debajo deframework:, messenger:, pega esto justo encima... y asegúrate de que está sangrado correctamente. Debajo, añade a middleware: una nueva línea, y luego nuestro nuevo servicio de middleware:App\Messenger\AuditMiddleware.

framework:
messenger:
buses:
messenger.bus.default:
middleware:
- App\Messenger\AuditMiddleware
... lines 7 - 25

Orden del middleware

Y así, nuestro middleware debería ser llamado... junto con todo el middleware principal. ¿Qué... son los middleware del núcleo? ¿Y en qué orden se llama todo? Bueno, todavía no hay una buena forma de verlo, pero puedes encontrar esta información ejecutando

php bin/console debug:container --show-arguments messenger.bus.default.inner

... que es una forma de muy bajo nivel de obtener información sobre el bus de mensajes. De todos modos, hay unos cuantos middleware centrales al principio que configuran algunas cosas básicas, luego nuestro middleware y, finalmente, se llama a SendMessageMiddleware yHandleMessageMiddleware al final. Saber el orden exacto de estas cosas no es tan importante, pero espero que ayude a desmitificar las cosas a medida que avancemos.

A continuación, vamos a ponernos a trabajar utilizando nuestro middleware para adjuntar un identificador único a cada mensaje. ¿Cómo? A través de nuestro propio sello

Leave a comment!

4
Login or Register to join the conversation
Default user avatar
Default user avatar John Esteban | posted hace 1 año

Hi! I'm working with the Symfony framework, and I'm using Messenger. I want to know if there is a way to ignore some messages when I'm using middlewares without finish with a error or exception or exit. For example, if I have already consumed a particular message and I want to ignore it.

Reply

Hi John Esteban!

Hmm. I've never done this before, but yes, I think this is definitely doable. One of the things you need to have in any middleware is this line:


return $stack->next()->handle($envelope, $stack);

Example: https://github.com/symfony/symfony/blob/8e8207bb72d7f2cb8be355994ad2fcfa97c00f74/src/Symfony/Component/Messenger/Middleware/AddBusNameStampMiddleware.php#L37

This is literally what calls the NEXT item in the middleware. If you changed this, instead to:


return $envelope.

Then I'm pretty sure that the rest of the middleware change would not execute. So as long as your middleware is put before the "handle message" middleware (and this will happen automatically), then this should do the trick.

Let me know if you're successful.

Cheers!

Reply
Default user avatar
Default user avatar Jeff Groves | posted hace 3 años

I'm trying to figure out how to using the Symfony Messenger component in a non-Symfony application. I've got it working fine in Symfony to send AMQP messages to RabbitMQ, but I need to send AMQP messages from a legacy PHP application. Basically, I need to figure out how to configure my own SendMessageMiddleware to work the same way as Symfony. The current documentation at https://symfony.com/doc/cur... is not very helpful. All it says is "...with Symfony’s FrameworkBundle, the following middleware are configured for you: SendMessageMiddleware...".

I need an example of how to do it myself without the Symfony magic.

Reply

Hey Jeff Groves !

I'm trying to figure out how to using the Symfony Messenger component in a non-Symfony application

Ah yes :). This is (as you correctly noticed) not the "first class citizen use case" in the docs - it's tricky, we have to write the docs primarily for the main audience. That, indeed, makes the "standalone component" usage trickier!

So let's talk about SendMessageMiddleware. This class itself is quite simple - the tricky part is the SendersLocatorInterface constructor argument, which normally Symfony handles creating for you. This is the concrete class that Symfony uses: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Messenger/Transport/Sender/SendersLocator.php

But, let's stop right there :). The full Symfony Framework Messenger is super feature rich and complex. And you may not need to hook up the same level of complexity and features in your legacy app. The simplest thing to do would be to create your own implementation of SendersLocatorInterface - it's a beautifully simple interface: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Messenger/Transport/Sender/SendersLocatorInterface.php

Inside, getSenders(), you could call $envelop->getMessage() and, if you want, just have a big map of which message class should go to which "SenderInterface". Or... maybe you only have one Sender/Transport. In that case, you could literally just return it:


public function getSenders(Envelope $envelope): iterable
{
    return [$this->myOneSender];
}

Let me know if that makes sense - or if there is something else tripping you up in this process :).

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

Este tutorial está construido con Symfony 4.3, pero funcionará bien en Symfony 4.4 o 5.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "doctrine/annotations": "^1.0", // v1.8.0
        "doctrine/doctrine-bundle": "^1.6.10", // 1.11.2
        "doctrine/doctrine-migrations-bundle": "^1.3|^2.0", // v2.0.0
        "doctrine/orm": "^2.5.11", // v2.6.3
        "intervention/image": "^2.4", // 2.4.2
        "league/flysystem-bundle": "^1.0", // 1.1.0
        "phpdocumentor/reflection-docblock": "^3.0|^4.0", // 4.3.1
        "sensio/framework-extra-bundle": "^5.3", // v5.3.1
        "symfony/console": "4.3.*", // v4.3.2
        "symfony/dotenv": "4.3.*", // v4.3.2
        "symfony/flex": "^1.9", // v1.18.7
        "symfony/framework-bundle": "4.3.*", // v4.3.2
        "symfony/messenger": "4.3.*", // v4.3.4
        "symfony/property-access": "4.3.*", // v4.3.2
        "symfony/property-info": "4.3.*", // v4.3.2
        "symfony/serializer": "4.3.*", // v4.3.2
        "symfony/validator": "4.3.*", // v4.3.2
        "symfony/webpack-encore-bundle": "^1.5", // v1.6.2
        "symfony/yaml": "4.3.*" // v4.3.2
    },
    "require-dev": {
        "easycorp/easy-log-handler": "^1.0.7", // v1.0.7
        "symfony/debug-bundle": "4.3.*", // v4.3.2
        "symfony/maker-bundle": "^1.0", // v1.12.0
        "symfony/monolog-bundle": "^3.0", // v3.4.0
        "symfony/stopwatch": "4.3.*", // v4.3.2
        "symfony/twig-bundle": "4.3.*", // v4.3.2
        "symfony/var-dumper": "4.3.*", // v4.3.2
        "symfony/web-profiler-bundle": "4.3.*" // v4.3.2
    }
}
userVoice