Планировщик задач Laravel позволяет определять расписание выполнения команд внутри приложения, заменяя множество cron-записей одной. Расписание определяется в методе schedule класса routes/console.php или в App\Console\Kernel.
Настройка планировщика
Для работы планировщика нужна одна cron-запись на сервере:
# Add to crontab (crontab -e)
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
В Laravel 11+ расписание определяется в routes/console.php:
<?php
// routes/console.php
use Illuminate\Support\Facades\Schedule;
use App\Console\Commands\SendDailyReport;
use App\Jobs\CleanUpExpiredTokens;
use App\Models\User;
// Schedule an Artisan command
Schedule::command('inspire')->hourly();
// Schedule with full command signature
Schedule::command('emails:send --force')->daily();
// Schedule a job
Schedule::job(new CleanUpExpiredTokens)->daily();
// Schedule a closure
Schedule::call(function () {
User::where('trial_ends_at', '<', now())
->update(['is_active' => false]);
})->daily();
// Schedule a shell command
Schedule::exec('node /home/forge/script.js')->daily();
Параметры частоты
Laravel предоставляет множество методов для определения частоты выполнения.
use Illuminate\Support\Facades\Schedule;
// Basic frequencies
Schedule::command('report:generate')->everySecond();
Schedule::command('report:generate')->everyTwoSeconds();
Schedule::command('report:generate')->everyFiveSeconds();
Schedule::command('report:generate')->everyTenSeconds();
Schedule::command('report:generate')->everyFifteenSeconds();
Schedule::command('report:generate')->everyTwentySeconds();
Schedule::command('report:generate')->everyThirtySeconds();
Schedule::command('report:generate')->everyMinute();
Schedule::command('report:generate')->everyTwoMinutes();
Schedule::command('report:generate')->everyThreeMinutes();
Schedule::command('report:generate')->everyFourMinutes();
Schedule::command('report:generate')->everyFiveMinutes();
Schedule::command('report:generate')->everyTenMinutes();
Schedule::command('report:generate')->everyFifteenMinutes();
Schedule::command('report:generate')->everyThirtyMinutes();
Schedule::command('report:generate')->hourly();
Schedule::command('report:generate')->hourlyAt(17); // At :17 past the hour
Schedule::command('report:generate')->everyOddHour();
Schedule::command('report:generate')->everyTwoHours();
Schedule::command('report:generate')->everyThreeHours();
Schedule::command('report:generate')->everyFourHours();
Schedule::command('report:generate')->everySixHours();
Schedule::command('report:generate')->daily();
Schedule::command('report:generate')->dailyAt('13:00');
Schedule::command('report:generate')->twiceDaily(1, 13); // At 1:00 & 13:00
Schedule::command('report:generate')->twiceDailyAt(1, 13, 15); // At 1:15 & 13:15
Schedule::command('report:generate')->weekly();
Schedule::command('report:generate')->weeklyOn(1, '8:00'); // Monday at 8:00
Schedule::command('report:generate')->monthly();
Schedule::command('report:generate')->monthlyOn(4, '15:00'); // 4th at 15:00
Schedule::command('report:generate')->twiceMonthly(1, 16, '13:00');
Schedule::command('report:generate')->lastDayOfMonth('17:00');
Schedule::command('report:generate')->quarterly();
Schedule::command('report:generate')->quarterlyOn(4, '14:00');
Schedule::command('report:generate')->yearly();
Schedule::command('report:generate')->yearlyOn(6, 1, '17:00'); // June 1st
Ограничения дня
use Illuminate\Support\Facades\Schedule;
// Day constraints
Schedule::command('report:generate')
->daily()
->weekdays(); // Mon-Fri
Schedule::command('report:generate')
->daily()
->weekends(); // Sat-Sun
Schedule::command('report:generate')
->daily()
->sundays(); // Only Sundays
Schedule::command('report:generate')
->daily()
->mondays(); // Only Mondays
Schedule::command('report:generate')
->daily()
->tuesdays();
Schedule::command('report:generate')
->daily()
->wednesdays();
Schedule::command('report:generate')
->daily()
->thursdays();
Schedule::command('report:generate')
->daily()
->fridays();
Schedule::command('report:generate')
->daily()
->saturdays();
// Multiple day constraints
Schedule::command('report:generate')
->daily()
->days([0, 3, 5]); // Sun, Wed, Fri
Временные ограничения
use Illuminate\Support\Facades\Schedule;
// Time constraints
Schedule::command('report:generate')
->hourly()
->between('8:00', '17:00'); // Only between 8am-5pm
Schedule::command('report:generate')
->hourly()
->unlessBetween('23:00', '4:00'); // Not between 11pm-4am
Cron-выражения
use Illuminate\Support\Facades\Schedule;
// Raw cron expression
Schedule::command('report:generate')
->cron('0 */6 * * *'); // Every 6 hours
Часовые пояса
use Illuminate\Support\Facades\Schedule;
Schedule::command('report:generate')
->daily()
->timezone('Europe/Moscow');
// Or set default timezone for all tasks
// In App\Providers\AppServiceProvider
protected function scheduleTimezone(): \DateTimeZone
{
return new \DateTimeZone('Europe/Moscow');
}
Предотвращение перекрытий
Метод withoutOverlapping() гарантирует, что одновременно выполняется только один экземпляр задачи.
use Illuminate\Support\Facades\Schedule;
// Default lock expiration: 24 hours
Schedule::command('report:generate')
->hourly()
->withoutOverlapping();
// Custom lock expiration (in minutes)
Schedule::command('report:generate')
->hourly()
->withoutOverlapping(30); // Lock expires after 30 minutes
Блокировка освобождается после завершения задачи или по истечении таймаута. По умолчанию используется кэш-драйвер для хранения блокировок.
Выполнение на одном сервере
При использовании нескольких серверов задача по умолчанию выполняется на каждом. Метод onOneServer() гарантирует выполнение на одном сервере.
use Illuminate\Support\Facades\Schedule;
Schedule::command('report:generate')
->daily()
->onOneServer();
// Requires a centralized cache driver: memcached, redis, dynamodb, database
// Does NOT work with the 'file' cache driver
Важно: onOneServer() требует централизованный кэш-драйвер (redis, memcached, database). Файловый кэш не подходит, так как каждый сервер имеет свою файловую систему.
Условное выполнение
use Illuminate\Support\Facades\Schedule;
// when() - execute only if true
Schedule::command('emails:send')
->daily()
->when(function () {
return app()->environment('production');
});
// skip() - skip if true
Schedule::command('emails:send')
->daily()
->skip(function () {
return app()->isDownForMaintenance();
});
// environments() - only on specified environments
Schedule::command('emails:send')
->daily()
->environments(['staging', 'production']);
Режим обслуживания (Maintenance Mode)
По умолчанию задачи не выполняются в режиме обслуживания. Метод evenInMaintenanceMode() позволяет принудительно выполнять задачу.
use Illuminate\Support\Facades\Schedule;
// Will run even during maintenance mode
Schedule::command('heartbeat:check')
->everyMinute()
->evenInMaintenanceMode();
Обработка вывода
use Illuminate\Support\Facades\Schedule;
// Send output to a file
Schedule::command('report:generate')
->daily()
->sendOutputTo('/var/log/schedule/report.log');
// Append output to a file
Schedule::command('report:generate')
->daily()
->appendOutputTo('/var/log/schedule/report.log');
// Email output (only if there is output)
Schedule::command('report:generate')
->daily()
->sendOutputTo('/var/log/schedule/report.log')
->emailOutputTo('[email protected]');
// Email output only on failure
Schedule::command('report:generate')
->daily()
->emailOutputOnFailure('[email protected]');
Хуки (Task Hooks)
use Illuminate\Support\Facades\Schedule;
Schedule::command('report:generate')
->daily()
->before(function () {
// Before the task runs
\Log::info('Starting report generation');
})
->after(function () {
// After the task completes
\Log::info('Report generation finished');
})
->onSuccess(function () {
// Only if the task succeeds
\Log::info('Report generated successfully');
})
->onFailure(function () {
// Only if the task fails
\Log::error('Report generation failed');
});
Ping URL (Webhooks)
use Illuminate\Support\Facades\Schedule;
Schedule::command('report:generate')
->daily()
->pingBefore('https://healthcheck.io/start/abc123')
->thenPing('https://healthcheck.io/complete/abc123')
->pingOnSuccess('https://healthcheck.io/success/abc123')
->pingOnFailure('https://healthcheck.io/failure/abc123');
// Ping with conditions
Schedule::command('report:generate')
->daily()
->pingBeforeIf($condition, $url)
->thenPingIf($condition, $url);
Sub-Minute Scheduling
Laravel 11 поддерживает выполнение задач чаще, чем раз в минуту.
use Illuminate\Support\Facades\Schedule;
// Run every second
Schedule::call(function () {
// Check for new messages
})->everySecond();
// Run every 10 seconds
Schedule::command('queue:monitor redis:default --max=100')
->everyTenSeconds();
// Run every 15 seconds
Schedule::job(new CheckServiceHealth)
->everyFifteenSeconds()
->withoutOverlapping();
Группировка задач
use Illuminate\Support\Facades\Schedule;
// Apply common settings to a group
Schedule::command('analytics:daily')->daily()->onOneServer()->withoutOverlapping();
Schedule::command('analytics:weekly')->weekly()->onOneServer()->withoutOverlapping();
Schedule::command('analytics:monthly')->monthly()->onOneServer()->withoutOverlapping();
Artisan-команды для мониторинга
# List all scheduled tasks
php artisan schedule:list
# Run the scheduler manually
php artisan schedule:run
# Run the scheduler in the foreground (development)
php artisan schedule:work
# Test a specific scheduled command
php artisan schedule:test
# Interrupt currently running scheduled tasks
php artisan schedule:interrupt
Практический пример: полная конфигурация
<?php
// routes/console.php
use Illuminate\Support\Facades\Schedule;
// Daily maintenance tasks
Schedule::command('sanctum:prune-expired --hours=24')
->daily()
->onOneServer()
->withoutOverlapping()
->appendOutputTo(storage_path('logs/schedule.log'));
// Hourly report generation
Schedule::command('reports:generate --type=hourly')
->hourly()
->between('6:00', '22:00')
->weekdays()
->onOneServer()
->withoutOverlapping(30)
->onSuccess(function () {
\Log::channel('schedule')->info('Hourly report generated');
})
->onFailure(function () {
\Log::channel('schedule')->error('Hourly report failed');
});
// Queue monitoring
Schedule::command('queue:monitor redis:default,redis:high --max=100')
->everyFiveMinutes()
->environments(['production']);
// Database cleanup
Schedule::command('model:prune --model=App\\Models\\ActivityLog')
->daily()
->at('03:00')
->onOneServer()
->evenInMaintenanceMode();
// Health checks
Schedule::call(function () {
$services = ['redis', 'database', 'queue'];
foreach ($services as $service) {
// Perform health check
}
})->everyMinute()
->onOneServer()
->pingOnFailure('https://healthcheck.io/fail/abc123');
// Weekly digest
Schedule::command('emails:weekly-digest')
->weeklyOn(1, '9:00') // Monday at 9:00 AM
->timezone('Europe/Moscow')
->onOneServer()
->withoutOverlapping()
->emailOutputOnFailure('[email protected]');