Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Sobres y sellos

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

Acabamos de recibir una petición de la propia Ponka... y cuando se trata de este sitio, Ponka es la jefa. Ella cree que, cuando un usuario sube una foto, su imagen se añade demasiado rápido. Quiere que tarde más tiempo: quiere que parezca que está haciendo un trabajo realmente épico entre bastidores para entrar en tu foto.

Lo sé, es un ejemplo un poco tonto: Ponka es muy rara cuando hablas con ella antes de su desayuno de gambas y su siesta matutina. Pero... es un reto interesante: ¿podríamos de alguna manera no sólo decir "manipula esto más tarde"... sino también "espera al menos 5 segundos antes de manipularlo".

El sobre: Un gran lugar para poner un mensaje

¡Sí! Y toca algunas partes súper chulas del sistema llamadas sellos y sobres. Primero, abre ImagePostController y sube hasta donde creamos el objetoAddPonkaToImage. AddPonkaToImage se llama "mensaje", eso lo sabemos. Lo que no sabemos es que, cuando pasas tu mensaje al bus, internamente, se envuelve dentro de algo llamado Envelope.

Ahora bien, esto no es un detalle especialmente importante, salvo que, si tienes un Envelope, puedes adjuntarle una configuración adicional mediante sellos. Así que sí, literalmente metes un mensaje en un sobre y luego le adjuntas sellos. ¿Es este tu componente favorito o qué?

De todos modos, esos sellos pueden llevar todo tipo de información. Por ejemplo, si usas RabbitMQ, puedes configurar algunas cosas sobre cómo se entrega el mensaje, como algo llamado "clave de enrutamiento". O puedes configurar un retraso.

Pon el mensaje en el sobre, y luego añade sellos

Comprueba esto: di $envelope = new Envelope() y pásale nuestro $message. Luego, pásale un segundo argumento opcional: una matriz de sellos

... lines 1 - 15
use Symfony\Component\Messenger\Envelope;
... lines 17 - 23
class ImagePostController extends AbstractController
{
... lines 26 - 40
public function create(Request $request, ValidatorInterface $validator, PhotoFileManager $photoManager, EntityManagerInterface $entityManager, MessageBusInterface $messageBus)
{
... lines 43 - 63
$envelope = new Envelope($message, [
... line 65
]);
... lines 67 - 69
}
... lines 71 - 98
}

Incluye sólo uno: new DelayStamp(5000). Esto indica al transporte... que es algo así como el cartero... que quieres que este mensaje se retrase 5 segundos antes de ser entregado. Por último, pasa el $envelope -no el mensaje- a $messageBus->dispatch().

... lines 1 - 17
use Symfony\Component\Messenger\Stamp\DelayStamp;
... lines 19 - 23
class ImagePostController extends AbstractController
{
... lines 26 - 40
public function create(Request $request, ValidatorInterface $validator, PhotoFileManager $photoManager, EntityManagerInterface $entityManager, MessageBusInterface $messageBus)
{
... lines 43 - 63
$envelope = new Envelope($message, [
new DelayStamp(5000)
]);
$messageBus->dispatch($envelope);
... lines 68 - 69
}
... lines 71 - 98
}

Sí, el método dispatch() acepta objetos de mensaje sin procesar u objetos Envelope. Si pasas un mensaje sin procesar, lo envuelve en un Envelope. Si pasas unEnvelope, ¡lo utiliza! El resultado final es el mismo que antes... salvo que ahora aplicamos un DelayStamp.

¡Vamos a probarlo! Esta vez no necesitamos reiniciar nuestro trabajador porque no hemos cambiado ningún código que vaya a utilizar: sólo hemos cambiado el código que controla cómo se entregará el mensaje. Pero... si alguna vez no estás seguro, reinícialo.

Borraré la consola para que podamos ver lo que ocurre. Entonces... vamos a subir tres fotos y... un, dos, tres, cuatro ¡ahí está! Se retrasó 5 segundos y luego empezó a procesar cada una de ellas con normalidad. No hay un retraso de 5 segundos entre el tratamiento de cada mensaje: sólo se asegura de que cada mensaje se trate no antes de 5 segundos después de enviarlo.

Tip

La compatibilidad con los retrasos en Redis se añadió en Symfony 4.4.

Nota al margen: En Symfony 4.3, el transporte Redis no admite retrasos, pero es posible que se añada en el futuro.

¿Qué otros sellos hay?

En cualquier caso, puede que no utilices mucho los sellos, pero los necesitarás de vez en cuando. Probablemente busques en Google "Cómo configuro los grupos de validación en Messenger" y aprendas qué sello controla esto. No te preocupes, ya hablaré de la validación más adelante, no es algo que ocurra ahora mismo.

Otra cosa interesante es que, internamente, el propio Messenger utiliza sellos para rastrear y ayudar a entregar los mensajes correctamente. Comprueba esto: envuelve $messageBus->dispatch()en una llamada a dump().

... lines 1 - 23
class ImagePostController extends AbstractController
{
... lines 26 - 40
public function create(Request $request, ValidatorInterface $validator, PhotoFileManager $photoManager, EntityManagerInterface $entityManager, MessageBusInterface $messageBus)
{
... lines 43 - 66
dump($messageBus->dispatch($envelope));
... lines 68 - 69
}
... lines 71 - 98
}

Vamos a subir una nueva imagen. A continuación, en la barra de herramientas de depuración de la web, busca la petición AJAX que acaba de terminar -será la de abajo-, haz clic para abrir su perfil y luego haz clic en "Depurar" a la izquierda. ¡Ahí lo tienes! El método dispatch() devuelve un Envelope... que contiene el mensaje, por supuesto... ¡y ahora tiene cuatro sellos! Tiene el DelayStamp como esperábamos, pero también un BusNameStamp, que registra el nombre del bus al que se envió. Esto es genial: ahora sólo tenemos un bus, pero puedes tener varios, y hablaremos de por qué podrías hacerlo más adelante. El BusNameStamp ayuda al comando trabajador a saber a qué bus debe enviar el Envelope una vez leído del transporte.

Ese SentStamp es básicamente un marcador que dice "este mensaje fue enviado a un transporte en lugar de ser manejado inmediatamente" y este TransportMessageIdStamp, contiene literalmente el id de la nueva fila en la tabla messenger_messages... por si es útil.

En realidad, no necesitas preocuparte por nada de esto, pero observar qué sellos se añaden a tu Envelope puede ayudarte a depurar un problema o a hacer cosas más avanzadas. De hecho, algunos de ellos serán útiles pronto cuando hablemos del middleware.

Por ahora, elimina el dump() y luego, para no volverme loco con lo lento que es esto, cambia el DelayStamp a 500 milisegundos. Shh, no se lo digas a Ponka. Después de este cambio... ¡sí! El mensaje se gestiona casi inmediatamente.

... lines 1 - 23
class ImagePostController extends AbstractController
{
... lines 26 - 40
public function create(Request $request, ValidatorInterface $validator, PhotoFileManager $photoManager, EntityManagerInterface $entityManager, MessageBusInterface $messageBus)
{
... lines 43 - 63
$envelope = new Envelope($message, [
new DelayStamp(500)
]);
$messageBus->dispatch($envelope);
... lines 68 - 69
}
... lines 71 - 98
}

A continuación, hablemos de los reintentos y de lo que ocurre cuando las cosas van mal No es una broma: estas cosas son superchulas.

Leave a comment!

6
Login or Register to join the conversation
Core Avatar

Hi Ryan, is it possible to delay 5 seconds between handling each message?

Reply

Hey @Core!

Hmm, I'm not sure! You can add a DelayStamp, but I don't think that's what you're referring to. Why do you want to delay exactly? Anyways, one way to do this would be to add a listener to the WorkerMessageHandledEvent event and, inside, literally sleep(5). That might sound a bit crazy, but that's actually what the worker (i.e. messenger:consume ) already does between messages: it does a usleep(1000000) (equivalent to sleep(1)) after not finding any new messages before checking again - https://github.com/symfony/symfony/blob/69f46f231b16be1bce1e2ecfa7f9a1fb192cda22/src/Symfony/Component/Messenger/Worker.php#L134

Cheers!

Reply
Dmitriy Avatar
Dmitriy Avatar Dmitriy | posted hace 1 año

Is it possible to set custom "queue name" with parameter on

$messageBus->dispatch($message);

For example, "competition_41".

I need progress bar 0-100% for each "competitions messages".

Reply

Hey Dmitriy!

I don't think this is easily possible. The problem is that... queue systems aren't meant to be used this way. For AMQP, for example, you're supposed to send to an "exchange"... which then delivers to one or more queues based on routing rules. It seems like it should be possible, but you're not "supposed to" think about "sending a message directly to a queue".

If you're using AMQP, this is still possible - but you'd need to set some things up manually. It would look something like:

A) Create the 100 queues that you need in AMQP.
B) Set up some custom routing in AMQP so that different routing keys (e.g. competition_41) send to different queues (e.g. competition_41).
C) In Messenger, attach a custom AmqpStamp to the message with whatever routing key you want.

If you're using the Doctrine transport, I don't think making the queue dynamic is at all possible. I don't fully understand what you're trying to accomplish, but I may try to do it a different way.

Cheers!

Reply
Nayte91 Avatar
Nayte91 Avatar Nayte91 | posted hace 1 año

Hello,

As a side note in your text, you wrote that redis transport delay is not supported in sf4.3, but may be added in future --> I think it's ok now since 4.4 ? https://github.com/symfony/... ? If so, we can update the text :) cheers !

Reply

Hey Julien R.!

Excellent idea - we'll add a note!

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