Quiero que se desencadene una tarea en respuesta a lo que ocurre en un controlador, pero de manera desacoplada. ¿Es apropiado utilizar eventos y listeners? ¿A un listener se le puede hacer type-hint del request? ¿El listener puede trabajar con los global helpers request y session?
Sí, utilizar eventos y listeners es la forma más apropiada y canónica de resolver ese problema.
Es la solución ideal para desacoplar la lógica, siguiendo el patrón de diseño Observer. En este patrón, el controlador "emite" una señal (el evento) de que algo ha ocurrido, y los "observadores" (los listeners) reaccionan a esa señal de forma independiente.
Esto te permite:
Mantener la lógica del controlador limpia, centrada solo en manejar la solicitud HTTP.
Hacer que la tarea desacoplada sea más modular y reutilizable.
¿Es apropiado utilizar eventos y listeners?
Absolutamente. Este es un caso de uso de libro para el sistema de eventos de Laravel. El controlador no necesita saber qué sucede después de que se completa su acción, solo necesita notificar que se ha realizado con éxito. Los listeners se encargan del resto, lo que te permite agregar o quitar tareas en el futuro sin modificar el controlador.
¿A un listener se le puede hacer type-hint del request?
No, no se puede. Esta es una limitación intencional y una de las reglas clave de este patrón. Un listener no recibe el objeto Request
directamente, ya que su método handle
solo recibe una instancia del evento.
La razón es simple pero crucial: los listeners pueden ser procesados en una cola en un proceso diferente al de la solicitud web original. En ese entorno, el objeto Request
ya no existe, y tratar de acceder a él causaría un error o devolvería null
.
¿El listener puede trabajar con los global helpers request()
y session()
?
Técnicamente, sí, pero es una muy mala práctica.
Aunque podrías llamar a request()
o session()
dentro de un listener, esto rompe por completo el propósito del desacoplamiento. Si el listener se ejecuta en una cola, estos helpers no tendrían un contexto HTTP y no funcionarían como esperas, lo que llevaría a errores difíciles de depurar.
La forma correcta de pasar datos
La solución correcta es pasar toda la información que el listener necesita desde el controlador a través del objeto Evento.
En tu Evento: Define las propiedades que necesitas, como la información de la solicitud, el usuario o los datos del modelo.
2. En tu Controlador: Dispara el evento, pasando los datos necesarios.
En tu Listener: El listener recibe el objeto
UserRegistered
y accede a las propiedades sin depender de nada del entorno global.
Al seguir esta estrategia, tu código es robusto, modular y fácil de probar, ya que cada componente cumple su función sin depender de otros.
Necesito que en consecuencia de una acción realizada en un controller, pase otra cosa que no tiene que ver con el objetivo del controller pero que necesita operar sobre el request y la sesión.
Tu pregunta es muy específica y apunta a un dilema común. Si una tarea debe ser desacoplada del controlador pero necesita imperiosamente el objeto Request
y la Session
, el enfoque con eventos y listeners sigue siendo la solución correcta, pero con una restricción crucial: el listener no puede ser asíncrono.
La clave es que el listener se ejecutará de forma síncrona, es decir, dentro del mismo ciclo de vida de la solicitud HTTP.
Esto te permite:
Desacoplar la lógica: Tu controlador no se llena de tareas que no le corresponden. Simplemente "anuncia" que una acción ha ocurrido.
Acceder al Request y Session: Como el listener se ejecuta en el mismo proceso de la solicitud, el estado del
Request
y laSession
aún están disponibles y son accesibles.
El enfoque correcto
Crea un Evento: Un simple objeto de datos (POPO - Plain Old PHP Object) que contiene las referencias necesarias, incluyendo el objeto
Request
.Dispara el Evento en el Controlador: En el momento adecuado, disparas el evento.
Crea un Listener Síncrono: Este listener recibirá el evento y podrá operar sobre el
Request
y laSession
sin problemas. No lo marques comoShouldQueue
.
Este es el único método seguro para mantener el acceso al estado HTTP mientras se mantiene un cierto nivel de desacoplamiento.
Ejemplo de código paso a paso
Vamos a crear un escenario donde, después de que un usuario actualiza su perfil, quieres registrar su actividad en la sesión y guardar su historial de cambios en una base de datos. La segunda tarea no es responsabilidad directa del controlador, pero requiere los datos de la solicitud.
Resumen del enfoque
Tu controlador llama a una sola línea:
ProfileUpdated::dispatch($request, $user);
. Su lógica principal se mantiene enfocada en la actualización del perfil.El Evento actúa como un simple contenedor de datos, llevando el
Request
y el$user
al listener.El Listener realiza las tareas secundarias (logging, manipulación de sesión), operando con todos los datos del
Request
que necesita.
Este patrón te permite mantener el código limpio y organizado. Si en el futuro necesitas agregar otra tarea, simplemente creas otro listener y lo asocias al mismo evento, sin tocar el controlador original.
No hay comentarios:
Publicar un comentario