タスクスケジュール
イントロダクション
以前は、サーバでスケジュールする必要があるタスクごとにcron設定エントリを書いていたかもしれません。しかし、タスクスケジュールがソース管理されなくなり、既存のcronエントリを表示したり、エントリを追加したりするためにサーバにSSH接続する必要があるため、これはすぐに面倒になります。
Laravelのコマンドスケジューラは、サーバ上のスケジュール済みタスクを管理するための新鮮なアプローチを提供します。スケジューラを使用すると、Laravelアプリケーション内でコマンドスケジュールを流暢かつ表現力豊かに定義できます。スケジューラを使用する場合、サーバ上で必要なcronエントリは1つだけです。タスクスケジュールは、通常、アプリケーションのroutes/console.phpファイルで定義します。
スケジュールの定義
すべてのスケジュール済みタスクは、アプリケーションのroutes/console.phpファイルで定義できます。まず、例を見てみましょう。この例では、毎日深夜に呼び出されるクロージャをスケジュールします。クロージャ内で、テーブルをクリアするデータベースクエリを実行します。
1<?php2 3use Illuminate\Support\Facades\DB;4use Illuminate\Support\Facades\Schedule;5 6Schedule::call(function () {7 DB::table('recent_users')->delete();8})->daily();
クロージャを使用したスケジューリングに加えて、呼び出し可能オブジェクトもスケジュールできます。呼び出し可能オブジェクトは、__invokeメソッドを含む単純なPHPクラスです。
1Schedule::call(new DeleteRecentUsers)->daily();
routes/console.phpファイルをコマンド定義専用にしたい場合は、アプリケーションのbootstrap/app.phpファイルでwithScheduleメソッドを使用してスケジュール済みタスクを定義できます。このメソッドは、スケジューラのインスタンスを受け取るクロージャを引数に取ります。
1use Illuminate\Console\Scheduling\Schedule;2 3->withSchedule(function (Schedule $schedule) {4 $schedule->call(new DeleteRecentUsers)->daily();5})
スケジュールされているタスクの概要と、次に実行される予定の時刻を確認したい場合は、schedule:list Artisanコマンドを使用します。
1php artisan schedule:list
Artisanコマンドのスケジューリング
クロージャのスケジューリングに加えて、Artisanコマンドとシステムコマンドもスケジュールできます。たとえば、commandメソッドを使用して、コマンドの名前またはクラスを使用してArtisanコマンドをスケジュールできます。
コマンドのクラス名を使用してArtisanコマンドをスケジュールする場合、コマンドが呼び出されたときに提供する必要のある追加のコマンドライン引数の配列を渡せます。
1use App\Console\Commands\SendEmailsCommand;2use Illuminate\Support\Facades\Schedule;3 4Schedule::command('emails:send Taylor --force')->daily();5 6Schedule::command(SendEmailsCommand::class, ['Taylor', '--force'])->daily();
クロージャArtisanコマンドのスケジューリング
クロージャによって定義されたArtisanコマンドをスケジュールしたい場合は、コマンドの定義の後にスケジューリング関連のメソッドをチェーンできます。
1Artisan::command('delete:recent-users', function () {2 DB::table('recent_users')->delete();3})->purpose('Delete recent users')->daily();
クロージャコマンドに引数を渡す必要がある場合は、scheduleメソッドに引数を指定できます。
1Artisan::command('emails:send {user} {--force}', function ($user) {2 // ...3})->purpose('Send emails to the specified user')->schedule(['Taylor', '--force'])->daily();
キュー投入するジョブのスケジューリング
jobメソッドは、キュー投入するジョブをスケジュールするために使用します。このメソッドは、ジョブをキューに入れるためのクロージャを定義するためにcallメソッドを使用せずに、キュー投入するジョブをスケジュールする便利な方法を提供します。
1use App\Jobs\Heartbeat;2use Illuminate\Support\Facades\Schedule;3 4Schedule::job(new Heartbeat)->everyFiveMinutes();
jobメソッドには、オプションの第2引数と第3引数を指定できます。これらは、ジョブをキューに追加するために使用するキュー名とキュー接続を指定します。
1use App\Jobs\Heartbeat;2use Illuminate\Support\Facades\Schedule;3 4// Dispatch the job to the "heartbeats" queue on the "sqs" connection...5Schedule::job(new Heartbeat, 'heartbeats', 'sqs')->everyFiveMinutes();
シェルコマンドのスケジューリング
execメソッドは、オペレーティングシステムにコマンドを発行するために使用します。
1use Illuminate\Support\Facades\Schedule;2 3Schedule::exec('node /home/forge/script.js')->daily();
スケジュール頻度オプション
指定した間隔でタスクを実行するように設定する方法の例をいくつか見てきました。しかし、タスクに割り当てることができるタスクスケジュールの頻度は他にもたくさんあります。
| メソッド | 説明 |
|---|---|
->cron('* * * * *'); |
カスタムcronスケジュールでタスクを実行 |
->everySecond(); |
毎秒タスクを実行 |
->everyTwoSeconds(); |
2秒ごとにタスクを実行 |
->everyFiveSeconds(); |
5秒ごとにタスクを実行 |
->everyTenSeconds(); |
10秒ごとにタスクを実行 |
->everyFifteenSeconds(); |
15秒ごとにタスクを実行 |
->everyTwentySeconds(); |
20秒ごとにタスクを実行 |
->everyThirtySeconds(); |
30秒ごとにタスクを実行 |
->everyMinute(); |
毎分タスクを実行 |
->everyTwoMinutes(); |
2分ごとにタスクを実行 |
->everyThreeMinutes(); |
3分ごとにタスクを実行 |
->everyFourMinutes(); |
4分ごとにタスクを実行 |
->everyFiveMinutes(); |
5分ごとにタスクを実行 |
->everyTenMinutes(); |
10分ごとにタスクを実行 |
->everyFifteenMinutes(); |
15分ごとにタスクを実行 |
->everyThirtyMinutes(); |
30分ごとにタスクを実行 |
->hourly(); |
毎時タスクを実行 |
->hourlyAt(17); |
毎時17分にタスクを実行 |
->everyOddHour($minutes = 0); |
奇数時間ごとにタスクを実行 |
->everyTwoHours($minutes = 0); |
2時間ごとにタスクを実行 |
->everyThreeHours($minutes = 0); |
3時間ごとにタスクを実行 |
->everyFourHours($minutes = 0); |
4時間ごとにタスクを実行 |
->everySixHours($minutes = 0); |
6時間ごとにタスクを実行 |
->daily(); |
毎日深夜0時にタスクを実行 |
->dailyAt('13:00'); |
毎日13:00にタスクを実行 |
->twiceDaily(1, 13); |
毎日1:00と13:00にタスクを実行 |
->twiceDailyAt(1, 13, 15); |
毎日1:15と13:15にタスクを実行 |
->weekly(); |
毎週日曜日の00:00にタスクを実行 |
->weeklyOn(1, '8:00'); |
毎週月曜日の8:00にタスクを実行 |
->monthly(); |
毎月1日の00:00にタスクを実行 |
->monthlyOn(4, '15:00'); |
毎月4日の15:00にタスクを実行 |
->twiceMonthly(1, 16, '13:00'); |
毎月1日と16日の13:00にタスクを実行 |
->lastDayOfMonth('15:00'); |
月の最終日の15:00にタスクを実行 |
->quarterly(); |
四半期ごと、最初の日の00:00にタスクを実行 |
->quarterlyOn(4, '14:00'); |
毎四半期の4日14:00にタスクを実行 |
->yearly(); |
毎年、最初の日の00:00にタスクを実行 |
->yearlyOn(6, 1, '17:00'); |
毎年6月1日の17:00にタスクを実行 |
->timezone('America/New_York'); |
タスクのタイムゾーンを設定 |
これらのメソッドを追加の制約と組み合わせて、特定の曜日にのみ実行される、さらに細かく調整されたスケジュールを作成できます。たとえば、毎週月曜日に実行するコマンドをスケジュールできます。
1use Illuminate\Support\Facades\Schedule; 2 3// Run once per week on Monday at 1 PM... 4Schedule::call(function () { 5 // ... 6})->weekly()->mondays()->at('13:00'); 7 8// Run hourly from 8 AM to 5 PM on weekdays... 9Schedule::command('foo')10 ->weekdays()11 ->hourly()12 ->timezone('America/Chicago')13 ->between('8:00', '17:00');
追加のスケジュール制約のリストは、以下にあります。
| メソッド | 説明 |
|---|---|
->weekdays(); |
タスクを平日に制限 |
->weekends(); |
タスクを週末に制限 |
->sundays(); |
タスクを日曜日に制限 |
->mondays(); |
タスクを月曜日に制限 |
->tuesdays(); |
タスクを火曜日に制限 |
->wednesdays(); |
タスクを水曜日に制限 |
->thursdays(); |
タスクを木曜日に制限 |
->fridays(); |
タスクを金曜日に制限 |
->saturdays(); |
タスクを土曜日に制限 |
->days(array|mixed); |
タスクを特定の曜日に制限 |
->between($startTime, $endTime); |
タスクを開始時刻と終了時刻の間に実行するように制限 |
->unlessBetween($startTime, $endTime); |
タスクを開始時刻と終了時刻の間に実行しないように制限 |
->when(Closure); |
真偽値テストに基づいてタスクを制限 |
->environments($env); |
タスクを特定の環境に制限 |
曜日制約
daysメソッドは、タスクの実行を特定の曜日に制限するために使用します。たとえば、毎週日曜と水曜に毎時実行するコマンドをスケジュールできます。
1use Illuminate\Support\Facades\Schedule;2 3Schedule::command('emails:send')4 ->hourly()5 ->days([0, 3]);
または、Illuminate\Console\Scheduling\Scheduleクラスで使用可能な定数を使用して、タスクを実行する曜日を定義することもできます。
1use Illuminate\Support\Facades;2use Illuminate\Console\Scheduling\Schedule;3 4Facades\Schedule::command('emails:send')5 ->hourly()6 ->days([Schedule::SUNDAY, Schedule::WEDNESDAY]);
時間制約
betweenメソッドは、時刻に基づいてタスクの実行を制限するために使用します。
1Schedule::command('emails:send')2 ->hourly()3 ->between('7:00', '22:00');
同様に、unlessBetweenメソッドを使用して、一定期間タスクの実行を除外できます。
1Schedule::command('emails:send')2 ->hourly()3 ->unlessBetween('23:00', '4:00');
真偽値テストによる制約
whenメソッドは、指定された真偽値テストの結果に基づいてタスクの実行を制限するために使用します。つまり、指定されたクロージャがtrueを返す場合、他の制約条件がタスクの実行を妨げない限り、タスクは実行されます。
1Schedule::command('emails:send')->daily()->when(function () {2 return true;3});
skipメソッドはwhenの逆と見なすことができます。skipメソッドがtrueを返す場合、スケジュールされたタスクは実行されません。
1Schedule::command('emails:send')->daily()->skip(function () {2 return true;3});
whenメソッドをチェーンして使用する場合、スケジュールされたコマンドは、すべてのwhen条件がtrueを返す場合にのみ実行されます。
環境制約
environmentsメソッドは、(APP_ENV環境変数で定義されている)指定された環境でのみタスクを実行するために使用します。
1Schedule::command('emails:send')2 ->daily()3 ->environments(['staging', 'production']);
タイムゾーン
timezoneメソッドを使用すると、スケジュールされたタスクの時刻が特定のタイムゾーン内で解釈されるように指定できます。
1use Illuminate\Support\Facades\Schedule;2 3Schedule::command('report:generate')4 ->timezone('America/New_York')5 ->at('2:00')
すべてのスケジュール済みタスクに同じタイムゾーンを繰り返し割り当てている場合は、アプリケーションのapp設定ファイルでschedule_timezoneオプションを定義することにより、すべてのスケジュールに割り当てるタイムゾーンを指定できます。
1'timezone' => 'UTC',2 3'schedule_timezone' => 'America/Chicago',
一部のタイムゾーンでは夏時間が採用されていることに注意してください。夏時間の変更が発生すると、スケジュールされたタスクが2回実行されたり、まったく実行されなかったりする場合があります。このため、可能な場合はタイムゾーンのスケジューリングを避けることをお勧めします。
タスクの重複実行の防止
デフォルトでは、タスクの前のインスタンスがまだ実行中の場合でも、スケジュールされたタスクは実行されます。これを防ぐには、withoutOverlappingメソッドを使用します。
1use Illuminate\Support\Facades\Schedule;2 3Schedule::command('emails:send')->withoutOverlapping();
この例では、emails:sendArtisanコマンドは、まだ実行中でなければ毎分実行されます。withoutOverlappingメソッドは、実行時間が大幅に異なるタスクがあり、特定のタスクにかかる時間を正確に予測できない場合に特に役立ちます。
必要に応じて、「重複なし」ロックが失効するまでの経過分数を指定できます。デフォルトでは、ロックは24時間後に失効します。
1Schedule::command('emails:send')->withoutOverlapping(10);
内部でwithoutOverlappingメソッドは、アプリケーションのキャッシュを利用してロックを取得します。必要であれば、schedule:clear-cache Artisanコマンドを使用してこれらのキャッシュロックをクリアできます。これは通常、予期しないサーバの問題によりタスクがスタックした場合にのみ必要です。
単一サーバでのタスク実行
この機能を利用するには、アプリケーションのデフォルトのキャッシュドライバとしてdatabase、memcached、dynamodb、またはredisキャッシュドライバを使用している必要があります。さらに、すべてのサーバが同じ中央キャッシュサーバと通信している必要があります。
アプリケーションのスケジューラが複数のサーバで実行されている場合、スケジュールされたジョブを単一のサーバでのみ実行するように制限できます。たとえば、毎週金曜日の夜に新しいレポートを生成するスケジュールされたタスクがあるとします。タスクスケジューラが3台のワーカサーバで実行されている場合、スケジュールされたタスクは3台すべてのサーバで実行され、レポートを3回生成します。これではよくありません!
タスクを1つのサーバでのみ実行することを示すには、スケジュールされたタスクを定義するときにonOneServerメソッドを使用します。タスクを最初に取得したサーバは、ジョブのアトミックロックを確保して、他のサーバが同じタスクを同時に実行するのを防ぎます。
1use Illuminate\Support\Facades\Schedule;2 3Schedule::command('report:generate')4 ->fridays()5 ->at('17:00')6 ->onOneServer();
単一サーバージョブの命名
同じジョブを異なるパラメータでディスパッチするようにスケジュールする必要がある場合がありますが、Laravelには各ジョブの順列を単一のサーバで実行するように指示する必要があります。これを実現するには、nameメソッドを使用して、各スケジュール定義に一意の名前を割り当てます。
1Schedule::job(new CheckUptime('https://laravel.dokyumento.jp'))2 ->name('check_uptime:laravel.com')3 ->everyFiveMinutes()4 ->onOneServer();5 6Schedule::job(new CheckUptime('https://vapor.laravel.com'))7 ->name('check_uptime:vapor.laravel.com')8 ->everyFiveMinutes()9 ->onOneServer();
同様に、スケジュールされたクロージャは、1つのサーバで実行することを目的としている場合は、名前を割り当てる必要があります。
1Schedule::call(fn () => User::resetApiRequestCount())2 ->name('reset-api-request-count')3 ->daily()4 ->onOneServer();
バックグラウンドタスク
デフォルトでは、同じ時刻にスケジュールされた複数のタスクは、scheduleメソッドで定義された順序に基づいて順次実行されます。長時間実行されるタスクがある場合、これにより後続のタスクが予想よりもはるかに遅れて開始される可能性があります。タスクをバックグラウンドで実行してすべて同時に実行できるようにする場合は、runInBackgroundメソッドを使用します。
1use Illuminate\Support\Facades\Schedule;2 3Schedule::command('analytics:report')4 ->daily()5 ->runInBackground();
runInBackgroundメソッドは、commandおよびexecメソッドを使用してタスクをスケジュールする場合にのみ使用できます。
メンテナンスモード
アプリケーションがメンテナンスモードの場合、アプリケーションのスケジュール済みタスクは実行されません。これは、サーバで実行している未完了のメンテナンスをタスクが妨げないようにするためです。ただし、メンテナンスモードでもタスクを強制的に実行したい場合は、タスクを定義するときにevenInMaintenanceModeメソッドを呼び出せます。
1Schedule::command('emails:send')->evenInMaintenanceMode();
スケジュールグループ
同様の設定を持つ複数のスケジュール済みタスクを定義する場合、Laravelのタスクグループ化機能を使用して、各タスクに同じ設定を繰り返すことを回避できます。タスクをグループ化すると、コードが簡素化され、関連するタスク間で一貫性が確保されます。
スケジュールされたタスクのグループを作成するには、目的のタスク設定メソッドを呼び出した後、groupメソッドを呼び出します。groupメソッドは、指定された設定を共有するタスクを定義する責任を負うクロージャを引数に取ります。
1use Illuminate\Support\Facades\Schedule;2 3Schedule::daily()4 ->onOneServer()5 ->timezone('America/New_York')6 ->group(function () {7 Schedule::command('emails:send --force');8 Schedule::command('emails:prune');9 });
スケジューラ実行
スケジュールされたタスクを定義する方法を学習したので、次にサーバで実際にタスクを実行する方法について説明します。schedule:run Artisanコマンドは、スケジュールされたすべてのタスクを評価し、サーバの現在時刻に基づいて実行する必要があるかどうかを判断します。
したがって、Laravelのスケジューラを使用する場合、schedule:runコマンドを毎分実行する単一のcron設定エントリをサーバに追加するだけで済みます。サーバにcronエントリを追加する方法がわからない場合は、Laravel Cloudなどのマネージドプラットフォームの使用を検討してください。これにより、スケジュールされたタスクの実行を管理できます。
1* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
1分未満のスケジュールタスク
ほとんどのオペレーティングシステムでは、cronジョブの実行は最大で1分に1回に制限されています。しかし、Laravelのスケジューラを使用すると、タスクをより頻繁な間隔で、1秒に1回という頻度で実行するようにスケジュールできます。
1use Illuminate\Support\Facades\Schedule;2 3Schedule::call(function () {4 DB::table('recent_users')->delete();5})->everySecond();
アプリケーション内で1分未満のタスクが定義されている場合、schedule:runコマンドはすぐには終了せず、現在の分の終わりまで実行を継続します。これにより、コマンドは1分間に必要なすべての1分未満のタスクを呼び出すことができます。
予想よりも実行時間が長くなる1分未満のタスクは、後の1分未満のタスクの実行を遅らせる可能性があるため、すべての1分未満のタスクは、実際のタスク処理を処理するためにキュー投入されたジョブまたはバックグラウンドコマンドをディスパッチすることをお勧めします。
1use App\Jobs\DeleteRecentUsers;2 3Schedule::job(new DeleteRecentUsers)->everyTenSeconds();4 5Schedule::command('users:delete')->everyTenSeconds()->runInBackground();
1分未満のタスクの中断
1分未満のタスクが定義されている場合、schedule:runコマンドは呼び出された分全体にわたって実行されるため、アプリケーションをデプロイするときにコマンドを中断する必要がある場合があります。そうしないと、すでに実行中のschedule:runコマンドのインスタンスは、現在の分が終了するまで、アプリケーションの以前にデプロイされたコードを使用し続けることになります。
進行中のschedule:runの呼び出しを中断するには、アプリケーションのデプロイスクリプトにschedule:interruptコマンドを追加します。このコマンドは、アプリケーションのデプロイが完了した後に呼び出す必要があります。
1php artisan schedule:interrupt
ローカルでのスケジューラ実行
通常、ローカルの開発マシンにスケジューラのcronエントリを追加することはありません。代わりに、schedule:work Artisanコマンドを使用できます。このコマンドはフォアグラウンドで実行され、コマンドを終了するまで毎分スケジューラを呼び出します。1分未満のタスクが定義されている場合、スケジューラは各分内で実行を継続してそれらのタスクを処理します。
1php artisan schedule:work
タスク出力
Laravelスケジューラは、スケジュールされたタスクによって生成された出力を操作するためのいくつかの便利なメソッドを提供します。まず、sendOutputToメソッドを使用して、後で検査するために出力をファイルに送信できます。
1use Illuminate\Support\Facades\Schedule;2 3Schedule::command('emails:send')4 ->daily()5 ->sendOutputTo($filePath);
出力を特定のファイルに追加したい場合は、appendOutputToメソッドを使用します。
1Schedule::command('emails:send')2 ->daily()3 ->appendOutputTo($filePath);
emailOutputToメソッドを使用すると、選択したメールアドレスに出力をメールで送信できます。タスクの出力をメールで送信する前に、Laravelのメールサービスを設定する必要があります。
1Schedule::command('report:generate')2 ->daily()3 ->sendOutputTo($filePath)
スケジュールされたArtisanまたはシステムコマンドがゼロ以外の終了コードで終了した場合にのみ出力を電子メールで送信したい場合は、emailOutputOnFailureメソッドを使用します。
1Schedule::command('report:generate')2 ->daily()
emailOutputTo、emailOutputOnFailure、sendOutputTo、appendOutputToメソッドは、commandおよびexecメソッド専用です。
タスクフック
beforeメソッドとafterメソッドを使用すると、スケジュールされたタスクの実行前後に実行するコードを指定できます。
1use Illuminate\Support\Facades\Schedule; 2 3Schedule::command('emails:send') 4 ->daily() 5 ->before(function () { 6 // The task is about to execute... 7 }) 8 ->after(function () { 9 // The task has executed...10 });
onSuccessメソッドとonFailureメソッドを使用すると、スケジュールされたタスクが成功または失敗した場合に実行されるコードを指定できます。失敗は、スケジュールされたArtisanまたはシステムコマンドがゼロ以外の終了コードで終了したことを示します。
1Schedule::command('emails:send')2 ->daily()3 ->onSuccess(function () {4 // The task succeeded...5 })6 ->onFailure(function () {7 // The task failed...8 });
コマンドから出力が利用可能な場合は、after、onSuccess、またはonFailureフックで、フックのクロージャ定義の$output引数としてIlluminate\Support\Stringableインスタンスをタイプヒントすることでアクセスできます。
1use Illuminate\Support\Stringable; 2 3Schedule::command('emails:send') 4 ->daily() 5 ->onSuccess(function (Stringable $output) { 6 // The task succeeded... 7 }) 8 ->onFailure(function (Stringable $output) { 9 // The task failed...10 });
URLへのピング
pingBeforeメソッドとthenPingメソッドを使用すると、スケジューラはタスクの実行前または実行後に特定のURLに自動的にpingを送信できます。このメソッドは、Envoyerなどの外部サービスに、スケジュールされたタスクが開始または終了したことを通知するのに役立ちます。
1Schedule::command('emails:send')2 ->daily()3 ->pingBefore($url)4 ->thenPing($url);
pingOnSuccessメソッドとpingOnFailureメソッドは、タスクが成功または失敗した場合にのみ、特定のURLにpingを送信するために使用します。失敗は、スケジュールされたArtisanまたはシステムコマンドがゼロ以外の終了コードで終了したことを示します。
1Schedule::command('emails:send')2 ->daily()3 ->pingOnSuccess($successUrl)4 ->pingOnFailure($failureUrl);
pingBeforeIf、thenPingIf、pingOnSuccessIf、およびpingOnFailureIfメソッドは、特定の条件がtrueである場合にのみ、特定のURLにpingを送信するために使用します。
1Schedule::command('emails:send')2 ->daily()3 ->pingBeforeIf($condition, $url)4 ->thenPingIf($condition, $url);5 6Schedule::command('emails:send')7 ->daily()8 ->pingOnSuccessIf($condition, $successUrl)9 ->pingOnFailureIf($condition, $failureUrl);
イベント
Laravelは、スケジューリングプロセス中にさまざまなイベントをディスパッチします。以下のいずれかのイベントのリスナを定義できます。
| イベント名 |
|---|
Illuminate\Console\Events\ScheduledTaskStarting |
Illuminate\Console\Events\ScheduledTaskFinished |
Illuminate\Console\Events\ScheduledBackgroundTaskFinished |
Illuminate\Console\Events\ScheduledTaskSkipped |
Illuminate\Console\Events\ScheduledTaskFailed |