> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# タスクスケジューリング

> Laravelのスケジューラを使って定期実行タスクをエレガントに管理する方法を学びます。

## タスクスケジューリングとは

従来、サーバーで定期実行が必要なタスクごとに cron エントリを手書きする必要がありました。
しかしこの方法ではスケジュール定義がソースコード外に存在するため、バージョン管理できず、確認・変更のたびに SSH ログインが必要になります。

Laravelのスケジューラを使うと、**アプリケーション内でスケジュールを流暢に定義**できます。
サーバーに登録する cron エントリは1行だけで済み、スケジュール定義はコードとともにバージョン管理されます。

スケジュールは `routes/console.php` に定義するのが標準的なスタイルです。

```php theme={null}
<?php

use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schedule;

Schedule::call(function () {
    DB::table('recent_users')->delete();
})->daily();
```

<Info>
  `schedule:list` Artisanコマンドで、定義済みのタスク一覧と次回実行予定時刻を確認できます。

  ```shell theme={null}
  php artisan schedule:list
  ```
</Info>

### スケジューラーの実行フロー

```mermaid theme={null}
flowchart TD
    A["* * * * * artisan schedule:run<br>（毎分 cron が呼び出す）"] --> B["Console Kernel"]
    B --> C["schedule() メソッド呼び出し"]
    C --> D["登録済みタスクを順番に評価"]
    D --> E{"Cron 式が現在時刻に<br>一致しているか?"}
    E -->|"一致しない"| F["スキップ"]
    E -->|"一致する"| G{"when / skip /<br>environments 条件チェック"}
    G -->|"条件不一致"| F
    G -->|"条件一致"| H{"メンテナンス<br>モード中?"}
    H -->|"通常モード"| I["タスク実行<br>(before フック → 本体 → after フック)"]
    H -->|"メンテナンスモード"| J{"evenInMaintenanceMode()<br>が設定されているか?"}
    J -->|"はい"| I
    J -->|"いいえ"| F
```

## スケジュールの定義方法

スケジュールの定義は `routes/console.php` に記述します。
`bootstrap/app.php` の `withSchedule` メソッドを使って定義する方法もあります。

```php theme={null}
// bootstrap/app.php
use Illuminate\Console\Scheduling\Schedule;

->withSchedule(function (Schedule $schedule) {
    $schedule->call(new DeleteRecentUsers)->daily();
})
```

## スケジュール可能なタスクの種類

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

`command` メソッドでArtisanコマンドをスケジュールします。
コマンド名またはクラス名で指定できます。

```php theme={null}
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コマンドにも、定義の直後にスケジュールメソッドをチェーンできます。

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

### キュージョブのスケジュール

`job` メソッドで[キュージョブ](/jp/queues)をスケジュールします。
クロージャを使わずにジョブをキューに積む便利な方法です。

```php theme={null}
use App\Jobs\Heartbeat;
use Illuminate\Support\Facades\Schedule;

Schedule::job(new Heartbeat)->everyFiveMinutes();
```

キュー名や接続先を指定することもできます。

```php theme={null}
// "heartbeats" キュー、"sqs" 接続でディスパッチ
Schedule::job(new Heartbeat, 'heartbeats', 'sqs')->everyFiveMinutes();
```

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

`exec` メソッドでOSコマンドを実行します。

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::exec('node /home/forge/script.js')->daily();
```

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

`call` メソッドで任意のPHPクロージャをスケジュールします。

```php theme={null}
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schedule;

Schedule::call(function () {
    DB::table('recent_users')->delete();
})->daily();
```

`__invoke` メソッドを持つ Invocable オブジェクトも渡せます。

```php theme={null}
Schedule::call(new DeleteRecentUsers)->daily();
```

## スケジュール頻度の設定

### 主な頻度メソッド

よく使う頻度メソッドを以下に示します。

| メソッド                       | 説明            |
| -------------------------- | ------------- |
| `->everySecond()`          | 毎秒実行          |
| `->everyMinute()`          | 毎分実行          |
| `->everyFiveMinutes()`     | 5分ごとに実行       |
| `->everyFifteenMinutes()`  | 15分ごとに実行      |
| `->everyThirtyMinutes()`   | 30分ごとに実行      |
| `->hourly()`               | 毎時実行          |
| `->hourlyAt(17)`           | 毎時17分に実行      |
| `->daily()`                | 毎日0時に実行       |
| `->dailyAt('13:00')`       | 毎日13時に実行      |
| `->twiceDaily(1, 13)`      | 毎日1時と13時に実行   |
| `->weekly()`               | 毎週日曜0時に実行     |
| `->weeklyOn(1, '8:00')`    | 毎週月曜8時に実行     |
| `->monthly()`              | 毎月1日0時に実行     |
| `->monthlyOn(4, '15:00')`  | 毎月4日15時に実行    |
| `->quarterly()`            | 四半期ごとの初日0時に実行 |
| `->yearly()`               | 毎年1月1日0時に実行   |
| `->timezone('Asia/Tokyo')` | タイムゾーンを指定     |

### cron式で直接指定

`cron` メソッドで cron 式を直接指定することもできます。

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::command('emails:send')->cron('0 9 * * *'); // 毎日9時に実行
```

### 頻度と曜日の組み合わせ

頻度メソッドと曜日の制約を組み合わせて、より細かいスケジュールを作れます。

```php theme={null}
use Illuminate\Support\Facades\Schedule;

// 毎週月曜13時に実行
Schedule::call(function () {
    // ...
})->weekly()->mondays()->at('13:00');

// 平日の8時から17時まで毎時実行
Schedule::command('foo')
    ->weekdays()
    ->hourly()
    ->timezone('Asia/Tokyo')
    ->between('8:00', '17:00');
```

### タイムゾーンの設定

`timezone` メソッドで個別タスクのタイムゾーンを指定できます。

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::command('report:generate')
    ->timezone('Asia/Tokyo')
    ->at('9:00');
```

すべてのタスクに共通のタイムゾーンを設定したい場合は、`config/app.php` の `schedule_timezone` を使います。

```php theme={null}
// config/app.php
'schedule_timezone' => 'Asia/Tokyo',
```

<Warning>
  夏時間(サマータイム)が適用されるタイムゾーンを使う場合、切り替えのタイミングでタスクが2回実行されるか、まったく実行されないことがあります。
  可能な限り UTC を使うことを推奨します。
</Warning>

## 条件制約

### when / skip

`when` はクロージャが `true` を返した場合のみタスクを実行します。
`skip` はその逆で、`true` を返した場合にスキップします。

```php theme={null}
use Illuminate\Support\Facades\Schedule;

// 条件が true のときだけ実行
Schedule::command('emails:send')->daily()->when(function () {
    return true;
});

// 条件が true のときはスキップ
Schedule::command('emails:send')->daily()->skip(function () {
    return true;
});
```

### environments

`environments` メソッドで特定の環境でのみ実行するよう制限できます。

```php theme={null}
Schedule::command('emails:send')
    ->daily()
    ->environments(['staging', 'production']);
```

### 時刻の制約

`between` / `unlessBetween` で実行時間帯を制限できます。

```php theme={null}
// 7時から22時の間だけ実行
Schedule::command('emails:send')
    ->hourly()
    ->between('7:00', '22:00');

// 23時から4時の間は実行しない
Schedule::command('emails:send')
    ->hourly()
    ->unlessBetween('23:00', '4:00');
```

### 曜日の制約

| メソッド                          | 説明                |
| ----------------------------- | ----------------- |
| `->weekdays()`                | 平日のみ              |
| `->weekends()`                | 週末のみ              |
| `->mondays()` 〜 `->sundays()` | 特定の曜日のみ           |
| `->days([0, 3])`              | 日曜(0)と水曜(3)など複数指定 |

```php theme={null}
use Illuminate\Support\Facades;
use Illuminate\Console\Scheduling\Schedule;

Facades\Schedule::command('emails:send')
    ->hourly()
    ->days([Schedule::SUNDAY, Schedule::WEDNESDAY]);
```

## 重複防止

デフォルトでは、前回のタスクがまだ実行中でも次の実行が開始されます。
`withoutOverlapping` を使うと、前の実行が終わるまで次の実行を待機させられます。

```php theme={null}
use Illuminate\Support\Facades\Schedule;

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

ロックの有効期限(分)を指定することもできます。デフォルトは24時間です。

```php theme={null}
// 10分後にロックが失効する
Schedule::command('emails:send')->withoutOverlapping(10);
```

<Info>
  `withoutOverlapping` はアプリケーションのキャッシュを使ってロックを管理します。
  タスクが予期せぬ問題でスタックした場合は `schedule:clear-cache` でロックを解除できます。

  ```shell theme={null}
  php artisan schedule:clear-cache
  ```
</Info>

## 複数サーバーでの実行制御

複数サーバーでスケジューラが動いている場合、`onOneServer` を使うことで1台のサーバーだけでタスクを実行できます。

```php theme={null}
use Illuminate\Support\Facades\Schedule;

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

<Warning>
  この機能を使うには、アプリケーションのデフォルトキャッシュドライバを `database`、`memcached`、`dynamodb`、または `redis` に設定し、すべてのサーバーが同じキャッシュサーバーに接続している必要があります。
</Warning>

### onOneServer() による分散実行制御フロー

```mermaid theme={null}
flowchart TD
    A["複数サーバーで<br>artisan schedule:run が同時実行"] --> B["サーバー 1"]
    A --> C["サーバー 2"]
    A --> D["サーバー 3"]

    B --> E["共有キャッシュサーバーで<br>アトミックロックを取得試行"]
    C --> E
    D --> E

    E -->|"取得成功（最初の 1 台）"| F["タスクを実行"]
    E -->|"取得失敗（他のサーバー）"| G["タスクをスキップ"]

    F --> H["実行完了後にロックを解放"]
```

## タスクのグループ化

複数のタスクに同じ設定を適用する場合は `group` メソッドを使ってまとめられます。

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::daily()
    ->onOneServer()
    ->timezone('Asia/Tokyo')
    ->group(function () {
        Schedule::command('emails:send --force');
        Schedule::command('emails:prune');
    });
```

## バックグラウンド実行

同じ時刻にスケジュールされたタスクは、デフォルトでは定義順に順番に実行されます。
長時間かかるタスクがあると後続のタスクの開始が遅れます。
`runInBackground` を使うと、タスクをバックグラウンドで並列実行できます。

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::command('analytics:report')
    ->daily()
    ->runInBackground();
```

<Warning>
  `runInBackground` は `command` と `exec` メソッドでのみ使用できます。
</Warning>

## メンテナンスモード

アプリケーションがメンテナンスモードのとき、スケジュールタスクは実行されません。
メンテナンスモード中でも強制的に実行させたい場合は `evenInMaintenanceMode` を使います。

```php theme={null}
Schedule::command('emails:send')->evenInMaintenanceMode();
```

### メンテナンスモードの対応フロー

```mermaid theme={null}
flowchart TD
    A["artisan schedule:run"] --> B{"アプリがメンテナンス<br>モードか?<br>(artisan down)"}
    B -->|"通常モード"| C["タスクを通常実行"]
    B -->|"メンテナンスモード"| D{"タスクに<br>evenInMaintenanceMode()<br>が設定されているか?"}
    D -->|"はい"| C
    D -->|"いいえ"| E["タスクをスキップ"]
```

## スケジューラの一時停止

コードを変更せずにスケジューラを一時停止できます。

```shell theme={null}
# スケジューラを停止
php artisan schedule:pause

# スケジューラを再開
php artisan schedule:continue
```

停止中でも特定のタスクだけ実行し続けたい場合は `evenWhenPaused` を使います。
ヘルスチェックやシステム監視など、メンテナンス中でも継続が必要なタスクに役立ちます。

```php theme={null}
Schedule::command('emails:send')->evenWhenPaused();
```

## 出力のハンドリング

### ファイルへの出力

`sendOutputTo` でタスクの出力をファイルに保存できます。

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::command('emails:send')
    ->daily()
    ->sendOutputTo(storage_path('logs/emails-send.log'));
```

`appendOutputTo` を使うと、既存のファイルに追記します。

```php theme={null}
Schedule::command('emails:send')
    ->daily()
    ->appendOutputTo(storage_path('logs/emails-send.log'));
```

### メールへの出力

`emailOutputTo` でタスクの出力をメールで送信できます。
事前にLaravelの[メール設定](/jp/mail)が必要です。

```php theme={null}
Schedule::command('report:generate')
    ->daily()
    ->sendOutputTo($filePath)
    ->emailOutputTo('admin@example.com');
```

失敗時のみメールを送る場合は `emailOutputOnFailure` を使います。

```php theme={null}
Schedule::command('report:generate')
    ->daily()
    ->emailOutputOnFailure('admin@example.com');
```

## タスクフック

`before` / `after` メソッドで、タスクの実行前後に処理を挿入できます。

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::command('emails:send')
    ->daily()
    ->before(function () {
        // タスク実行前の処理
    })
    ->after(function () {
        // タスク実行後の処理
    });
```

成功・失敗時のフックは `onSuccess` / `onFailure` で定義します。

```php theme={null}
use Illuminate\Support\Stringable;

Schedule::command('emails:send')
    ->daily()
    ->onSuccess(function (Stringable $output) {
        // 成功時の処理
    })
    ->onFailure(function (Stringable $output) {
        // 失敗時の処理
    });
```

## サーバーへのデプロイ

<Steps>
  <Step title="cron エントリを追加する">
    サーバーの crontab に以下の1行を追加するだけで、Laravelのスケジューラが毎分実行されます。

    ```shell theme={null}
    * * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
    ```

    `crontab -e` コマンドで編集できます。
  </Step>

  <Step title="スケジューラの動作を確認する">
    定義済みタスクの一覧と次回実行時刻を確認します。

    ```shell theme={null}
    php artisan schedule:list
    ```
  </Step>
</Steps>

<Tip>
  [Laravel Cloud](https://cloud.laravel.com) を使うと、cron の設定なしにスケジュールタスクを管理できます。
</Tip>

### ローカル開発での実行

ローカル環境では cron を使わず、`schedule:work` コマンドでスケジューラを常時起動できます。

```shell theme={null}
php artisan schedule:work
```

このコマンドはフォアグラウンドで動作し、毎分スケジューラを呼び出します。`Ctrl+C` で停止するまで動き続けます。

### サブ分スケジュール(1分未満の頻度)

通常の cron は1分が最小単位ですが、Laravelでは1秒単位のスケジュールも設定できます。

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::call(function () {
    DB::table('recent_users')->delete();
})->everySecond();
```

サブ分タスクが定義されている場合、`schedule:run` はその分が終わるまで動き続けて、すべてのサブ分タスクを処理します。

<Tip>
  サブ分タスクはキュージョブやバックグラウンドコマンドに処理を委譲することを推奨します。
  タスク自体が長時間かかると、後続のサブ分タスクの実行が遅れるためです。
</Tip>

デプロイ中に実行中の `schedule:run` を中断するには、デプロイスクリプトに以下を追加します。

```shell theme={null}
php artisan schedule:interrupt
```

## よく使うコマンドまとめ

```shell theme={null}
# タスク一覧を表示
php artisan schedule:list

# スケジューラを手動実行(サーバーのcronが呼び出すコマンド)
php artisan schedule:run

# ローカル開発用 常時起動
php artisan schedule:work

# スケジューラを一時停止
php artisan schedule:pause

# スケジューラを再開
php artisan schedule:continue

# 重複防止ロックをクリア
php artisan schedule:clear-cache

# サブ分タスクの実行中断(デプロイ時)
php artisan schedule:interrupt
```
