Install via Composer (for Symfony 2 compatibility):
composer require egulias/listeners-debug
Note: This will pull Symfony 2 dependencies, which may conflict with Laravel.
Create a Laravel-Compatible Wrapper (since the package targets Symfony 2):
// app/Services/LaravelListenerFetcher.php
namespace App\Services;
use Illuminate\Contracts\Events\Dispatcher;
class LaravelListenerFetcher
{
public function __construct(private Dispatcher $events)
{
}
public function fetchListeners(string $eventName = null): array
{
return $eventName
? $this->events->getListeners($eventName)
: $this->events->getListeners();
}
}
Register the Service in AppServiceProvider:
public function register()
{
$this->app->singleton(LaravelListenerFetcher::class, function ($app) {
return new LaravelListenerFetcher($app['events']);
});
}
First Use Case: Dump All Listeners
use App\Services\LaravelListenerFetcher;
$listeners = app(LaravelListenerFetcher::class)->fetchListeners();
dd($listeners);
Output: Array of all registered listeners with their closures/services.
Filter by Event (Laravel-native equivalent):
$userCreatedListeners = app(LaravelListenerFetcher::class)->fetchListeners('user.created');
dd($userCreatedListeners);
app('events')->getListeners() before adapting this package.event_listener tag usage.spatie/laravel-debugbar for a GUI-based listener inspector.// Get all listeners for a specific event
$listeners = app(LaravelListenerFetcher::class)->fetchListeners('order.placed');
// Iterate with priorities (Laravel doesn't natively support priority, but you can add it)
foreach ($listeners as $priority => $handler) {
echo "Priority: $priority, Handler: " . get_class($handler) . "\n";
}
// app/Http/Middleware/DebugListeners.php
public function handle($request, Closure $next)
{
if ($request->query('debug_listeners')) {
$event = $request->query('event');
dd(app(LaravelListenerFetcher::class)->fetchListeners($event));
}
return $next($request);
}
Usage: ?debug_listeners=1&event=user.created
// Generate a DOT graph for event dependencies
$graph = "digraph EventFlow {\n";
$listeners = app(LaravelListenerFetcher::class)->fetchListeners();
foreach ($listeners as $event => $handlers) {
$graph .= " \"$event\" -> {\n";
foreach ($handlers as $handler) {
$graph .= " \"" . get_class($handler) . "\"\n";
}
$graph .= " }\n";
}
$graph .= "}";
file_put_contents(storage_path('app/event_graph.dot'), $graph);
Render with: dot -Tpng event_graph.dot -o event_graph.png
# Add to your deploy script to validate event listeners
php artisan tinker --execute="
$listeners = app(LaravelListenerFetcher::class)->fetchListeners();
if (empty($listeners['user.created'])) {
throw new Exception('Critical event listener missing!');
}
"
Identify Missing Listeners:
$expectedListeners = ['App\Listeners\SendWelcomeEmail'];
$actualListeners = array_map(
fn($handler) => is_string($handler) ? $handler : get_class($handler),
app(LaravelListenerFetcher::class)->fetchListeners('user.created')
);
$missing = array_diff($expectedListeners, $actualListeners);
if (!empty($missing)) {
throw new \RuntimeException("Missing listeners: " . implode(', ', $missing));
}
Log Listener Execution (via a decorator):
// app/Providers/EventServiceProvider.php
public function boot()
{
$this->events->listen('*', function ($event) {
$listeners = app(LaravelListenerFetcher::class)->fetchListeners(get_class($event));
logger()->debug("Event triggered: " . get_class($event) . ". Listeners: " . json_encode($listeners));
});
}
// Measure listener execution time
$listeners = app(LaravelListenerFetcher::class)->fetchListeners('order.processed');
$times = [];
foreach ($listeners as $handler) {
$start = microtime(true);
$handler($event = new \stdClass()); // Dummy event
$times[get_class($handler)] = microtime(true) - $start;
}
dd($times);
If your Laravel app integrates with Symfony 2 (e.g., via a microkernel):
// app/Services/SymfonyListenerFetcher.php
use Egulias\ListenersDebug\ListenerFetcher;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class SymfonyListenerFetcher
{
public function __construct(private ContainerBuilder $container)
{
}
public function fetchListeners(bool $showPrivate = false): array
{
$symfonyFetcher = new ListenerFetcher($this->container);
return $symfonyFetcher->fetchListeners($showPrivate);
}
}
Register the ContainerBuilder via a Symfony 2 service provider.
// Add tags or descriptions to listeners
$listeners = app(LaravelListenerFetcher::class)->fetchListeners();
$enriched = [];
foreach ($listeners as $event => $handlers) {
$enriched[$event] = [];
foreach ($handlers as $handler) {
$class = is_string($handler) ? $handler : get_class($handler);
$enriched[$event][] = [
'handler' => $class,
'description' => $this->getListenerDescription($class),
];
}
}
dd($enriched);
// app/Console/Commands/DebugListeners.php
use Illuminate\Console\Command;
class DebugListeners extends Command
{
protected $signature = 'debug:listeners {event?}';
protected $description = 'List all event listeners';
public function handle()
{
$listeners = app(LaravelListenerFetcher::class)->fetchListeners($this->argument('event'));
$this->table(['Event', 'Handler'], collect($listeners)->mapWithKeys(function ($handlers, $event) {
return [$event => array_map(fn($h) => is_string($h) ? $h : get_class($h), $handlers)];
})->flatten());
}
}
Usage: php artisan debug:listeners user.created
Symfony Dependency Conflicts:
egulias/listeners-debug pulls in Symfony 2 components (e.g., symfony/dependency-injection:2.x), which may conflict with Laravel’s Symfony bridge or other packages.Laravel vs. Symfony API Mismatches:
event_listener tags in the DIC, but Laravel uses:
Event::listen()).$events->listen()).handle()).$handler = is_string($handler) ? $handler : (is_object($handler) ? get_class($handler) : 'closure');
Private Listeners:
$showPrivate parameter in the original package will always return false in Laravel.How can I help you explore Laravel packages today?