laravel-task-scheduling について
laravel-task-scheduling は、Laravel でスケジュールタスクを定義・実行するためのリファレンススキルです。コマンド、ジョブ、クロージャ、シェルコマンドに加え、実行条件、監視フック、出力管理を扱います。
主な機能
- 01コマンド、ジョブ、クロージャ、シェルコマンドのスケジュール
- 02時間と環境の実行条件設定
- 03タスクの重複実行防止
- 04単一サーバーでの実行制限
- 05フックとヘルスチェック ping の設定
- 06タスク出力の管理
活用シーン
- 01定期的なレポート生成
- 02指定した曜日や環境へのタスク制限
- 03複数サーバーでのタスク重複実行防止
- 04タスク出力の記録やメール送信
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ iserter/ laravel-claude-agents --skill laravel-task-schedulingインストール後、Claude Code で/laravel-task-schedulingを入力して読み込まれたか確認します。
詳細
タスクのスケジューリング
routes/console.php でのスケジュールの定義
<?php
use Illuminate\Support\Facades\Schedule;
// Schedule an Artisan command
Schedule::command('reports:generate')->daily();
// Schedule a job
Schedule::job(new ProcessDailyMetrics)->dailyAt('01:00');
// Schedule a closure
Schedule::call(function () {
Cache::flush();
})->weekly();
// Schedule a shell command
Schedule::exec('node /home/forge/script.js')->daily();
スケジュールの種類
Artisan コマンド
// With arguments and options
Schedule::command('emails:send', ['--force'])->daily();
Schedule::command('reports:generate --type=weekly')->sundays();
// Using command class
Schedule::command(SendEmailsCommand::class, ['--force'])->daily();
キューに登録されたジョブ
// Dispatch a job
Schedule::job(new CleanUpExpiredTokens)->daily();
// Dispatch to a specific queue and connection
Schedule::job(new ProcessAnalytics, 'analytics', 'redis')->hourly();
クロージャ
Schedule::call(function () {
DB::table('sessions')
->where('last_activity', '<', now()->subHours(24))
->delete();
})->hourly()->description('Clean expired sessions');
シェルコマンド
Schedule::exec('pg_dump mydb > /backups/db.sql')->daily();
実行頻度のメソッド
// Common frequencies
Schedule::command('task')->everyMinute();
Schedule::command('task')->everyTwoMinutes();
Schedule::command('task')->everyFiveMinutes();
Schedule::command('task')->everyTenMinutes();
Schedule::command('task')->everyFifteenMinutes();
Schedule::command('task')->everyThirtyMinutes();
Schedule::command('task')->hourly();
Schedule::command('task')->hourlyAt(17); // At :17 past each hour
Schedule::command('task')->everyOddHour();
Schedule::command('task')->everyTwoHours();
Schedule::command('task')->everyThreeHours();
Schedule::command('task')->everyFourHours();
Schedule::command('task')->everySixHours();
Schedule::command('task')->daily();
Schedule::command('task')->dailyAt('13:00');
Schedule::command('task')->twiceDaily(1, 13); // At 1:00 and 13:00
Schedule::command('task')->twiceDailyAt(1, 13, 15); // At 1:15 and 13:15
Schedule::command('task')->weekly();
Schedule::command('task')->weeklyOn(1, '8:00'); // Monday at 8:00
Schedule::command('task')->monthly();
Schedule::command('task')->monthlyOn(4, '15:00'); // 4th of month at 15:00
Schedule::command('task')->twiceMonthly(1, 16); // 1st and 16th
Schedule::command('task')->lastDayOfMonth('17:00');
Schedule::command('task')->quarterly();
Schedule::command('task')->quarterlyOn(4, '14:00'); // 4th day of quarter
Schedule::command('task')->yearly();
Schedule::command('task')->yearlyOn(6, 1, '17:00'); // June 1st at 17:00
// Cron expression
Schedule::command('task')->cron('0 */6 * * *'); // Every 6 hours
制約
時刻の制約
// Between specific times
Schedule::command('task')->hourly()->between('8:00', '17:00');
// Outside specific times
Schedule::command('task')->hourly()->unlessBetween('23:00', '04:00');
曜日の制約
Schedule::command('task')->daily()->weekdays();
Schedule::command('task')->daily()->weekends();
Schedule::command('task')->daily()->sundays();
Schedule::command('task')->daily()->mondays();
Schedule::command('task')->daily()->tuesdays();
Schedule::command('task')->daily()->wednesdays();
Schedule::command('task')->daily()->thursdays();
Schedule::command('task')->daily()->fridays();
Schedule::command('task')->daily()->saturdays();
Schedule::command('task')->daily()->days([0, 3]); // Sunday and Wednesday
環境の制約
// ✅ Only run in production
Schedule::command('analytics:process')->daily()->environments(['production']);
// ✅ Skip in testing
Schedule::command('reports:send')->daily()->environments(['production', 'staging']);
条件付きの制約
// Run only when condition is true
Schedule::command('emails:send')
->daily()
->when(function () {
return config('services.email.enabled');
});
// Skip when condition is true
Schedule::command('maintenance:run')
->daily()
->skip(function () {
return app()->isDownForMaintenance();
});
重複実行の防止
// ✅ Prevent overlapping: skip if previous instance is still running
Schedule::command('reports:generate')
->hourly()
->withoutOverlapping();
// With custom expiration (default is 24 hours)
Schedule::command('reports:generate')
->hourly()
->withoutOverlapping(expiresAt: 60); // Lock expires after 60 minutes
分散環境(onOneServer)
// ✅ Only run on a single server (requires memcached, redis, or database cache driver)
Schedule::command('reports:generate')
->daily()
->onOneServer();
// Combine with overlap prevention
Schedule::command('analytics:process')
->hourly()
->onOneServer()
->withoutOverlapping();
スケジュールグループ
// ✅ Apply shared configuration to multiple scheduled tasks
Schedule::command('analytics:aggregate')
->daily()
->onOneServer()
->withoutOverlapping()
->emailOutputOnFailure('[email protected]');
Schedule::command('analytics:cleanup')
->daily()
->onOneServer()
->withoutOverlapping()
->emailOutputOnFailure('[email protected]');
// Group with shared config (if using a helper)
collect([
'analytics:aggregate',
'analytics:cleanup',
'analytics:summary',
])->each(function ($command) {
Schedule::command($command)
->daily()
->onOneServer()
->withoutOverlapping()
->emailOutputOnFailure('[email protected]');
});
フック(before、after、onSuccess、onFailure、ping)
Schedule::command('reports:generate')
->daily()
->before(function () {
Log::info('Starting report generation...');
})
->after(function () {
Log::info('Report generation complete.');
})
->onSuccess(function () {
Notification::route('slack', '#ops')
->notify(new ScheduledTaskSucceeded('reports:generate'));
})
->onFailure(function () {
Notification::route('slack', '#alerts')
->notify(new ScheduledTaskFailed('reports:generate'));
});
URL への ping(ヘルスチェック)
// Ping a URL before and after
Schedule::command('reports:generate')
->daily()
->pingBefore('https://health.example.com/start')
->thenPing('https://health.example.com/end');
// Ping only on success or failure
Schedule::command('reports:generate')
->daily()
->pingOnSuccess('https://health.example.com/success')
->pingOnFailure('https://health.example.com/failure');
// Works with services like Oh Dear, Healthchecks.io, Cronitor
Schedule::command('reports:generate')
->daily()
->pingBefore('https://hc-ping.com/your-uuid/start')
->thenPing('https://hc-ping.com/your-uuid');
メール出力
Schedule::command('reports:generate')
->daily()
->sendOutputTo('/var/log/reports.log')
->emailOutputTo('[email protected]');
// Only email on failure
Schedule::command('reports:generate')
->daily()
->emailOutputOnFailure('[email protected]');
サブ分単位のスケジューリング
// ✅ Run every second (Laravel 11+)
Schedule::call(function () {
// Process real-time data
MetricsCollector::capture();
})->everySecond();
// Every 10 seconds
Schedule::call(function () {
QueueMonitor::check();
})->everyTenSeconds();
// Every 15 seconds
Schedule::command('queue:monitor')->everyFifteenSeconds();
// Every 20 / 30 seconds
Schedule::call(fn () => HealthCheck::ping())->everyTwentySeconds();
Schedule::call(fn () => HealthCheck::ping())->everyThirtySeconds();
出力の管理
// Send output to a file
Schedule::command('reports:generate')
->daily()
->sendOutputTo('/var/log/schedule/reports.log');
// Append output to a file
Schedule::command('reports:generate')
->daily()
->appendOutputTo('/var/log/schedule/reports.log');
// Write output to storage
Schedule::command('reports:generate')
->daily()
->appendOutputTo(storage_path('logs/reports.log'));
// Discard output
Schedule::command('reports:generate')
->daily()
->sendOutputTo('/dev/null');
メンテナンスモードでの実行
// ✅ Run even during maintenance mode
Schedule::command('queue:work --stop-when-empty')
->everyMinute()
->evenInMaintenanceMode();
スケジューラーの実行
# Add to crontab (required for scheduling to work)
* * * * * cd /path-to-project && php artisan schedule:run >> /dev/null 2>&1
# For sub-minute scheduling
* * * * * cd /path-to-project && php artisan schedule:run >> /dev/null 2>&1
# Local development - runs scheduler in the foreground
php artisan schedule:work
# List all scheduled tasks
php artisan schedule:list
# Test a specific scheduled command
php artisan schedule:test
ベストプラクティス
// ✅ Always give scheduled closures a description
Schedule::call(function () {
// ...
})->daily()->description('Clean expired sessions');
// ✅ Use onOneServer for distributed deployments
Schedule::command('sitemap:generate')->daily()->onOneServer();
// ✅ Use withoutOverlapping for long-running tasks
Schedule::command('import:products')->hourly()->withoutOverlapping();
// ✅ Monitor with hooks and pings
Schedule::command('billing:charge')
->daily()
->onSuccess(fn () => Log::info('Billing completed'))
->onFailure(fn () => Log::critical('Billing failed'))
->pingOnFailure('https://alerts.example.com/billing');
// ❌ Don't schedule heavy work without overlap prevention
Schedule::command('heavy:task')->everyMinute(); // Could stack up
// ❌ Don't forget to set the timezone if needed
Schedule::command('report:daily')
->dailyAt('09:00')
->timezone('America/New_York');
チェックリスト
- スケジュールが
routes/console.phpで定義されている - 実行頻度のメソッドが実際の業務要件に合っている
- 実行時間の長いタスクに
withoutOverlapping()を使用している - マルチサーバー構成で
onOneServer()を使用している - 適切な場合に環境制約を適用している
- 監視用にフック(before、after、onSuccess、onFailure)を設定している
- 重要なタスクにヘルスチェック用の ping を設定している
- デバッグ用に出力をログに記録するか、メールで送信している
-
schedule:listで確認できるよう、クロージャに説明を付けている -
schedule:runを実行する crontab エントリをサーバーに追加している - 時刻が重要な場合はタイムゾーンを明示的に設定している
- 必須タスクに
evenInMaintenanceMode()を使用している
コメント
Markdown 対応