はじめに
過去には、サーバー上でスケジュールする必要のある各タスクのために cron 設定エントリを作成していたかもしれません。しかし、これではすぐに面倒になり、タスクスケジュールがソース管理に含まれなくなり、既存の cron エントリを表示したり追加したりするためにサーバーに SSH で接続する必要があります。
Laravel のコマンドスケジューラは、サーバー上のスケジュールされたタスクを管理するための新しいアプローチを提供します。スケジューラを使用すると、Laravel アプリケーション内でコマンドスケジュールを流暢かつ表現豊かに定義できます。スケジューラを使用する場合、サーバー上には単一の cron エントリのみが必要です。タスクスケジュールは通常、アプリケーションの 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(); |
タスクを毎秒実行します。 |
->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
メソッドを使用して、タスクの実行を特定の曜日に制限できます。たとえば、日曜日と水曜日に毎時コマンドを実行するようにスケジュールできます:
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
メソッドを使用して、日中の時間に基づいてタスクの実行を制限できます:
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',
一部のタイムゾーンは夏時間を利用することを忘れないでください。夏時間の変更が発生すると、スケジュールされたタスクが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-cache
Artisan コマンドを使用してこれらのキャッシュロックをクリアできます。これは、タスクが予期しないサーバーの問題でスタックした場合にのみ通常必要です。
1台のサーバーでのタスクの実行
この機能を利用するには、アプリケーションが database
、memcached
、dynamodb
、または redis
キャッシュドライバーをデフォルトのキャッシュドライバーとして使用している必要があります。さらに、すべてのサーバーが同じ中央キャッシュサーバーと通信している必要があります。
アプリケーションのスケジューラが複数のサーバーで実行されている場合、スケジュールされたジョブを単一のサーバーでのみ実行するように制限できます。たとえば、毎週金曜日の夜に新しいレポートを生成するスケジュールされたタスクがあると仮定します。タスクスケジューラが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.com'))
->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();
runInBackground
メソッドは、command
および exec
メソッドを使用してタスクをスケジュールする場合にのみ使用できます。
メンテナンスモード
アプリケーションのスケジュールされたタスクは、アプリケーションが メンテナンスモード のときには実行されません。これは、サーバーで行っている未完了のメンテナンスにタスクが干渉しないようにするためです。しかし、メンテナンスモードでもタスクを強制的に実行したい場合は、タスクを定義する際に evenInMaintenanceMode
メソッドを呼び出すことができます:
Schedule::command('emails:send')->evenInMaintenanceMode();
スケジューラの実行
スケジュールされたタスクの定義方法を学んだので、サーバー上で実際にそれらを実行する方法について説明しましょう。schedule:run
Artisan コマンドは、すべてのスケジュールされたタスクを評価し、サーバーの現在の時刻に基づいて実行する必要があるかどうかを判断します。
したがって、Laravel のスケジューラを使用する場合、schedule:run
コマンドを毎分実行する単一の cron 設定エントリをサーバーに追加するだけで済みます。サーバーに cron エントリを追加する方法がわからない場合は、Laravel Forge のようなサービスを使用して、cron エントリを管理することを検討してください:
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
サブ分スケジュールされたタスク
ほとんどのオペレーティングシステムでは、cron ジョブは最大で1分ごとに実行される制限があります。しかし、Laravel のスケジューラを使用すると、タスクをより頻繁に実行するようにスケジュールできます。たとえば、1秒ごとに実行することも可能です:
use Illuminate\Support\Facades\Schedule;
Schedule::call(function () {
DB::table('recent_users')->delete();
})->everySecond();
アプリケーション内でサブ分タスクが定義されると、schedule:run
コマンドは、すぐに終了するのではなく、現在の分の終わりまで実行し続けます。これにより、コマンドは分の間に必要なすべてのサブ分タスクを呼び出すことができます。
サブ分タスクが予想以上に長く実行されると、後続のサブ分タスクの実行が遅れる可能性があるため、すべてのサブ分タスクは、実際のタスク処理を処理するためにキューイングされたジョブまたはバックグラウンドコマンドをディスパッチすることをお勧めします:
use App\Jobs\DeleteRecentUsers;
Schedule::job(new DeleteRecentUsers)->everyTenSeconds();
Schedule::command('users:delete')->everyTenSeconds()->runInBackground();
サブ分タスクの中断
schedule:run
コマンドは、サブ分タスクが定義されているときに呼び出しの全分間実行されるため、アプリケーションをデプロイするときにコマンドを中断する必要がある場合があります。そうしないと、すでに実行中の schedule:run
コマンドのインスタンスが、現在の分が終了するまでアプリケーションの以前にデプロイされたコードを使用し続けます。
進行中の schedule:run
呼び出しを中断するには、schedule:interrupt
コマンドをアプリケーションのデプロイスクリプトに追加できます。このコマンドは、アプリケーションのデプロイが完了した後に呼び出す必要があります:
php artisan schedule:interrupt
ローカルでのスケジューラの実行
通常、ローカル開発マシンにスケジューラの cron エントリを追加することはありません。代わりに、schedule:work
Artisan コマンドを使用できます。このコマンドはフォアグラウンドで実行され、コマンドを終了するまで毎分スケジューラを呼び出します:
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('');
スケジュールされた Artisan またはシステムコマンドが非ゼロの終了コードで終了した場合にのみ出力をメール送信したい場合は、emailOutputOnFailure
メソッドを使用します:
Schedule::command('report:generate')
->daily()
->emailOutputOnFailure('');
emailOutputTo
、emailOutputOnFailure
、sendOutputTo
、および 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 のピング
pingBefore
および thenPing
メソッドを使用して、スケジューラはタスクが実行される前後に指定された URL を自動的にピングできます。このメソッドは、Envoyer のような外部サービスに、スケジュールされたタスクが開始または終了したことを通知するのに便利です:
Schedule::command('emails:send')
->daily()
->pingBefore($url)
->thenPing($url);
pingBeforeIf
および thenPingIf
メソッドは、指定された条件が true
の場合にのみ、指定された URL をピングするために使用できます:
Schedule::command('emails:send')
->daily()
->pingBeforeIf($condition, $url)
->thenPingIf($condition, $url);
pingOnSuccess
および pingOnFailure
メソッドは、タスクが成功または失敗した場合にのみ、指定された URL をピングするために使用できます。失敗は、スケジュールされた Artisan またはシステムコマンドが非ゼロの終了コードで終了したことを示します:
Schedule::command('emails:send')
->daily()
->pingOnSuccess($successUrl)
->pingOnFailure($failureUrl);
イベント
Laravel は、スケジューリングプロセス中にさまざまな イベント をディスパッチします。次のイベントのいずれかに対して リスナー を定義できます:
イベント名 |
---|
Illuminate\Console\Events\ScheduledTaskStarting |
Illuminate\Console\Events\ScheduledTaskFinished |
Illuminate\Console\Events\ScheduledBackgroundTaskFinished |
Illuminate\Console\Events\ScheduledTaskSkipped |
Illuminate\Console\Events\ScheduledTaskFailed |