コンテンツへスキップ
Laravel Nightwatch のウェイティングリストにご登録ください。

タスクスケジューリング

導入

以前は、サーバーでスケジュールする必要がある各タスクに対してcron設定エントリを作成していたかもしれません。しかし、これにより、タスクスケジュールがソース管理されなくなり、既存のcronエントリを表示したり、追加のエントリを追加するためにサーバーにSSHで接続する必要があるため、すぐに煩雑になる可能性があります。

Laravelのコマンドスケジューラは、サーバーでスケジュールされたタスクを管理するための新しいアプローチを提供します。スケジューラを使用すると、Laravelアプリケーション自体内でコマンドスケジュールを流暢かつ簡潔に定義できます。スケジューラを使用する場合、サーバーにはcronエントリが1つだけ必要です。タスクスケジュールは通常、アプリケーションの`routes/console.php`ファイルに定義されます。

スケジュールの定義

すべてのスケジュール済みタスクは、アプリケーションの`routes/console.php`ファイルに定義できます。始めましょう、例を見てみましょう。この例では、毎日深夜に呼び出されるクロージャをスケジュールします。クロージャ内では、テーブルをクリアするためのデータベースクエリを実行します。

<?php
 
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schedule;
 
Schedule::call(function () {
DB::table('recent_users')->delete();
})->daily();

クロージャを使用したスケジューリングに加えて、呼び出し可能なオブジェクトをスケジュールすることもできます。呼び出し可能なオブジェクトは、`__invoke`メソッドを含む単純なPHPクラスです。

Schedule::call(new DeleteRecentUsers)->daily();

コマンドの定義のみに`routes/console.php`ファイルを残しておきたい場合は、アプリケーションの`bootstrap/app.php`ファイルで`withSchedule`メソッドを使用して、スケジュール済みタスクを定義できます。このメソッドは、スケジューラのインスタンスを受け取るクロージャを受け入れます。

use Illuminate\Console\Scheduling\Schedule;
 
->withSchedule(function (Schedule $schedule) {
$schedule->call(new DeleteRecentUsers)->daily();
})

スケジュールされたタスクの概要と、次に実行される時刻を確認するには、`schedule:list` Artisanコマンドを使用できます。

php artisan schedule:list

Artisanコマンドのスケジュール

クロージャのスケジュールに加えて、Artisanコマンドとシステムコマンドをスケジュールすることもできます。たとえば、`command`メソッドを使用して、コマンドの名前またはクラスを使用してArtisanコマンドをスケジュールできます。

コマンドのクラス名を使用してArtisanコマンドをスケジュールする場合、コマンドが呼び出されたときにコマンドに提供される追加のコマンドライン引数の配列を渡すことができます。

use App\Console\Commands\SendEmailsCommand;
use Illuminate\Support\Facades\Schedule;
 
Schedule::command('emails:send Taylor --force')->daily();
 
Schedule::command(SendEmailsCommand::class, ['Taylor', '--force'])->daily();

Artisanクロージャコマンドのスケジュール

クロージャで定義されたArtisanコマンドをスケジュールする場合は、コマンドの定義後にスケジュール関連のメソッドをチェーンできます。

Artisan::command('delete:recent-users', function () {
DB::table('recent_users')->delete();
})->purpose('Delete recent users')->daily();

クロージャコマンドに引数を渡す必要がある場合は、`schedule`メソッドに渡すことができます。

Artisan::command('emails:send {user} {--force}', function ($user) {
// ...
})->purpose('Send emails to the specified user')->schedule(['Taylor', '--force'])->daily();

キューされたジョブのスケジュール

`job`メソッドを使用してキューされたジョブをスケジュールできます。このメソッドは、ジョブをキューに入れるクロージャを定義するために`call`メソッドを使用せずに、キューされたジョブを簡単にスケジュールする方法を提供します。

use App\Jobs\Heartbeat;
use Illuminate\Support\Facades\Schedule;
 
Schedule::job(new Heartbeat)->everyFiveMinutes();

`job`メソッドには、オプションで2番目と3番目の引数を指定できます。これにより、ジョブをキューに入れるために使用するキュー名とキュー接続を指定できます。

use App\Jobs\Heartbeat;
use Illuminate\Support\Facades\Schedule;
 
// Dispatch the job to the "heartbeats" queue on the "sqs" connection...
Schedule::job(new Heartbeat, 'heartbeats', 'sqs')->everyFiveMinutes();

シェルコマンドのスケジュール

`exec`メソッドを使用して、オペレーティングシステムにコマンドを発行できます。

use Illuminate\Support\Facades\Schedule;
 
Schedule::exec('node /home/forge/script.js')->daily();

スケジュールの頻度のオプション

既に、特定の間隔で実行するようにタスクを設定する方法のいくつかの例を見てきました。しかし、タスクに割り当てることができるタスクスケジュールの頻度は他にもたくさんあります。

メソッド 説明
->cron('* * * * *'); カスタムcronスケジュールでタスクを実行します。
->everySecond(); タスクを1秒ごとに実行します。
->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(); タスクを毎日深夜に実行します。
->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'); タスクのタイムゾーンを設定します。

これらのメソッドは、追加の制約と組み合わせることで、さらに細かく調整されたスケジュールを作成できます。例えば、毎週月曜日に実行されるコマンドをスケジュールできます。

use Illuminate\Support\Facades\Schedule;
 
// Run once per week on Monday at 1 PM...
Schedule::call(function () {
// ...
})->weekly()->mondays()->at('13:00');
 
// Run hourly from 8 AM to 5 PM on weekdays...
Schedule::command('foo')
->weekdays()
->hourly()
->timezone('America/Chicago')
->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メソッドを使用して、タスクの実行を特定の曜日に制限できます。たとえば、日曜日と水曜日に毎時実行されるコマンドをスケジュールできます。

use Illuminate\Support\Facades\Schedule;
 
Schedule::command('emails:send')
->hourly()
->days([0, 3]);

あるいは、タスクを実行する曜日を定義する際に、Illuminate\Console\Scheduling\Scheduleクラスで使用可能な定数を使用することもできます。

use Illuminate\Support\Facades;
use Illuminate\Console\Scheduling\Schedule;
 
Facades\Schedule::command('emails:send')
->hourly()
->days([Schedule::SUNDAY, Schedule::WEDNESDAY]);

時間範囲制約

betweenメソッドを使用して、タスクの実行を1日の時間に基づいて制限できます。

Schedule::command('emails:send')
->hourly()
->between('7:00', '22:00');

同様に、unlessBetweenメソッドを使用して、一定期間タスクの実行を除外できます。

Schedule::command('emails:send')
->hourly()
->unlessBetween('23:00', '4:00');

真偽テスト制約

whenメソッドを使用して、指定された真偽テストの結果に基づいてタスクの実行を制限できます。言い換えれば、指定されたクロージャがtrueを返す場合、他の制約条件によってタスクの実行が妨げられない限り、タスクは実行されます。

Schedule::command('emails:send')->daily()->when(function () {
return true;
});

skipメソッドはwhenの逆と考えることができます。skipメソッドがtrueを返す場合、スケジュールされたタスクは実行されません。

Schedule::command('emails:send')->daily()->skip(function () {
return true;
});

チェーンされたwhenメソッドを使用する場合、スケジュールされたコマンドは、すべてのwhen条件がtrueを返す場合にのみ実行されます。

環境制約

environmentsメソッドを使用して、指定された環境(APP_ENV[環境変数]で定義されている)でのみタスクを実行できます。

Schedule::command('emails:send')
->daily()
->environments(['staging', 'production']);

タイムゾーン

timezoneメソッドを使用すると、スケジュールされたタスクの時間が特定のタイムゾーン内で解釈されるように指定できます。

use Illuminate\Support\Facades\Schedule;
 
Schedule::command('report:generate')
->timezone('America/New_York')
->at('2:00')

すべてのスケジュールされたタスクに同じタイムゾーンを繰り返し割り当てている場合は、アプリケーションのapp設定ファイル内にschedule_timezoneオプションを定義することで、すべてのスケジュールに割り当てるタイムゾーンを指定できます。

'timezone' => env('APP_TIMEZONE', 'UTC'),
 
'schedule_timezone' => 'America/Chicago',
exclamation

一部のタイムゾーンではサマータイムが使用されていることに注意してください。サマータイムの変更が発生すると、スケジュールされたタスクが2回実行されたり、まったく実行されなかったりする可能性があります。このため、可能であればタイムゾーンのスケジュールは避けることをお勧めします。

タスクの重複の防止

デフォルトでは、スケジュールされたタスクは、タスクの以前のインスタンスがまだ実行中の場合でも実行されます。これを防ぐには、withoutOverlappingメソッドを使用できます。

use Illuminate\Support\Facades\Schedule;
 
Schedule::command('emails:send')->withoutOverlapping();

この例では、emails:send[Artisanコマンド]は、まだ実行されていない場合、毎分実行されます。withoutOverlappingメソッドは、実行時間が大きく異なるタスクがある場合に特に役立ち、特定のタスクにかかる時間を正確に予測できなくなります。

必要に応じて、「重複しない」ロックが期限切れになるまでの分数を指定できます。デフォルトでは、ロックは24時間後に期限切れになります。

Schedule::command('emails:send')->withoutOverlapping(10);

内部的には、withoutOverlappingメソッドはアプリケーションの[キャッシュ]を使用してロックを取得します。必要に応じて、schedule:clear-cacheArtisanコマンドを使用してこれらのキャッシュロックをクリアできます。これは通常、予期せぬサーバーの問題によりタスクが停止した場合にのみ必要です。

1台のサーバーでのタスクの実行

exclamation

この機能を使用するには、アプリケーションでdatabasememcacheddynamodb、またはredisキャッシュドライバをアプリケーションのデフォルトキャッシュドライバとして使用する必要があります。さらに、すべてのサーバーは同じ中央キャッシュサーバーと通信する必要があります。

アプリケーションのスケジューラが複数のサーバーで実行されている場合、スケジュールされたジョブを単一のサーバーでのみ実行するように制限できます。たとえば、毎週金曜日の夜に新しいレポートを生成するスケジュールされたタスクがあるとします。タスクスケジューラが3つのワーカサーバーで実行されている場合、スケジュールされたタスクは3つのサーバーすべてで実行され、レポートが3回生成されます。これは良くありません!

タスクを1つのサーバーでのみ実行することを示すには、スケジュールされたタスクを定義する際にonOneServerメソッドを使用します。タスクを取得した最初のサーバーは、他のサーバーが同時に同じタスクを実行するのを防ぐために、ジョブに対してアトミックロックを確保します。

use Illuminate\Support\Facades\Schedule;
 
Schedule::command('report:generate')
->fridays()
->at('17:00')
->onOneServer();

単一サーバージョブの命名

場合によっては、同じジョブを異なるパラメータでディスパッチしながら、Laravelにジョブの各順列を単一サーバーで実行するように指示する必要がある場合があります。これを実現するには、nameメソッドを使用して各スケジュール定義に一意の名前を割り当てることができます。

Schedule::job(new CheckUptime('https://laravel.dokyumento.jp'))
->name('check_uptime:laravel.com')
->everyFiveMinutes()
->onOneServer();
 
Schedule::job(new CheckUptime('https://vapor.laravel.com'))
->name('check_uptime:vapor.laravel.com')
->everyFiveMinutes()
->onOneServer();

同様に、スケジュールされたクロージャは、1つのサーバーで実行されることを意図している場合は、名前を付ける必要があります。

Schedule::call(fn () => User::resetApiRequestCount())
->name('reset-api-request-count')
->daily()
->onOneServer();

バックグラウンドタスク

デフォルトでは、同時にスケジュールされた複数のタスクは、scheduleメソッドで定義されている順序に基づいて順番に実行されます。長時間のタスクがある場合、後続のタスクは予想よりもはるかに遅れて開始される可能性があります。タスクをバックグラウンドで実行して、すべて同時に実行できるようにするには、runInBackgroundメソッドを使用できます。

use Illuminate\Support\Facades\Schedule;
 
Schedule::command('analytics:report')
->daily()
->runInBackground();
exclamation

runInBackgroundメソッドは、commandメソッドとexecメソッドを使用してタスクをスケジュールする場合にのみ使用できます。

メンテナンスモード

アプリケーションが[メンテナンスモード]の場合、アプリケーションのスケジュールされたタスクは実行されません。サーバーで未完了のメンテナンス作業を妨げないようにするためです。ただし、メンテナンスモードでもタスクを強制的に実行する場合は、タスクを定義する際にevenInMaintenanceModeメソッドを呼び出すことができます。

Schedule::command('emails:send')->evenInMaintenanceMode();

スケジュールグループ

同様の設定を持つ複数のスケジュールされたタスクを定義する場合は、Laravelのタスクグループ化機能を使用して、各タスクに対して同じ設定を繰り返さないようにできます。タスクのグループ化により、コードが簡素化され、関連タスク間の一貫性が確保されます。

スケジュールされたタスクのグループを作成するには、目的のタスク設定メソッドを呼び出し、その後にgroupメソッドを呼び出します。groupメソッドは、指定された設定を共有するタスクを定義する責任を負うクロージャを受け入れます。

use Illuminate\Support\Facades\Schedule;
 
Schedule::daily()
->onOneServer()
->timezone('America/New_York')
->group(function () {
Schedule::command('emails:send --force');
Schedule::command('emails:prune');
});

スケジューラの起動

スケジュールされたタスクの定義方法を学習したので、実際にサーバーでそれらを実行する方法について説明します。schedule:runArtisanコマンドは、すべてのスケジュールされたタスクを評価し、サーバーの現在時刻に基づいて実行する必要があるかどうかを判断します。

そのため、Laravelのスケジューラを使用する場合、サーバーにschedule:runコマンドを毎分実行する単一のcron設定エントリを追加するだけで済みます。サーバーにcronエントリを追加する方法がわからない場合は、[Laravel Forge]などのサービスを使用することを検討してください。これにより、cronエントリを管理できます。

* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1

1分未満のスケジュールタスク

ほとんどのオペレーティングシステムでは、cronジョブは1分間に最大1回しか実行できません。ただし、Laravelのスケジューラでは、1秒間に1回という頻度でタスクをスケジュールできます。

use Illuminate\Support\Facades\Schedule;
 
Schedule::call(function () {
DB::table('recent_users')->delete();
})->everySecond();

アプリケーション内に1分未満のタスクが定義されている場合、schedule:runコマンドは、すぐに終了するのではなく、現在の分が終了するまで実行を続けます。これにより、コマンドは分全体を通して必要な1分未満のタスクをすべて呼び出すことができます。

予想よりも長く実行される1分未満のタスクは、後の1分未満のタスクの実行を遅らせる可能性があるため、1分未満のタスクはすべて、キューされたジョブまたはバックグラウンドコマンドをディスパッチして、実際タスク処理を行うことをお勧めします。

use App\Jobs\DeleteRecentUsers;
 
Schedule::job(new DeleteRecentUsers)->everyTenSeconds();
 
Schedule::command('users:delete')->everyTenSeconds()->runInBackground();

1分未満のタスクの中断

1分未満のタスクが定義されている場合、schedule:runコマンドは呼び出しの1分間全体実行されるため、アプリケーションをデプロイする際にコマンドを中断する必要がある場合があります。そうでない場合、既に実行されているschedule:runコマンドのインスタンスは、現在の分が終了するまで、アプリケーションの以前にデプロイされたコードを引き続き使用します。

進行中のschedule:run呼び出しを中断するには、アプリケーションのデプロイメントスクリプトにschedule:interruptコマンドを追加できます。このコマンドは、アプリケーションのデプロイが完了した後に呼び出す必要があります。

php artisan schedule:interrupt

ローカルでのスケジューラの起動

通常、ローカル開発マシンにスケジューラのcronエントリを追加することはありません。代わりに、schedule:workArtisanコマンドを使用できます。このコマンドはフォアグラウンドで実行され、コマンドを終了するまで毎分スケジューラを呼び出します。1分未満のタスクが定義されている場合、スケジューラは各分内で実行を継続してこれらのタスクを処理します。

php artisan schedule:work

タスク出力

Laravelスケジューラは、スケジュールされたタスクによって生成された出力を使用するためのいくつかの便利なメソッドを提供します。まず、sendOutputToメソッドを使用して、出力をファイルに送信して後で検査できます。

use Illuminate\Support\Facades\Schedule;
 
Schedule::command('emails:send')
->daily()
->sendOutputTo($filePath);

出力を特定のファイルに追加する場合は、appendOutputToメソッドを使用できます。

Schedule::command('emails:send')
->daily()
->appendOutputTo($filePath);

emailOutputToメソッドを使用すると、選択したメールアドレスに出力をメールで送信できます。タスクの出力をメールで送信する前に、Laravelの[メールサービス]を設定する必要があります。

Schedule::command('report:generate')
->daily()
->sendOutputTo($filePath)
->emailOutputTo('[email protected]');

スケジュールされたArtisanコマンドまたはシステムコマンドがゼロ以外の終了コードで終了した場合にのみ出力をメールで送信する場合は、emailOutputOnFailureメソッドを使用します。

Schedule::command('report:generate')
->daily()
->emailOutputOnFailure('[email protected]');
exclamation

emailOutputToemailOutputOnFailuresendOutputTo、およびappendOutputToメソッドは、commandメソッドとexecメソッドに限定されます。

タスクフック

beforeメソッドとafterメソッドを使用して、スケジュールされたタスクの実行前と実行後に実行するコードを指定できます。

use Illuminate\Support\Facades\Schedule;
 
Schedule::command('emails:send')
->daily()
->before(function () {
// The task is about to execute...
})
->after(function () {
// The task has executed...
});

onSuccessメソッドとonFailureメソッドを使用すると、スケジュールされたタスクが成功した場合と失敗した場合に実行するコードを指定できます。失敗とは、スケジュールされたArtisanコマンドまたはシステムコマンドがゼロ以外の終了コードで終了したことを示します。

Schedule::command('emails:send')
->daily()
->onSuccess(function () {
// The task succeeded...
})
->onFailure(function () {
// The task failed...
});

コマンドから出力結果が得られる場合、`after`、`onSuccess`、または`onFailure`フックで、`$output`引数を`Illuminate\Support\Stringable`インスタンスとして型ヒントすることでアクセスできます。

use Illuminate\Support\Stringable;
 
Schedule::command('emails:send')
->daily()
->onSuccess(function (Stringable $output) {
// The task succeeded...
})
->onFailure(function (Stringable $output) {
// The task failed...
});

URLのPing

`pingBefore`メソッドと`thenPing`メソッドを使用すると、スケジューラはタスクの実行前または実行後に指定されたURLを自動的にpingできます。このメソッドは、Envoyerなどの外部サービスに、スケジュールされたタスクが開始または終了したことを通知する場合に便利です。

Schedule::command('emails:send')
->daily()
->pingBefore($url)
->thenPing($url);

`pingOnSuccess`メソッドと`pingOnFailure`メソッドは、タスクが成功した場合または失敗した場合のみに指定されたURLをpingするために使用できます。失敗とは、スケジュールされたArtisanコマンドまたはシステムコマンドがゼロ以外の終了コードで終了したことを示します。

Schedule::command('emails:send')
->daily()
->pingOnSuccess($successUrl)
->pingOnFailure($failureUrl);

`pingBeforeIf`、`thenPingIf`、`pingOnSuccessIf`、`pingOnFailureIf`メソッドは、指定された条件が`true`の場合のみに指定されたURLをpingするために使用できます。

Schedule::command('emails:send')
->daily()
->pingBeforeIf($condition, $url)
->thenPingIf($condition, $url);
 
Schedule::command('emails:send')
->daily()
->pingOnSuccessIf($condition, $successUrl)
->pingOnFailureIf($condition, $failureUrl);

イベント

Laravelは、スケジューリングプロセス中にさまざまなイベントをディスパッチします。以下のいずれかのイベントについてもリスナーを定義できます。

イベント名
Illuminate\Console\Events\ScheduledTaskStarting
Illuminate\Console\Events\ScheduledTaskFinished
Illuminate\Console\Events\ScheduledBackgroundTaskFinished
Illuminate\Console\Events\ScheduledTaskSkipped
Illuminate\Console\Events\ScheduledTaskFailed