Laravel タスクスケジュールまとめ

# Laravel タスクスケジュールまとめ

- サーバで `cron` エントリでタスクの定期実行することに近い
- Laravel のスケジューラを使用すると、サーバには cron エントリがたった一つだけで済む
- タスクスケジュールは `app/Console/Kernel.php` の `schedule()` に定義する

## スケジューラを使う

以下が可能

- **クロージャ**の呼び出し
- **Artisan** コマンドをスケジュールする
- **Linux コマンド**をスケジュールする

### 前提

- スケジューラを使用するには、サーバに下記のような `cron` エントリを追加する
- 簡単に cron エントリを管理できる [Laravel Forge](https://forge.laravel.com/) のようなサービスをも存在する

```sh
# エディタが起動する
crontab -e

# Laravel コマンドスケジューラを毎分呼び出す
# スケジュールされているジョブを評価し、実行する必要のあるジョブを起動する

# 追記する。* * * * * にすると毎分実行の意味
# 最終行は改行しないと動かないので注意
* * * * * cd [プロジェクトのパス] && php artisan schedule:run >> /dev/null 2>&1


```

### クロージャ定義のスケジュール

- この例は毎日深夜 12 時にクロージャをスケジュールする
- クロージャの中でテーブルをクリアするデータベースクエリを実行する

```php
<?php

namespace App\Console;

use DB;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{
    /**
     * アプリケーションで提供するArtisanコマンド
     *
     * @var array
     */
    protected $commands = [
        //
    ];

    /**
     * アプリケーションのコマンド実行スケジュール定義
     *
     * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
     * @return void
     */
    protected function schedule(Schedule $schedule)
    {
        $schedule->call(function () {
            DB::table('recent_users')->delete();
        })->daily();
    }
}
```

- クロージャを使用したスケジュールでは、**invoke 可能なオブジェクトも使用可能**
    - `__invoke` メソッドを含む PHP クラスのこと

```php
$schedule->call(new DeleteRecentUsers)->daily();
```

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

- `command()`
    - コマンド名か、そのクラスのどちらかを用いて、Artisanコマンドをスケジュールする

```php
$schedule->command('emails:send --force')->daily();

$schedule->command(EmailsCommand::class, ['--force'])->daily();
```

### キュー投入するジョブのスケジュール

- `job()`
    - ジョブをキューに入れるためのクロージャを自前で作成する `call()` を使わずとも、ジョブをスケジュール実行できる

```php
$schedule->job(new Heartbeat)->everyFiveMinutes();

// ジョブを "heartbeats" キューへ投入する
$schedule->job(new Heartbeat, 'heartbeats')->everyFiveMinutes();
```

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

- `exec()`

```php
$schedule->exec('node /home/forge/script.js')->daily();
```

### ルール

#### 繰り返しのスケジュールオプション


| メソッド                          | 説明                               |
| --------------------------------- | ---------------------------------- |
| `->cron('* * * * *');`            | Cron指定に基づきタスク実行         |
| `->everyMinute();`                | 毎分タスク実行                     |
| `->everyFiveMinutes();`           | ５分毎にタスク実行                 |
| `->everyTenMinutes();`            | １０分毎にタスク実行               |
| `->everyFifteenMinutes();`        | １５分毎にタスク実行               |
| `->everyThirtyMinutes();`         | ３０分毎にタスク実行               |
| `->hourly();`                     | 毎時タスク実行                     |
| `->hourlyAt(17);`                 | 一時間ごと、毎時１７分にタスク実行 |
| `->daily();`                      | 毎日深夜１２時に実行               |
| `->dailyAt('13:00');`             | 毎日13:00に実行                    |
| `->twiceDaily(1, 13);`            | 毎日1:00と13:00時に実行            |
| `->weekly();`                     | 毎週実行                           |
| `->weeklyOn(1, '8:00');`          | 毎週月曜日の8:00時に実行           |
| `->monthly();`                    | 毎月実行                           |
| `->monthlyOn(4, '15:00');`        | 毎月4日の15:00に実行               |
| `->quarterly();`                  | 四半期ごとに実行                   |
| `->yearly();`                     | 毎年実行                           |
| `->timezone('America/New_York');` | タイムゾーン設定                   |


```php
// 週に一回、月曜の13:00に実行
$schedule->call(function () {
    //
})->weekly()->mondays()->at('13:00');

// ウィークエンドの8時から17時まで一時間ごとに実行
$schedule->command('foo')
          ->weekdays()
          ->hourly()
          ->timezone('America/Chicago')
          ->between('8:00', '17:00');
```

#### 追加のスケジュール制約のリスト

| メソッド                   | 説明                                   |
| -------------------------- | -------------------------------------- |
| `->weekdays();`            | ウイークデーのみに限定                 |
| `->sundays();`             | 日曜だけに限定                         |
| `->mondays();`             | 月曜だけに限定                         |
| `->tuesdays();`            | 火曜だけに限定                         |
| `->wednesdays();`          | 水曜だけに限定                         |
| `->thursdays();`           | 木曜だけに限定                         |
| `->fridays();`             | 金曜だけに限定                         |
| `->saturdays();`           | 土曜だけに限定                         |
| `->between($start, $end);` | 開始と終了時間間にタスク実行を制限     |
| `->when(Closure);`         | クロージャの戻り値がtrueの時のみに限定 |


#### 時間制限

- `between()`
    - 一日の時間に基づき、実行時間を制限する

```php
$schedule->command('reminders:send')
                    ->hourly()
                    ->between('7:00', '22:00');
```

- `unlessBetween()`
    - その時間にタスクの実行を除外する

```php
$schedule->command('reminders:send')
                    ->hourly()
                    ->unlessBetween('23:00', '4:00');
```

#### 真値テスト制約

- `when()`
    - 指定した真値テストの結果に基づき制限を実行
    - 指定したクロージャが　true を返し、他の制約が実行を停止しない限りタスクを実行する
    - `when()` をいくつかチェーンした場合、全部の when 条件が true を返すときのみスケジュールされたコマンドが実行する

```php
$schedule->command('emails:send')->daily()->when(function () {
    return true;
});
```

- `skip()`
    - `when()`をひっくり返したもの
    - `skip()`へ渡したクロージャが true を返した時、スケジュールタスクは実行される

```php
$schedule->command('emails:send')->daily()->skip(function () {
    return true;
});
```

#### タイムゾーン

- `timezone()`
    - タスクのスケジュールをどこのタイムゾーンとみなすか指定できる

```php
$schedule->command('report:generate')
         ->timezone('America/New_York')
         ->at('02:00')
```

##### 夏時間

- タイムゾーンの中には夏時間を取り入れているものがある
- 夏時間の切り替えにより、スケジュールされたタスクが2回実行されたり、全くされないことがある
- 可能であればタイムゾーンによるスケジュールは使用しないことが推奨される


#### タスク多重起動の防止

- **デフォルトでは以前の同じジョブが起動中であっても、スケジュールされたジョブは実行される**
- これを防ぐには `withoutOverlapping()` を使用する
- 指定したタスクの実行時間の変動が非常に大きく、予想がつかない場合に特に便利

```php
// emails:send という Artisanコマンドは実行中でない限り毎分実行される
$schedule->command('emails:send')->withoutOverlapping();
```

##### without overlapping を期限切れにする

- 時間切れまでのデフォルトは 24 時間

```php
$schedule->command('emails:send')->withoutOverlapping(10);
```

#### 単一サーバ上でのタスク実行

- アプリケーションが複数のサーバで実行されている場合、スケジュール済みのジョブを単一サーバ上でのみに制限して実行できる

##### 前提

- この機能を使用するには、アプリケーションのデフォルトキャッシュドライバとして **memcached** か **redis** キャッシュドライバを使用する
- 全サーバが同じ単一のキャッシュサーバと通信している必要がある



##### ユースケース

たとえば、毎週の金曜の夜に、新しいレポートを生成するタスクをスケジュールしていると仮定する  
タスクスケジューラが 3 つのワーカーサーバ上で実行されているなら、スケジュールされているタスクは 3 つ全部のサーバで実行され、3 回レポートが生成される  
これを 1 回に制限したいようなケースに使う


##### 実行

- `onOneServer()`
    - このタスクを最初に取得したサーバが、同じタスクを同じ cron サイクルで他のサーバが実行しないように、ジョブにアトミックなロックをする

```php
$schedule->command('report:generate')
                ->fridays()
                ->at('17:00')
                ->onOneServer();
```

#### メンテナンスモード

- 通常のスケジュールタスクは、Laravel が**メンテナンスモードの間は実行されない**
- メンテナンスモードでも実行するように強制したい場合は `evenInMaintenanceMode()` を使用する

```php
$schedule->command('emails:send')->evenInMaintenanceMode();
```

#### タスク出力

以下のメソッドは `command()` と `exec()` に対して排他的なので注意する

- `sendOutputTo()`
    - 後ほどタスク内容を調べられるようにファイルへ出力できる

```php
$schedule->command('emails:send')
         ->daily()
         ->sendOutputTo($filePath);

```

- `appendOutputTo()`
    - タスク出力を指定したファイルに追記する

```php
$schedule->command('emails:send')
         ->daily()
         ->appendOutputTo($filePath);
```

- `emailOutputTo()`
    - 出力をメールで送ることができる
    - メール送信サービスの設定を済ませておく必要がある

```php
$schedule->command('foo')
         ->daily()
         ->sendOutputTo($filePath)
         ->emailOutputTo('foo@example.com');
```

#### タスクフック

- `before()`
- `after()`
    - スケジュールされたタスクの実行前後に指定したコードを実行できる

```php
$schedule->command('emails:send')
         ->daily()
         ->before(function () {
             // タスク開始時…
         })
         ->after(function () {
             // タスク終了時…
         });
```

#### URLへの Ping

##### 前提

- [Guzzle](https://github.com/guzzle/guzzle) HTTP ライブラリーが必要

```sh
composer require guzzlehttp/guzzle
```

##### 実行

- `pingBefore($url)`
- `thenPing($url$)`
    - タスク実行前、タスク完了後に指定したURLへ自動的に Ping することができる
    - [Laravel Envoyer](https://envoyer.io/) のような外部サービスにスケジュールされたタスクが始まる、または完了したことを知らせるのに便利

```php
$schedule->command('emails:send')
         ->daily()
         ->pingBefore($url)
         ->thenPing($url);
```

## 参考

[タスクスケジュール 5.6 Laravel](https://readouble.com/laravel/5.6/ja/scheduling.html)