はじめに

過去には、サーバー上でスケジュールする必要のある各タスクのために cron 設定エントリを作成していたかもしれません。しかし、これではすぐに面倒になり、タスクスケジュールがソース管理に含まれなくなり、既存の cron エントリを表示したり追加したりするためにサーバーに SSH で接続する必要があります。

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

スケジュールの定義

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

  1. <?php
  2. use Illuminate\Support\Facades\DB;
  3. use Illuminate\Support\Facades\Schedule;
  4. Schedule::call(function () {
  5. DB::table('recent_users')->delete();
  6. })->daily();

クロージャを使用してスケジュールするだけでなく、呼び出し可能オブジェクトをスケジュールすることもできます。呼び出し可能オブジェクトは、invoke メソッドを含むシンプルな PHP クラスです:

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

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

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

スケジュールされたタスクの概要と次回の実行時刻を表示したい場合は、schedule:list Artisan コマンドを使用できます:

  1. php artisan schedule:list

Artisan コマンドのスケジューリング

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

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

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

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

クロージャによって定義された Artisan コマンドをスケジュールしたい場合は、コマンドの定義の後にスケジューリング関連のメソッドをチェーンできます:

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

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

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

キューイングされたジョブのスケジューリング

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

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

job メソッドには、ジョブをキューに入れるために使用するキュー名とキュー接続を指定するためのオプションの第2引数と第3引数を提供できます:

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

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

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

  1. use Illuminate\Support\Facades\Schedule;
  2. Schedule::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(); タスクを毎日真夜中に実行します。
->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(); タスクを毎月の最初の日の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'); タスクのタイムゾーンを設定します。

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


php<br>use Illuminate\Support\Facades\Schedule;<br><br>// Run once per week on Monday at 1 PM...<br>Schedule::call(function () {<br> // ...<br>})->weekly()->mondays()->at('13:00');<br><br>// Run hourly from 8 AM to 5 PM on weekdays...<br>Schedule::command('foo')<br> ->weekdays()<br> ->hourly()<br> ->timezone('America/Chicago')<br> ->between('8:00', '17:00');<br>

追加のスケジュール制約のリストは以下にあります:


| メソッド | 説明 |
| —- | —- |
| ->weekdays(); | タスクを平日に制限します。 |
| ->weekends(); | タスクを週末に制限します。 |
| ->sundays(); | タスクを日曜日に制限します。 |
| ->mondays(); | タスクを月曜日に制限します。 |
| ->tuesdays(); | タスクを火曜日に制限します。 |
| ->wednesdays(); | タスクを水曜日に制限します。 |
| ->thursdays(); | タスクを木曜日に制限します。 |
| ->fridays(); | タスクを金曜日に制限します。 |
| ->saturdays(); | タスクを土曜日に制限します。 |
| ->days(array|mixed); | 特定の日にタスクを制限します。 |
| ->between($startTime, $endTime); | 開始時刻と終了時刻の間にタスクを実行するように制限します。 |
| ->unlessBetween($startTime, $endTime); | 開始時刻と終了時刻の間にタスクを実行しないように制限します。 |
| ->when(Closure); | 真偽テストに基づいてタスクを制限します。 |
| ->environments($env); | 特定の環境にタスクを制限します。 |

曜日制約

days メソッドを使用して、タスクの実行を特定の曜日に制限できます。たとえば、日曜日と水曜日に毎時コマンドを実行するようにスケジュールできます:

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

また、タスクが実行されるべき曜日を定義する際に、Illuminate\Console\Scheduling\Schedule クラスで利用可能な定数を使用することもできます:

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

時間制約の間

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

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

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

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

真偽テスト制約

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

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

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

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

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

環境制約

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

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

タイムゾーン

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

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

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

  1. 'timezone' => env('APP_TIMEZONE', 'UTC'),
  2. 'schedule_timezone' => 'America/Chicago',

一部のタイムゾーンは夏時間を利用することを忘れないでください。夏時間の変更が発生すると、スケジュールされたタスクが2回実行されたり、まったく実行されなかったりする可能性があります。このため、可能な限りタイムゾーンスケジューリングを避けることをお勧めします。

タスクの重複を防ぐ

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

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

この例では、emails:send Artisan コマンドは、すでに実行中でない場合、毎分実行されます。withoutOverlapping メソッドは、実行時間が大きく異なるタスクがある場合に特に便利で、特定のタスクがどれくらいの時間がかかるかを正確に予測することを妨げます。

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

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

裏では、withoutOverlapping メソッドは、アプリケーションの キャッシュを利用してロックを取得します。必要に応じて、schedule:clear-cache Artisan コマンドを使用してこれらのキャッシュロックをクリアできます。これは、タスクが予期しないサーバーの問題でスタックした場合にのみ通常必要です。

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

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

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

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

  1. use Illuminate\Support\Facades\Schedule;
  2. Schedule::command('report:generate')
  3. ->fridays()
  4. ->at('17:00')
  5. ->onOneServer();

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

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

  1. Schedule::job(new CheckUptime('https://laravel.com'))
  2. ->name('check_uptime:laravel.com')
  3. ->everyFiveMinutes()
  4. ->onOneServer();
  5. Schedule::job(new CheckUptime('https://vapor.laravel.com'))
  6. ->name('check_uptime:vapor.laravel.com')
  7. ->everyFiveMinutes()
  8. ->onOneServer();

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

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

バックグラウンドタスク

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

  1. use Illuminate\Support\Facades\Schedule;
  2. Schedule::command('analytics:report')
  3. ->daily()
  4. ->runInBackground();

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

メンテナンスモード

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

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

スケジューラの実行

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

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

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

サブ分スケジュールされたタスク

ほとんどのオペレーティングシステムでは、cron ジョブは最大で1分ごとに実行される制限があります。しかし、Laravel のスケジューラを使用すると、タスクをより頻繁に実行するようにスケジュールできます。たとえば、1秒ごとに実行することも可能です:

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

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

サブ分タスクが予想以上に長く実行されると、後続のサブ分タスクの実行が遅れる可能性があるため、すべてのサブ分タスクは、実際のタスク処理を処理するためにキューイングされたジョブまたはバックグラウンドコマンドをディスパッチすることをお勧めします:

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

サブ分タスクの中断

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

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

  1. php artisan schedule:interrupt

ローカルでのスケジューラの実行

通常、ローカル開発マシンにスケジューラの cron エントリを追加することはありません。代わりに、schedule:work Artisan コマンドを使用できます。このコマンドはフォアグラウンドで実行され、コマンドを終了するまで毎分スケジューラを呼び出します:

  1. php artisan schedule:work

タスク出力

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

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

出力を指定されたファイルに追加したい場合は、appendOutputTo メソッドを使用できます:

  1. Schedule::command('emails:send')
  2. ->daily()
  3. ->appendOutputTo($filePath);

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

  1. Schedule::command('report:generate')
  2. ->daily()
  3. ->sendOutputTo($filePath)
  4. ->emailOutputTo('');

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

  1. Schedule::command('report:generate')
  2. ->daily()
  3. ->emailOutputOnFailure('');

emailOutputToemailOutputOnFailuresendOutputTo、および appendOutputTo メソッドは、command および exec メソッドにのみ適用されます。

タスクフック

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

  1. use Illuminate\Support\Facades\Schedule;
  2. Schedule::command('emails:send')
  3. ->daily()
  4. ->before(function () {
  5. // The task is about to execute...
  6. })
  7. ->after(function () {
  8. // The task has executed...
  9. });

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

  1. Schedule::command('emails:send')
  2. ->daily()
  3. ->onSuccess(function () {
  4. // The task succeeded...
  5. })
  6. ->onFailure(function () {
  7. // The task failed...
  8. });

コマンドから出力が利用可能な場合、afteronSuccess、または onFailure フックで、フックのクロージャ定義の $output 引数として Illuminate\Support\Stringable インスタンスを型ヒントすることでアクセスできます:

  1. use Illuminate\Support\Stringable;
  2. Schedule::command('emails:send')
  3. ->daily()
  4. ->onSuccess(function (Stringable $output) {
  5. // The task succeeded...
  6. })
  7. ->onFailure(function (Stringable $output) {
  8. // The task failed...
  9. });

URL のピング

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

  1. Schedule::command('emails:send')
  2. ->daily()
  3. ->pingBefore($url)
  4. ->thenPing($url);

pingBeforeIf および thenPingIf メソッドは、指定された条件が true の場合にのみ、指定された URL をピングするために使用できます:

  1. Schedule::command('emails:send')
  2. ->daily()
  3. ->pingBeforeIf($condition, $url)
  4. ->thenPingIf($condition, $url);

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

  1. Schedule::command('emails:send')
  2. ->daily()
  3. ->pingOnSuccess($successUrl)
  4. ->pingOnFailure($failureUrl);

イベント

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

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