At boot, event discovery reflects over app/Listeners and registers each public handle* or __invoke method for the event type-hinted on its first parameter (a union type registers it for each). Anything else goes in AppServiceProvider::boot(), where a closure's event also comes from its type hint:
Event::listen(function (ProductRestocked $event) {
Log::info("Restocked {$event->product->sku} (+{$event->added})");
});Output
$ php artisan event:list --event='App\Events' App\Events\OrderPlaced ..................................................... ⇂ App\Listeners\CatalogCacheSubscriber@forgetAfterSale ⇂ App\Listeners\NotifyWarehouse@handle (ShouldQueue) ⇂ App\Listeners\ReserveStock@handle ⇂ App\Listeners\SendOrderConfirmation@handle App\Events\ProductRestocked ................................................ ⇂ App\Listeners\CatalogCacheSubscriber@forgetAfterRestock ⇂ Closure at: /app/Providers/AppServiceProvider.php:31
->withEvents(discover: [...]) in bootstrap/app.php scans other folders. Deployments run php artisan event:cache (or optimize, Config and Route Caching) to write bootstrap/cache/events.php. A listener added after that is ignored until you cache again, the usual cause of "my listener never runs".