If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeSupón que necesitas que tu amigo venga a cuidar a tu perra durante el fin de semana, llamémosla Molly. Así que le escribes un mensaje en el que le explicas todos los detalles que necesita saber: con qué frecuencia debe alimentar a Molly, cuándo debe pasearla, dónde le gusta exactamente que le rasquen detrás de las orejas, tu película de superhéroes favorita y el nombre de tu mejor amigo de la infancia. Espera... esas dos últimas cosas... aunque fascinantes... ¡no tienen nada que ver con la vigilancia de tu perro Molly!
Y esto toca una práctica recomendada para diseñar tus clases de mensajes: haz que contengan todos los detalles que el adiestrador necesita... y nada extra. Esto no es una regla absoluta... sólo hace que sean más delgadas, más pequeñas y más dirigidas.
Si piensas en nuestro mensaje, en realidad no necesitamos todo el objeto ImagePost
. Lo más pequeño que podríamos pasar es en realidad el id... que luego podríamos utilizar para consultar el objeto ImagePost
y obtener el nombre del archivo.
Cambia el argumento del constructor por int $imagePostId
. Lo cambiaré a continuación y ve a Código -> Refactorizar para cambiar el nombre de la propiedad. Ah, y ¡brillante! También cambió el nombre de mi getter a getImagePostId()
. Actualiza el tipo de retorno para que sea un int
. Podemos eliminar la antigua declaración use
como crédito extra.
... lines 1 - 2 | |
namespace App\Message; | |
use App\Entity\ImagePost; | |
class AddPonkaToImage | |
{ | |
private $imagePostId; | |
public function __construct(int $imagePostId) | |
{ | |
$this->imagePostId = $imagePostId; | |
} | |
public function getImagePostId(): int | |
{ | |
return $this->imagePostId; | |
} | |
} |
A continuación, en ImagePostController
, busca AddPonkaToImage
y... cámbialo por $imagePost->getId()
.
... lines 1 - 21 | |
class ImagePostController extends AbstractController | |
{ | |
... lines 24 - 38 | |
public function create(Request $request, ValidatorInterface $validator, PhotoFileManager $photoManager, EntityManagerInterface $entityManager, MessageBusInterface $messageBus) | |
{ | |
... lines 41 - 60 | |
$message = new AddPonkaToImage($imagePost->getId()); | |
... lines 62 - 64 | |
} | |
... lines 66 - 93 | |
} |
Nuestra clase de mensaje es ahora lo más pequeña posible. Por supuesto, esto significa que tenemos que hacer un poco de trabajo extra en nuestro manejador. En primer lugar, la variable $imagePost
ya no es... bueno... ¡un ImagePost
! Cámbiale el nombre a $imagePostId
.
... lines 1 - 11 | |
class AddPonkaToImageHandler implements MessageHandlerInterface | |
{ | |
... lines 14 - 26 | |
public function __invoke(AddPonkaToImage $addPonkaToImage) | |
{ | |
$imagePostId = $addPonkaToImage->getImagePostId(); | |
... lines 30 - 37 | |
} | |
} |
Para consultar el objeto real, añade un nuevo argumento del constructor:ImagePostRepository $imagePostRepository
. Pulsaré Alt + Enter -> Inicializar campos para crear esa propiedad y establecerla.
... lines 1 - 7 | |
use App\Repository\ImagePostRepository; | |
... lines 9 - 11 | |
class AddPonkaToImageHandler implements MessageHandlerInterface | |
{ | |
... lines 14 - 16 | |
private $imagePostRepository; | |
... line 18 | |
public function __construct(PhotoPonkaficator $ponkaficator, PhotoFileManager $photoManager, EntityManagerInterface $entityManager, ImagePostRepository $imagePostRepository) | |
{ | |
... lines 21 - 23 | |
$this->imagePostRepository = $imagePostRepository; | |
} | |
... lines 26 - 38 | |
} |
De vuelta al método, podemos decir$imagePost = $this->imagePostRepository->find($imagePostId)
.
... lines 1 - 11 | |
class AddPonkaToImageHandler implements MessageHandlerInterface | |
{ | |
... lines 14 - 26 | |
public function __invoke(AddPonkaToImage $addPonkaToImage) | |
{ | |
... line 29 | |
$imagePost = $this->imagePostRepository->find($imagePostId); | |
... lines 31 - 37 | |
} | |
} |
Ya está ¡Y esto soluciona nuestro problema con Doctrine! Ahora que estamos consultando la entidad, cuando llamemos a flush()
, la guardará correctamente con un UPDATE
. Podemos eliminar la llamada a persist()
porque no es necesaria para las actualizaciones.
¡Vamos a probarlo! Como acabamos de cambiar el código en nuestro manejador, pulsa Ctrl+C para detener nuestro trabajador y luego reinícialo:
php bin/console messenger:consume -vv
¡Ya está! Sube un nuevo archivo... comprueba el trabajador -sí, se ha procesado bien- y... ¡actualiza! ¡Sí! ¡No hay duplicación, Ponka está visitando mi taller y la fecha está fijada!
Pero... siento dar malas noticias... ¿qué pasa si no se puede encontrar el ImagePost
para este $imagePostId
? Eso no debería ocurrir... pero dependiendo de tu aplicación, ¡podría ser posible! Para nosotros... ¡lo es! Si un usuario sube una foto y luego la borra antes de que el trabajador pueda gestionarla, ¡el ImagePost
desaparecerá!
¿Es realmente un problema? Si el ImagePost
ya se ha borrado, ¿nos importa que este manipulador explote? Probablemente no... siempre que hayas pensado en cómo va a explotar y sea intencionado.
Fíjate en esto: empecemos diciendo: if (!$imagePost)
para poder hacer un manejo especial... en lugar de intentar llamar a getFilename()
sobre null aquí abajo. Si esto ocurre, sabemos que probablemente sea sólo porque la imagen ya se ha borrado. Pero... como odio las sorpresas en producción, vamos a registrar un mensaje para que sepamos que esto ha ocurrido... por si acaso se debe a un error en nuestro código.
A partir de Symfony 4.2, hay un pequeño atajo para conseguir el servicio principal logger
. Primero, haz que tu servicio implemente LoggerAwareInterface
. Luego, utiliza un rasgo llamado LoggerAwareTrait
.
... lines 1 - 9 | |
use Psr\Log\LoggerAwareInterface; | |
use Psr\Log\LoggerAwareTrait; | |
... lines 12 - 13 | |
class AddPonkaToImageHandler implements MessageHandlerInterface, LoggerAwareInterface | |
{ | |
use LoggerAwareTrait; | |
... lines 17 - 30 | |
public function __invoke(AddPonkaToImage $addPonkaToImage) | |
{ | |
... lines 33 - 35 | |
if (!$imagePost) { | |
... lines 37 - 44 | |
} | |
... lines 46 - 52 | |
} | |
} |
Y ya está Vamos a echar un vistazo al interior de LoggerAwareTrait
. Muy bien. En el núcleo de Symfony, hay un poco de código que dice
siempre que veas un servicio de usuario que implemente
LoggerAwareInterface
, llama automáticamente asetLogger()
sobre él y pasa el logger.
Al combinar la interfaz con este rasgo... ¡no tenemos que hacer nada! Al instante tenemos una propiedad $logger
que podemos utilizar.
Bien, volviendo a nuestra sentencia if... ¿qué debemos hacer si no se encuentra el ImagePost
? Tenemos dos opciones... y la elección correcta depende de la situación. En primer lugar, podríamos lanzar una excepción -cualquier excepción- y eso haría que este mensaje se reintentara. Pronto habrá más reintentos. O bien, podrías simplemente "devolver" y este mensaje "parecerá" que se ha gestionado con éxito... y se eliminará de la cola.
Volvamos: no tiene sentido reintentar este mensaje más tarde... ¡ese ImagePost
se ha ido!
... lines 1 - 13 | |
class AddPonkaToImageHandler implements MessageHandlerInterface, LoggerAwareInterface | |
{ | |
... lines 16 - 30 | |
public function __invoke(AddPonkaToImage $addPonkaToImage) | |
{ | |
... lines 33 - 35 | |
if (!$imagePost) { | |
// could throw an exception... it would be retried | |
// or return and this message will be discarded | |
... lines 39 - 43 | |
return; | |
} | |
... lines 46 - 52 | |
} | |
} |
Pero también registremos un mensaje: si $this->logger
, entonces $this->logger->alert()
con, qué tal,
¡Falta la imagen del puesto %d!
pasando $imagePostId
por el comodín
... lines 1 - 13 | |
class AddPonkaToImageHandler implements MessageHandlerInterface, LoggerAwareInterface | |
{ | |
... lines 16 - 30 | |
public function __invoke(AddPonkaToImage $addPonkaToImage) | |
{ | |
... lines 33 - 35 | |
if (!$imagePost) { | |
// could throw an exception... it would be retried | |
// or return and this message will be discarded | |
if ($this->logger) { | |
$this->logger->alert(sprintf('Image post %d was missing!', $imagePostId)); | |
} | |
return; | |
} | |
... lines 46 - 52 | |
} | |
} |
Ah, y la única razón por la que compruebo si $this->logger
está activado es... básicamente... para ayudar en las pruebas unitarias. Dentro de Symfony, la propiedad logger
siempre estará establecida. Pero a nivel orientado a objetos, no hay nada que garantice que alguien haya llamado a setLogger()
... así que esto es un poco más responsable.
¡Vamos a probar esta cosa! ¡Veamos qué ocurre si borramos un ImagePost
antes de que se procese! Primero, muévete, detén el manipulador y reinícialo:
php bin/console messenger:consume -vv
Y como cada mensaje tarda unos segundos en procesarse, si subimos un montón de fotos... y las borramos súper rápido... con un poco de suerte, borraremos una antes de que se procese su mensaje.
¡Veamos si ha funcionado! Así que... algunas sí se procesaron con éxito. Pero... ¡sí! ¡Este tiene una alerta! Y gracias al "retorno" que añadimos, fue "reconocido"... lo que significa que fue eliminado de la cola.
Ah... e interesante... hay otro error que no había previsto a continuación
Se produjo una excepción al manejar el mensaje AddPonkaToImage: Archivo no encontrado en la ruta...
¡Esto es increíble! Esto es lo que parece si, por cualquier motivo, se lanza una excepción en tu manejador. Al parecer, el ImagePost
se encontró en la base de datos... pero cuando intentó leer el archivo en el sistema de archivos, ¡se había eliminado!
Lo realmente sorprendente es que Messenger vio este fallo y volvió a intentar automáticamente el mensaje una segunda... y luego una tercera vez. Hablaremos más sobre los fallos y los reintentos un poco más tarde.
Pero antes, nuestro mensaje DeleteImagePost
se sigue gestionando de forma sincrónica. ¿Podríamos hacerlo asíncrono? Bueno... ¡no! Necesitamos que el ImagePost
se elimine de la base de datos inmediatamente para que el usuario no lo vea si actualiza. A menos que... podamos dividir la tarea de eliminación en dos partes... ¡Vamos a intentarlo a continuación!
Hey Cristóbal Rejdych
That's odd. I tested it on Symfony 4.4 and it worked fine. Can you double-check if you imported the right classes
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
if that's not the case, can you upgrade to Symfony 4.4? it's possible that there is a bug on the version you are
Cheers!
Hi Diego,
yeah I'm imported that classes, implement LoggerAwareInterface and use LoggerAwareTrait inside class. So if nothing else could break it down, it must be bug. For now I change it to simple DI inside constructor, but when I complete all required by project tasks I try to upgrade it to Symfony 4.4 and wrote here result.
Thanks a lot for reply.
Cheers ;)!
Interesting... One last thing I can think of is that you have to restart the Messenger Worker every time you change your App's code but I suppose you did that so I'm leaning towards a bug on Messenger
Yeah I'm restarting worker after any changes I do inside messenger. Like I wrote before I was curious of this problem and I'm upgraded Symfony to latest version 4.4 and test it again and problem with this trait still exist, so it's not bug in Symfony, but fortunately DI logger service still work properly so I don't see reason to make it big problem which must be resolved. If I in the future find cause of problem I will post info about it here ;)
// 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
}
}
Hi guys,
I wanna ask about LoggerAwareTrait. You wrote in this tutorial:
But yesterday I did manual tests for some of my project functionality and I discovered that logger setted inside MessageHandler by using LoggerAwareTrait and implementing LoggerAwareInterface is always null. So I thought it's probably something wrong with logger. So I created new one by Dependency injection in constructor and that one works correctly. What could I do wrong with LoggerAwareTrait?? I use Symfony 4.3.11.