laravel/horizon
Laravel Horizon adds a polished dashboard and code-driven configuration for Laravel Redis queues. Monitor throughput, runtimes, and failed jobs, and manage all worker and supervisor settings from a single config file kept in source control.
Installation:
composer require laravel/horizon
Publish Horizon's configuration file:
php artisan horizon:publish
Configure Redis:
Ensure your config/horizon.php points to a Redis connection (default: redis).
'connections' => [
'redis' => [
'driver' => 'redis',
'connection' => 'redis',
'queue' => 'default',
'retry_after' => 90,
],
],
Run Horizon: Start the dashboard and workers:
php artisan horizon
Access the dashboard at http://your-app.test/horizon.
First Use Case: Dispatch a job and monitor it in real-time:
dispatch(new ProcessPodcast);
Check the dashboard for job status, runtime, and failures.
Job Monitoring:
Worker Configuration:
config/horizon.php:
'processes' => [
'default' => [
'balance' => ['SendPodcastEmail', 'ProcessPodcast'],
'supervisor' => 'throttle:10',
'tries' => 3,
],
],
balance to distribute jobs across workers and supervisor to throttle or batch jobs.Batch Processing:
$batch = Batch::dispatch(function () {
ProcessPodcast::dispatch($podcast1);
ProcessPodcast::dispatch($podcast2);
});
Delayed and Retry Jobs:
ProcessPodcast::dispatch($podcast)->delay(now()->addMinutes(10));
config/horizon.php:
'retry_via' => 'database',
'retry_after' => 90,
Custom Job Events:
JobProcessed, JobFailed):
ProcessPodcast::dispatch($podcast)->onQueue('podcasts')->onConnection('redis');
EventServiceProvider:
protected $listen = [
'JobFailed' => [
'App\Listeners\LogFailedJob',
],
];
Environment-Specific Config:
config/horizon-local.php):
'processes' => [
'local' => [
'balance' => ['*'],
'supervisor' => 'simple-queue',
],
],
config/horizon.php:
$config = require __DIR__.'/horizon.php';
if (app()->environment('local')) {
$config = array_merge($config, require __DIR__.'/horizon-local.php');
}
return $config;
CLI Integration:
horizon:listen for local development:
php artisan horizon:listen --queue=podcasts
php artisan horizon:reload
Testing:
Horizon::fake():
public function test_job_processing()
{
Horizon::fake();
ProcessPodcast::dispatch($podcast);
$this->assertProcessed(ProcessPodcast::class);
}
Redis Connection Issues:
horizon connection name (v5.35.0+).Memory Leaks:
horizon:terminate to restart workers if needed.horizon:listen (v5.45.1).Job Stuck in Pending:
horizon:flush to clear stale jobs or fix the race condition (v5.42.0).Large Queue Backlogs:
Environment-Specific Quirks:
local environments (v5.45.0).Deprecated Features:
horizon:publish is deprecated (v5.45.0). Use horizon:install instead.Logs:
storage/logs/horizon.log for worker errors.config/horizon.php:
'debug' => env('APP_DEBUG', false),
Job Inspection:
horizon:inspect to debug specific jobs:
php artisan horizon:inspect <job-id>
Worker Supervision:
horizon:terminate to restart workers gracefully.Redis Cluster Support:
config/redis.php is configured for clustering:
'cluster' => [
'driver' => 'predis',
'connection' => 'redis',
'options' => [
'cluster' => 'redis',
],
],
Performance Tuning:
balance and supervisor settings to optimize throughput.throttle to limit job processing rates:
'supervisor' => 'throttle:10',
Customizing the Dashboard:
resources/views/vendor/horizon/layouts/app.blade.php).public/vendor/horizon.Silencing Tags:
silenced_tags (v5.34.0):
'silenced_tags' => ['logs', 'notifications'],
PHP 8.5+ Compatibility:
setAccessible() guard for PHP 8.5+ (v5.40.1). Example:
if (method_exists($model, 'setAccessible')) {
$model->setAccessible(['attribute']);
} else {
$model->setFillable(['attribute']);
}
Custom Job Metrics:
Horizon\Jobs\Job to add custom metrics:
class ProcessPodcast extends Job
{
public function handle()
{
// Custom logic
$this->recordMetrics(['podcast_id' => $this->podcast->id]);
}
}
Event Listeners:
JobProcessed, SupervisorTerminated):
public function boot()
{
Horizon::routeJobFailed(function ($exception, $job) {
Log::error("Job failed: {$job->getJobId()}", [
'exception' => $exception,
'job' => $job,
]);
});
}
Middleware:
class LogJobMiddleware
{
public function handle($job, Closure $next)
{
Log::info("Processing job {$job->getJobId()}");
return $next($job);
}
}
Register in AppServiceProvider:
Horizon::middleware([LogJobMiddleware::class]);
Custom Supervisors:
Horizon\Supervisors\Supervisor for custom worker logic:
class CustomSupervisor extends Supervisor
{
public function start()
{
// Custom startup logic
}
public function stop()
{
// Custom shutdown logic
}
}
Register in config/horizon.php:
'supervisors' => [
'custom' => CustomSupervisor::class,
],
Batch Customization:
Horizon\Jobs\Batch for custom batch behavior:
class CustomBatch extends Batch
{
public function handle()
{
How can I help you explore Laravel packages today?