Laravel 13 の scheduler を最小構成で試す手順です。fresh app から make:command で Command を 1 本作り、routes/console.php に Schedule::command(...)->everyMinute() を書いて、schedule:run で実行確認するところまでを扱います。認証、starter kit、Queue、production の cron 詳説はここでは入れません。
scheduler は、Laravel の Command をいつ実行するかをアプリ側で定義する仕組みです。この記事では cron の本番運用まで広げず、schedule:run で 1 本の Command が評価されるところだけを確認します。
前提環境
- Windows 11
- WSL2(Ubuntu)
- VS Code(Remote - WSL)
- Docker Desktop(WSL 連携有効)
- Laravel 13
composer:2コンテナ
コマンド実行は、特記がない限り WSL 側ターミナルです。
1. ゴールと非対象
この記事で着地したいのは次の 4 点です。
php artisan make:commandで最小 Command を作れるroutes/console.phpに scheduler を登録できるphp artisan schedule:listで登録内容を確認できるphp artisan schedule:run実行後にログファイルへ 1 行追記されることを確認できる
今回は scheduler の最初の 1 本だけを扱います。Queue 連携、withoutOverlapping() や onOneServer() などの運用オプション、cron の本番設定、認証付き画面からの起動導線は対象外です。
2. 新規デモを作って開始位置をそろえる
Laravel 13 の公式 installation docs では laravel new が基本導線ですが、この記事ではローカルに PHP を直接入れずに試せるよう、composer:2 コンテナでプロジェクトを作ります。
開始位置は次のディレクトリです。
mkdir -p ~/projects/laravel-scheduler-command-demo
cd ~/projects/laravel-scheduler-command-demo
code .
docker run --rm -u "$(id -u):$(id -g)" -v "$(pwd):/app" -w /app composer:2 create-project laravel/laravel .
compose.yml は次の内容で作成します。
services:
app:
image: composer:2
user: "${LOCAL_UID:-1000}:${LOCAL_GID:-1000}"
working_dir: /app
volumes:
- ./:/app
ports:
- "8000:8000"
command: php artisan serve --host=0.0.0.0 --port=8000
同じシェルで UID / GID を合わせて起動します。
export LOCAL_UID="$(id -u)"
export LOCAL_GID="$(id -g)"
docker compose up -d
docker compose ps
ブラウザで http://localhost:8000 を開き、Laravel の welcome 画面が見えれば準備完了です。ポート 8000 が埋まっている場合は、compose.yml の左側を 8001:8000 のように変えてから docker compose up -d をやり直してください。
起動状態は docker compose ps と welcome 画面で確認できます。
3. scheduler の全体像を先に見る
Laravel 13 の scheduling docs では、定期実行の定義場所として routes/console.php が案内されています。ここに「何を」「どの頻度で実行するか」を置き、実際に評価するのは php artisan schedule:run です。
全体の流れは次の図です。
flowchart LR
C[cron または schedule:run] --> S[routes/console.php の schedule 定義を評価]
S --> K[WriteSchedulerHeartbeat Command]
K --> L[storage/logs/scheduler-demo.log に追記]
確認する観点は次の 3 つです。
- Command は「実際に何をするか」を持つ
- Scheduler は「その Command をいつ動かすか」を持つ
- production では 1 本の cron から
php artisan schedule:runを毎分呼ぶ構成が基本になる
この記事では local で 1 回の評価を確かめたいので、cron の代わりに schedule:run を手で実行します。
4. 最小 Command を作る
Laravel アプリのルートで次のコマンドを実行し、Command のひな形を作ります。
docker compose exec app php artisan make:command WriteSchedulerHeartbeat
生成された app/Console/Commands/WriteSchedulerHeartbeat.php を次の内容に更新します。
<?php
namespace App\Console\Commands;
use Illuminate\Console\Attributes\Description;
use Illuminate\Console\Attributes\Signature;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
#[Signature('demo:scheduler-heartbeat')]
#[Description('Write one line to the scheduler demo log file')]
class WriteSchedulerHeartbeat extends Command
{
public function handle(): int
{
$logFile = storage_path('logs/scheduler-demo.log');
File::ensureDirectoryExists(dirname($logFile));
File::append(
$logFile,
sprintf('[%s] scheduler command executed%s', now()->format('Y-m-d H:i:s'), PHP_EOL),
);
$this->info('scheduler-demo.log に 1 行追記しました。');
return self::SUCCESS;
}
}
Laravel 13 の Artisan docs では、Command クラスに Signature と Description attribute を付ける例が示されています。今回はその形に合わせ、やることを 1 つに絞った Command にします。
コードのポイント
① Command の役割は「仕事そのもの」を持つこと
#[Signature('demo:scheduler-heartbeat')]
#[Description('Write one line to the scheduler demo log file')]
class WriteSchedulerHeartbeat extends Command
{
ここで定義しているのは、scheduler に登録される対象そのものです。頻度はまだ書かず、Command は「何をするか」だけを担います。
② 観測点をログファイルに固定すると挙動を追いやすい
$logFile = storage_path('logs/scheduler-demo.log');
File::ensureDirectoryExists(dirname($logFile));
File::append(
$logFile,
sprintf('[%s] scheduler command executed%s', now()->format('Y-m-d H:i:s'), PHP_EOL),
);
DB テーブルや画面は使わず、storage/logs/scheduler-demo.log への追記を成否の観測点にしています。最小構成では、観測点を絞るほうが追いやすいからです。
5. routes/console.php に schedule を登録する
Laravel 13 では app/Console/Commands 配下の Command が自動登録されるため、次は routes/console.php を開いて scheduler 定義を追加します。
更新後の routes/console.php は次のとおりです。
<?php
use App\Console\Commands\WriteSchedulerHeartbeat;
use Illuminate\Foundation\Inspiring;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Schedule;
Artisan::command('inspire', function () {
$this->comment(Inspiring::quote());
})->purpose('Display an inspiring quote');
Schedule::command(WriteSchedulerHeartbeat::class)
->everyMinute();
ここでは inspire の既定コマンドを残したまま、Schedule::command(...) を 1 本追加しています。scheduler の docs では、Command 名でもクラス名でも登録できますが、今回はクラス名でつなぐほうが、どの Command を呼ぶか追いやすくなります。
設定後の最初の確認は schedule:list です。
docker compose exec app php artisan schedule:list
php artisan demo:scheduler-heartbeat を含む行が表示され、先頭が * * * * * なら everyMinute() で Scheduler に登録できています。右端の Next Due: ... は次回実行予定です。
コードのポイント
① routes/console.php は scheduler 定義の入口になる
use App\Console\Commands\WriteSchedulerHeartbeat;
use Illuminate\Support\Facades\Schedule;
routes/console.php は HTTP route ではなく、console 側の入口をまとめる場所です。scheduler の定義もここへ寄せると、Command と実行頻度を同じ文脈で追えます。
② 頻度は scheduler 側にだけ書く
Schedule::command(WriteSchedulerHeartbeat::class)
->everyMinute();
everyMinute() のような頻度指定は scheduler 側の責務です。Command クラスへ時間条件を書き込まないため、同じ Command を別頻度で再利用しやすくなります。
6. schedule:run を実行してログを確認する
一覧に出ることを確認したら、次は 1 回だけ評価して動作を見ます。
docker compose exec app php artisan schedule:run
docker compose exec app tail -n 5 storage/logs/scheduler-demo.log
everyMinute() の task は、その時点の minute で実行対象なら schedule:run で評価されます。成功すると、1 つ目のコマンドでは scheduled task の実行ログが表示され、2 つ目のコマンドでは scheduler command executed を含む行が見えます。
ログファイルがまだ無い場合は、Command が一度も動いていないだけです。schedule:list に出ているか、schedule:run の実行に失敗していないかを先に確認してください。
開発中に毎分の評価を手動で打ち直したくない場合は、公式 docs にある schedule:work も使えます。
docker compose exec app php artisan schedule:work
これは foreground で scheduler を回し続ける local 向けの確認手段です。この記事の主線は schedule:run にあるため、まずは 1 回の評価が通ることだけ押さえれば足ります。
7. よくある詰まりどころ
schedule:list に出ない
原因の多くは routes/console.php の import 漏れか、Schedule::command(...) 自体を書いていないことです。WriteSchedulerHeartbeat::class を参照しているか、use Illuminate\Support\Facades\Schedule; があるかを見直してください。
schedule:run は通るのにログが増えない
Command 側の handle() が例外で止まっている可能性があります。次を実行して Laravel のログを確認します。
docker compose exec app tail -n 20 storage/logs/laravel.log
Command を直したのに挙動が変わらない
schedule:run はその都度アプリを起動して評価するので、手動確認だけなら再起動不要です。schedule:work を動かしっぱなしにしている場合は、コード変更後にそのプロセスを止めて起動し直してください。
production では何を置けばよいか分からない
基本は 1 行です。
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
これは Laravel 13 の scheduling docs にある基本形です。この記事では local 確認に留めていますが、本番ではこの 1 行から scheduler を呼びます。
8. まとめ
Laravel 13 の scheduler を最小構成で試すなら、押さえる場所は 3 つです。make:command で仕事本体を作ること、routes/console.php で頻度を登録すること、schedule:run で評価して観測点を確認すること。この 3 点だけで、scheduler の輪郭は十分つかめます。
関連して読みたい記事: