イントロダクション
以前は、サーバでスケジュールする必要のあるタスクごとにcron設定エントリを作成する必要がありました。しかしながら、タスクスケジュールがソース管理されないため、これはすぐに苦痛になる可能性があります。既存のcronエントリを表示したり、エントリを追加したりするには、サーバへSSHで接続する必要がありました。
Laravelのコマンドスケジューラは、サーバ上でスケジュールするタスクを管理するための新しいアプローチを提供しています。スケジューラを使用すると、Laravelアプリケーション自体の中でコマンドスケジュールを流暢かつ表現力豊かに定義できます。スケジューラを使用する場合、サーバに必要なcronエントリは1つだけです。タスクスケジュールは、app/Console/Kernel.php
ファイルのschedule
メソッドで定義されます。手を付けるのに役立つように、メソッド内に簡単な例が定義されています。
スケジュール定義
スケジュールするすべてのタスクは、アプリケーションのApp\Console\Kernel
クラスのschedule
メソッドで定義します。はじめに、例を見てみましょう。この例では、毎日深夜に呼び出されるようにクロージャをスケジュールします。クロージャ内で、データベースクエリを実行してテーブルをクリアします。
<?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
use Illuminate\Support\Facades\DB;
class Kernel extends ConsoleKernel
{
/**
* アプリケーションのコマンド実行スケジュール定義
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
*/
protected function schedule(Schedule $schedule)
{
$schedule->call(function () {
DB::table('recent_users')->delete();
})->daily();
}
}
クロージャを使用したスケジュールに加えて、呼び出し可能なオブジェクトをスケジュールすることもできます。呼び出し可能なオブジェクトは、__invoke
メソッドを含む単純なPHPクラスです。
$schedule->call(new DeleteRecentUsers)->daily();
スケジュールしたタスクの概要と、次に実行がスケジュールされている時間を表示したい場合は、schedule:list
Artisanコマンドを使用します。
php artisan schedule:list
Artisanコマンドのスケジュール
クロージャのスケジュールに加えて、Artisanコマンドおよびシステムコマンドをスケジュールすることもできます。たとえば、command
メソッドを使用して、コマンドの名前またはクラスのいずれかを使用してArtisanコマンドをスケジュールできます。
コマンドのクラス名を使用してArtisanコマンドをスケジュールする場合、コマンドが呼び出されたときにコマンドに提供する必要がある追加のコマンドライン引数の配列を渡せます。
use App\Console\Commands\SendEmailsCommand;
$schedule->command('emails:send Taylor --force')->daily();
$schedule->command(SendEmailsCommand::class, ['Taylor', '--force'])->daily();
キュー投入するジョブのスケジュール
キュー投入するジョブをスケジュールするには、job
メソッドを使います。このメソッドを使うと、ジョブをキューに入れるためのクロージャを自前で作成するcall
メソッドを使わずとも、ジョブをスケジュール実行できます。
use App\Jobs\Heartbeat;
$schedule->job(new Heartbeat)->everyFiveMinutes();
オプションの2番目と3番目の引数をjob
メソッドに指定して、ジョブのキューに入れるために使用するキュー名とキュー接続を指定できます。
use App\Jobs\Heartbeat;
// "sqs"接続の"heartbeats"キューにジョブをディスパッチ
$schedule->job(new Heartbeat, 'heartbeats', 'sqs')->everyFiveMinutes();
シェルコマンドのスケジュール
オペレーティングシステムでコマンドを実行するためにはexec
メソッドを使います。
$schedule->exec('node /home/forge/script.js')->daily();
繰り返しのスケジュールオプション
指定間隔で実行するようにタスクを設定する方法の例をいくつか見てきました。しかし、タスクに割り当てることができるタスクスケジュールの間隔は他にもたくさんあります。
メソッド | 説明 |
---|---|
->cron('* * * * *'); |
カスタムcronスケジュールでタスクを実行 |
->everyMinute(); |
毎分タスク実行 |
->everyTwoMinutes(); |
2分毎にタスク実行 |
->everyThreeMinutes(); |
3分毎にタスク実行 |
->everyFourMinutes(); |
4分毎にタスク実行 |
->everyFiveMinutes(); |
5分毎にタスク実行 |
->everyTenMinutes(); |
10分毎にタスク実行 |
->everyFifteenMinutes(); |
15分毎にタスク実行 |
->everyThirtyMinutes(); |
30分毎にタスク実行 |
->hourly(); |
毎時タスク実行 |
->hourlyAt(17); |
1時間ごと、毎時17分にタスク実行 |
->everyOddHour(); |
奇数時間ごとにタスク実行 |
->everyTwoHours(); |
2時間毎にタスク実行 |
->everyThreeHours(); |
3時間毎にタスク実行 |
->everyFourHours(); |
4時間毎にタスク実行 |
->everySixHours(); |
6時間毎にタスク実行 |
->daily(); |
毎日深夜12時に実行 |
->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(); |
毎年1月1日の00:00にタスク実行 |
->yearlyOn(6, 1, '17:00'); |
毎年6月1日の17:00にタスク実行 |
->timezone('America/New_York'); |
タスクのタイムゾーンを設定 |
これらの方法を追加の制約と組み合わせてると、特定の曜日にのみ実行する、さらに細かく調整したスケジュールを作成できます。たとえば、毎週月曜日に実行するようにコマンドをスケジュールできます。
// 週に1回、月曜の13:00に実行
$schedule->call(function () {
//
})->weekly()->mondays()->at('13:00');
// ウィークデーの8時から17時まで1時間ごとに実行
$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); |
クロージャの戻り値がtrue の時のみに限定 |
->environments($env); |
指定の環境でのみタスク実行を限定 |
曜日の限定
days
メソッドはタスクを週の指定した曜日に実行するように制限するために使用します。たとえば、日曜日と水曜日に毎時コマンドを実行するようにスケジュールするには次のように指定します。
$schedule->command('emails:send')
->hourly()
->days([0, 3]);
または、タスクを実行する日を定義するときに、Illuminate\Console\Scheduling\Schedule
クラスで使用可能な定数を使用することもできます。
use Illuminate\Console\Scheduling\Schedule;
$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
メソッドを使い、タスクのスケジュールをどこのタイムゾーンとみなすか指定できます。
$schedule->command('report:generate')
->timezone('America/New_York')
->at('2:00')
スケジュールされたすべてのタスクに同じタイムゾーンを繰り返し割り当てる場合は、App\Console\Kernel
クラスでscheduleTimezone
メソッドを定義することをお勧めします。このメソッドは、スケジュールされたすべてのタスクに割り当てる必要があるデフォルトのタイムゾーンを返す必要があります。
/**
* スケジュールされたイベントで使用するデフォルトのタイムゾーン取得
*
* @return \DateTimeZone|string|null
*/
protected function scheduleTimezone()
{
return 'America/Chicago';
}
Warning!! タイムゾーンの中には夏時間を取り入れているものがあることを忘れないでください。夏時間の切り替えにより、スケジュールしたタスクが2回実行されたり、まったくされないことがあります。そのため、可能であればタイムゾーンによるスケジュールは使用しないことを推奨します。
タスク多重起動の防止
デフォルトでは以前の同じジョブが起動中であっても、スケジュールされたジョブは実行されます。これを防ぐには、withoutOverlapping
メソッドを使用してください。
$schedule->command('emails:send')->withoutOverlapping();
この例の場合、emails:send
Artisanコマンドは実行中でない限り毎分実行されます。withoutOverlapping
メソッドは指定したタスクの実行時間の変動が非常に大きく、予想がつかない場合にとくに便利です。
必要であれば、「重起動の防止(without overlapping)」ロックを期限切れにするまでに、何分間経過させるかを指定できます。時間切れまでデフォルトは、24時間です。
$schedule->command('emails:send')->withoutOverlapping(10);
withoutOverlapping
メソッドは、動作の裏でアプリケーションのキャッシュを利用してロックを取得します。必要であれば、schedule:clear-cache
Artisanコマンドを使用して、これらのキャッシュ・ロックを解除できます。これは通常、予期しないサーバの問題でタスクがスタックした場合のみ必要です。
単一サーバ上でのタスク実行
Warning!! この機能を利用するには、アプリケーションのデフォルトのキャッシュドライバとして
database
、memcached
、dynamodb
、redis
キャッシュドライバを使用している必要があります。さらに、すべてのサーバが同じ中央キャッシュサーバと通信している必要があります。
アプリケーションのスケジューラを複数のサーバで実行する場合は、スケジュールしたジョブを単一のサーバでのみ実行するように制限できます。たとえば、毎週金曜日の夜に新しいレポートを生成するスケジュールされたタスクがあるとします。タスクスケジューラが3つのワーカーサーバで実行されている場合、スケジュールされたタスクは3つのサーバすべてで実行され、レポートを3回生成してしまいます。これは良くありません!
タスクをサーバひとつだけで実行するように指示するには、スケジュールタスクを定義するときにonOneServer
メソッドを使用します。このタスクを最初に取得したサーバが、同じタスクを同じCronサイクルで他のサーバで実行しないように、ジョブにアトミックなロックを確保します。
$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
メソッドを使用できます。
$schedule->command('analytics:report')
->daily()
->runInBackground();
Warning!!
runInBackground
メソッドはcommand
かexec
メソッドにより、タスクをスケジュールするときにのみ使用してください。
メンテナンスモード
アプリケーションがメンテナンスモードの場合、アプリケーションのスケジュールされたタスクは実行されません。これは、タスクがそのサーバで実行している未完了のメンテナンスに干渉することを望まないためです。ただし、メンテナンスモードでもタスクを強制的に実行したい場合は、タスクを定義するときにevenInMaintenanceMode
メソッドを呼び出すことができます。
$schedule->command('emails:send')->evenInMaintenanceMode();
スケジューラの実行
スケジュールするタスクを定義する方法を学習したので、サーバで実際にタスクを実行する方法について説明しましょう。schedule:run
Artisanコマンドは、スケジュールしたすべてのタスクを評価し、サーバの現在の時刻に基づいてタスクを実行する必要があるかどうかを判断します。
したがって、Laravelのスケジューラを使用する場合、サーバに1分ごとにschedule:run
コマンドを実行する単一のcron設定エントリを追加するだけで済みます。サーバにcronエントリを追加する方法がわからない場合は、Laravel
Forgeなどのcronエントリを管理できるサービスの使用を検討してください。
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
スケジュールをローカルで実行
通常、ローカル開発マシンにスケジューラのcronエントリを追加することはありません。代わりに、schedule:work
Artisanコマンドを使用できます。このコマンドはフォアグラウンドで実行し、コマンドを終了するまで1分ごとにスケジューラーを呼び出します。
php artisan schedule:work
タスク出力
Laravelスケジューラはスケジュールしたタスクが生成する出力を取り扱う便利なメソッドをたくさん用意しています。最初にsendOutputTo
メソッドを使い、後ほど内容を調べられるようにファイルへ出力してみましょう。
$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('taylor@example.com');
スケジュールしたArtisanまたはシステムコマンドが、ゼロ以外の終了コードで終了した場合にのみ出力を電子メールで送信する場合は、emailOutputOnFailure
メソッドを使用します。
$schedule->command('report:generate')
->daily()
->emailOutputOnFailure('taylor@example.com');
Warning!!
emailOutputTo
、emailOutputOnFailure
、sendOutputTo
、appendOutputTo
メソッドは、command
とexec
メソッドに対してのみ指定できます。
タスクフック
before
およびafter
メソッドを使用して、スケジュール済みのタスクを実行する前後に実行するコードを指定できます。
$schedule->command('emails:send')
->daily()
->before(function () {
// タスクが実行されようとしている
})
->after(function () {
// タスクが実行された
});
onSuccess
メソッドとonFailure
メソッドを使用すると、スケジュールされたタスクが成功または失敗した場合に実行されるコードを指定できます。失敗は、スケジュールされたArtisanまたはシステムコマンドがゼロ以外の終了コードで終了したことを示します。
$schedule->command('emails:send')
->daily()
->onSuccess(function () {
// タスク成功時…
})
->onFailure(function () {
// タスク失敗時…
});
コマンドから出力を利用できる場合は、フックのクロージャの定義で$output
引数としてIlluminate\Support\Stringable
インスタンスを型指定することで、after
、onSuccess
、またはonFailure
フックでアクセスできます。
use Illuminate\Support\Stringable;
$schedule->command('emails:send')
->daily()
->onSuccess(function (Stringable $output) {
// タスク成功時…
})
->onFailure(function (Stringable $output) {
// タスク失敗時…
});
URLへのPing
pingBefore
メソッドとthenPing
メソッドを使用すると、スケジューラーはタスクの実行前または実行後に、指定するURLに自動的にpingを実行できます。このメソッドは、Envoyerなどの外部サービスに、スケジュールされたタスクが実行を開始または終了したことを通知するのに役立ちます。
$schedule->command('emails:send')
->daily()
->pingBefore($url)
->thenPing($url);
pingBeforeIf
およびthenPingIf
メソッドは、特定の条件がtrue
である場合にのみ、特定のURLにpingを実行するために使用します。
$schedule->command('emails:send')
->daily()
->pingBeforeIf($condition, $url)
->thenPingIf($condition, $url);
pingOnSuccess
メソッドとpingOnFailure
メソッドは、タスクが成功または失敗した場合にのみ、特定のURLにpingを実行するために使用します。失敗は、スケジュールされたArtisanまたはシステムコマンドがゼロ以外の終了コードで終了したことを示します。
$schedule->command('emails:send')
->daily()
->pingOnSuccess($successUrl)
->pingOnFailure($failureUrl);
すべてのpingメソッドにGuzzle HTTPライブラリが必要です。Guzzleは通常、デフォルトですべての新しいLaravelプロジェクトにインストールされますが、誤って削除した場合は、Composerパッケージマネージャを使用してプロジェクトへ自分でGuzzleをインストールできます。
composer require guzzlehttp/guzzle
イベント
必要に応じて、スケジューラーから送られてくるイベントをリッスンすることもできます。通常、イベントリスナのマッピングは、アプリケーションのApp\Providers\EventServiceProvider
クラス内で定義します。
/**
* アプリケーションのイベントリスナのマッピング
*
* @var array
*/
protected $listen = [
'Illuminate\Console\Events\ScheduledTaskStarting' => [
'App\Listeners\LogScheduledTaskStarting',
],
'Illuminate\Console\Events\ScheduledTaskFinished' => [
'App\Listeners\LogScheduledTaskFinished',
],
'Illuminate\Console\Events\ScheduledBackgroundTaskFinished' => [
'App\Listeners\LogScheduledBackgroundTaskFinished',
],
'Illuminate\Console\Events\ScheduledTaskSkipped' => [
'App\Listeners\LogScheduledTaskSkipped',
],
'Illuminate\Console\Events\ScheduledTaskFailed' => [
'App\Listeners\LogScheduledTaskFailed',
],
];