Skip to main content
Cuando quieras ofrecer puntos de extensión basados en eventos en tu paquete, diseña por separado la provisión de las clases de evento y el registro de los listeners. Si el paquete funciona sin que el usuario tenga que añadir directorios dentro de vendor al descubrimiento automático, reduces las suposiciones necesarias durante la instalación, la actualización y la generación de la caché. Esta página revisa la implementación de Laravel Framework v13.36.0 y aplica las operaciones básicas de los eventos al mantenimiento a largo plazo de un paquete. Para la carga de providers, consulta Descubrimiento de paquetes; para el entorno de pruebas, consulta Pruebas de paquetes.

Registrar explícitamente los listeners del paquete

Las tareas que el paquete ejecuta de forma predeterminada pueden registrarse con Event::listen() en el método boot() de un Illuminate\Support\ServiceProvider normal. El destino predeterminado del descubrimiento automático de listeners de Laravel es app/Listeners de la aplicación; no recorre automáticamente el directorio src/Listeners del paquete. Como ejemplo, crearemos un paquete que registra en el log los eventos de entrega completada. El evento es simplemente una clase que contiene los valores necesarios para la notificación.
El listener recibe el logger en el constructor y procesa el evento en handle().
En el provider se registran los nombres de clase del evento y del listener.
Esta configuración supone que el propio provider se carga. Prepara extra.laravel.providers de Composer o un registro explícito por parte del usuario. Si el evento se necesita tanto en el procesamiento HTTP como en los workers de colas, no limites el registro con runningInConsole().
No implementes DeferrableProvider en este provider solo por registrar listeners. Si el servicio diferido no llega a resolverse, los listeners tampoco se registran. Si es necesario, separa los bindings de servicios pesados en otro provider diferido.

El registro y la creación de la instancia ocurren en momentos distintos

Dispatcher::listen() añade el listener a un array interno para el nombre de evento normal. Cuando se pasa un nombre de clase como en el ejemplo anterior, en este momento no se crea la instancia del listener. En los listeners síncronos, después de disparar el evento, createClassCallable() resuelve la clase mediante el make() del contenedor. Por eso puedes utilizar la inyección de dependencias en el constructor. Esta ruta no realiza un call() del contenedor sobre handle(). Diseña el listener de modo que los servicios arbitrarios se inyecten en el constructor y handle() reciba el evento. No supongas que funciona igual que la inyección de dependencias en handle() de los comandos Artisan. Lo descrito aquí se refiere a los listeners síncronos. Si implementas ShouldQueue, se utiliza una ruta distinta para las colas, así que revisa también el procesamiento de eventos en cola y las colas en cuanto a transacciones, reintentos y resolución de dependencias.

event:cache no impide el registro explícito

event:cache no es un comando que serialice en bloque todos los listeners registrados en el Dispatcher de eventos. EventCacheCommand::getEvents() enumera Illuminate\Foundation\Support\Providers\EventServiceProvider y sus providers derivados, y guarda el resultado del descubrimiento automático y el mapeo de listens() de cada provider. Las llamadas a Event::listen() escritas en el ServiceProvider::boot() normal no forman parte de esta recopilación. Por otro lado, incluso en un arranque con caché se ejecuta el boot() de los providers normales. No estar incluido en la caché no es lo mismo que no registrarse cuando existe la caché. También es posible usar $listen en un paquete que herede del EventServiceProvider de Foundation. Sin embargo, en la implementación revisada, el valor predeterminado de shouldDiscoverEvents() es una comprobación sobre la propia clase base. Heredar de ella no hace que el directorio de listeners del paquete se descubra automáticamente. Además, $subscribe y $observers de ese mismo provider se procesan aparte del bucle que registra el resultado de getEvents(). No concluyas, fijándote solo en la caché de $listen, que también se omiten estos registros.
Event::subscribe(SomeSubscriber::class) crea el subscriber con el contenedor en el momento del registro y llama a subscribe(). No tiene la misma carga diferida que Event::listen() con un nombre de clase. Evita efectos secundarios, como comunicaciones externas, en el constructor del subscriber y en subscribe().

Evitar el doble registro con el descubrimiento automático

Si el usuario añade el directorio de listeners del paquete con withEvents(discover: [...]) y el provider del paquete también registra explícitamente los mismos listeners, el proceso puede ejecutarse varias veces para un mismo evento. Dispatcher::listen() añade elementos al array para los eventos normales, y el Dispatcher en su conjunto no elimina automáticamente los nombres de clase duplicados. El array_unique() que el EventServiceProvider de Foundation aplica dentro de su propio mapeo tampoco unifica los registros explícitos realizados por otros providers. Asigna la responsabilidad del registro a una sola parte.
  • Si el paquete registra los listeners estándar, no pidas al usuario que añada ese directorio al descubrimiento automático.
  • Si el usuario elige los listeners que necesita, el paquete proporciona las clases de evento y de listener, y no registra esos mismos listeners de forma predeterminada.
  • Al migrar del descubrimiento automático al registro explícito, indica que también deben actualizarse la configuración de descubrimiento del usuario y las cachés de eventos antiguas.
La idempotencia del proceso de negocio también es importante, pero no ocultes el doble registro solo con idempotencia: ordena las rutas de registro.

Verificar el comportamiento antes y después de actualizar

Para comprobar el estado del registro, ejecuta event:list en una aplicación de verificación que tenga instalado el paquete. Confirma que el listener existe tanto sin caché como con ella.
Este es un procedimiento para la aplicación de verificación. La salida de event:list por sí sola no garantiza que el listener complete su proceso ni que se ejecute una sola vez. Añade también pruebas que disparen el evento realmente. Como ejemplo, después de registrar CourierServiceProvider en el TestCase base de Testbench, verificamos que el logger se llama exactamente una vez.
Event::fake() es útil para comprobar que se dispara un evento, pero normalmente impide la ejecución de los listeners reales. En esta prueba no se utiliza porque se verifican el registro y el procesamiento. Comprueba también la combinación con el descubrimiento automático y el arranque con caché en otra aplicación de verificación o en una nueva instancia de la aplicación.
Ejecutar event:cache después del arranque no reconstruye a partir de la caché el Dispatcher ya registrado en ese proceso. Verifica el comportamiento con caché en un nuevo arranque. Para que los listeners actualizados se reflejen en workers de larga duración, también es necesario reiniciarlos siguiendo el procedimiento de despliegue habitual.

Lista de comprobación para el mantenimiento a largo plazo

  • Documenta los nombres de las clases de evento, sus propiedades públicas y la política de registro de listeners como un contrato con los usuarios.
  • Cuando cambies el nombre o el payload de los eventos a los que se suscriben los usuarios, comunica el cambio siguiendo la gestión de compatibilidad de versiones.
  • En las actualizaciones que modifiquen el descubrimiento automático o $listen, incluye la regeneración de event:cache en el procedimiento de despliegue.
  • Distingue los listeners estándar del paquete de los listeners propios del usuario, y no provoques que el mismo listener estándar se registre dos veces.
  • Mantén, para cada versión compatible de PHP y Laravel, pruebas que verifiquen que el listener se ejecuta una sola vez.

Fuentes primarias consultadas

Última modificación el 13 de octubre de 2026