> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Registro y caché de listeners de eventos en paquetes

> Analiza la implementación del registro de eventos y de event:cache en Laravel 13, y explica el registro explícito en paquetes, el doble registro con el descubrimiento automático, la resolución de dependencias y cómo verificar el comportamiento al actualizar.

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](/es/events) al mantenimiento a largo plazo de un paquete. Para la carga de providers, consulta [Descubrimiento de paquetes](/es/advanced/package-discovery); para el entorno de pruebas, consulta [Pruebas de paquetes](/es/advanced/package-testing).

## 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.

```php theme={null}
<?php

namespace Acme\Courier\Events;

final class ShipmentDelivered
{
    public function __construct(public readonly string $shipmentId)
    {
    }
}
```

El listener recibe el logger en el constructor y procesa el evento en `handle()`.

```php theme={null}
<?php

namespace Acme\Courier\Listeners;

use Acme\Courier\Events\ShipmentDelivered;
use Psr\Log\LoggerInterface;

final class LogDeliveredShipment
{
    public function __construct(private readonly LoggerInterface $logger)
    {
    }

    public function handle(ShipmentDelivered $event): void
    {
        $this->logger->info('Shipment delivered', [
            'shipment_id' => $event->shipmentId,
        ]);
    }
}
```

En el provider se registran los nombres de clase del evento y del listener.

```php theme={null}
<?php

namespace Acme\Courier;

use Acme\Courier\Events\ShipmentDelivered;
use Acme\Courier\Listeners\LogDeliveredShipment;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\ServiceProvider;

class CourierServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Event::listen(
            ShipmentDelivered::class,
            LogDeliveredShipment::class,
        );
    }
}
```

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()`.

<Warning>
  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](/es/advanced/deferred-provider).
</Warning>

## 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.

```mermaid theme={null}
flowchart TD
    A["boot() del provider"] --> B["Event::listen() guarda el nombre de clase"]
    B --> C["Se dispara ShipmentDelivered"]
    C --> D["El Dispatcher selecciona los listeners"]
    D --> E["El contenedor resuelve el listener síncrono"]
    E --> F["Se pasa el evento a handle()"]
```

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](/es/advanced/package-commands).

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](/es/events) y las [colas](/es/queues) 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é.**

| Método de registro | Contenido guardado por `event:cache` | Arranque con caché |
| - | - | - |
| `Event::listen()` en el `boot()` de un provider normal | Este registro no forma parte de la recopilación | Se vuelve a registrar en `boot()` |
| `$listen` del `EventServiceProvider` de Foundation | Se guarda el mapeo por nombre de clase del provider | `getEvents()` lee la caché |
| Listeners incluidos en el descubrimiento automático | Se guarda el resultado del descubrimiento en el mapeo | Se usa la caché en lugar de volver a recorrer los directorios |

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.

<Tip>
  `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()`.
</Tip>

## 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.

```bash theme={null}
php artisan event:clear
php artisan event:list
php artisan event:cache
php artisan event:list
```

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](/es/advanced/package-testing), verificamos que el logger se llama exactamente una vez.

```php theme={null}
<?php

namespace Acme\Courier\Tests\Feature;

use Acme\Courier\Events\ShipmentDelivered;
use Acme\Courier\Tests\TestCase;
use Illuminate\Support\Facades\Event;
use Psr\Log\LoggerInterface;

class PackageEventsTest extends TestCase
{
    public function test_delivery_is_logged_once(): void
    {
        $logger = $this->createMock(LoggerInterface::class);
        $logger->expects($this->once())
            ->method('info')
            ->with('Shipment delivered', ['shipment_id' => 'shipment-123']);

        $this->app->instance(LoggerInterface::class, $logger);

        Event::dispatch(new ShipmentDelivered('shipment-123'));
    }
}
```

`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.

<Warning>
  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.
</Warning>

## 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](/es/advanced/package-versioning).
* 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

* [Documentación oficial de Laravel: registro de eventos, descubrimiento automático, caché y subscribers](https://github.com/laravel/docs/blob/73e61270287e4aaa1cecd77cd66aa6ef4f499828/events.md)
* [Dispatcher: listen, subscribe y createClassCallable](https://github.com/laravel/framework/blob/v13.36.0/src/Illuminate/Events/Dispatcher.php)
* [Foundation EventServiceProvider: register, getEvents y shouldDiscoverEvents](https://github.com/laravel/framework/blob/v13.36.0/src/Illuminate/Foundation/Support/Providers/EventServiceProvider.php)
* [EventCacheCommand: recopilación de los elementos de la caché](https://github.com/laravel/framework/blob/v13.36.0/src/Illuminate/Foundation/Console/EventCacheCommand.php)


## Related topics

- [Temas avanzados](/es/advanced/index.md)
- [Registro de rutas y caché en paquetes](/es/advanced/package-routes.md)
- [Eventos y listeners](/es/events.md)
- [Eventos](/es/packages/laravel-copilot-sdk/events.md)
- [Service providers diferidos](/es/advanced/deferred-provider.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.