株式会社WR

株式会社WR

WEB TOTAL CONSULTING

Laravelのスケジューラーで定期バッチ処理を実装する
ブログ一覧へ
技術ブログ

Laravelのスケジューラーで定期バッチ処理を実装する

Laravelのタスクスケジューラーを使うと、cronの設定を1行だけにしてすべてのバッチ処理をコードで管理できます。実用的なコマンドの作り方も解説します。

Laravelスケジューラーとは

Laravelのスケジューラーは、Linuxのcrontabの代わりにPHPコードでジョブのスケジュールを管理できる仕組みです。

crontabは設定が分散しやすく、バージョン管理もできません。Laravelのスケジューラーを使うと、全てのスケジュール定義をコードベースに集約できます。

# サーバーのcrontabに追加するのはこの1行だけ
* * * * * cd /path/to/project && php artisan schedule:run >> /dev/null 2>&1

スケジューラーの定義場所

Laravel 11では routes/console.php または bootstrap/app.php でスケジュールを定義します。

// bootstrap/app.php
use Illuminate\Console\Scheduling\Schedule;

->withSchedule(function (Schedule $schedule) {
    $schedule->command('app:send-daily-report')->dailyAt('09:00');
    $schedule->command('app:cleanup-old-records')->weekly()->mondays()->at('03:00');
    $schedule->job(new SendNewsletterJob)->monthly()->at('10:00');
})

Artisanコマンドの作成

php artisan make:command SendDailyReport
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Services\ReportService;

class SendDailyReport extends Command
{
    protected $signature = 'app:send-daily-report
                            {--date= : レポート対象日(YYYY-MM-DD)}
                            {--dry-run : 実際には送信しない}';

    protected $description = '日次レポートをメールで送信する';

    public function __construct(
        private readonly ReportService $reportService,
    ) {
        parent::__construct();
    }

    public function handle(): int
    {
        $date = $this->option('date')
            ? now()->parse($this->option('date'))
            : now()->yesterday();

        $this->info("レポート生成: {$date->format('Y-m-d')}");

        $report = $this->reportService->generate($date);

        if ($this->option('dry-run')) {
            $this->table(
                ['指標', '値'],
                collect($report->summary)->map(fn($v, $k) => [$k, $v])->values()->toArray()
            );
            $this->warn('Dry-runモード: メールは送信しません');
            return self::SUCCESS;
        }

        $this->reportService->sendEmail($report);
        $this->info("送信完了: {$report->recipients->count()}名");

        return self::SUCCESS;
    }
}

スケジュール頻度の指定

$schedule->command('app:task')
    ->everyMinute()          // 毎分
    ->everyFiveMinutes()     // 5分ごと
    ->everyFifteenMinutes()  // 15分ごと
    ->hourly()               // 毎時
    ->hourlyAt(30)           // 毎時30分
    ->daily()                // 毎日(0:00)
    ->dailyAt('09:00')       // 毎日9時
    ->twiceDaily(8, 20)      // 8時と20時
    ->weekly()               // 毎週(日曜0:00)
    ->weeklyOn(1, '08:00')   // 毎週月曜8時
    ->monthly()              // 毎月1日0:00
    ->monthlyOn(15, '09:00') // 毎月15日9時
    ->quarterly()            // 四半期ごと
    ->yearly()               // 毎年1月1日
    ->cron('0 9 * * 1-5');   // 平日9時(カスタムcron式)

実践的なスケジュール設定例

->withSchedule(function (Schedule $schedule) {

    // EC価格収集:平日の業務時間帯に1時間ごと
    $schedule->command('ec:collect-prices')
        ->hourly()
        ->weekdays()
        ->between('08:00', '20:00')
        ->withoutOverlapping(30)  // 30分以内に前の実行が完了しない場合はスキップ
        ->onFailure(function () {
            // Slackに通知
        });

    // 日次レポート:毎朝9時
    $schedule->command('app:send-daily-report')
        ->dailyAt('09:00')
        ->timezone('Asia/Tokyo')
        ->onSuccess(fn() => Log::info('日次レポート送信完了'))
        ->onFailure(fn() => Log::error('日次レポート送信失敗'));

    // データベースバックアップ:毎晩2時
    $schedule->command('backup:run --only-db')
        ->dailyAt('02:00')
        ->onOneServer()  // 複数サーバーでも1台だけ実行
        ->runInBackground();

    // 古いレコードの削除:毎週日曜3時
    $schedule->command('app:prune-old-data --days=365')
        ->weekly()->sundays()->at('03:00');

    // キャッシュウォームアップ:5分ごと
    $schedule->call(function () {
        cache()->remember('global.stats', 300, fn() => computeStats());
    })->everyFiveMinutes();

})

withoutOverlapping——重複実行を防ぐ

時間のかかるジョブが次の実行時刻までに終わらない場合、withoutOverlapping で重複を防げます。

$schedule->command('app:heavy-batch')
    ->everyFifteenMinutes()
    ->withoutOverlapping(60); // 前のジョブが60分以内に終わらなければスキップ

onOneServer——複数サーバー環境での排他制御

// Redisなどのキャッシュドライバーが必要
$schedule->command('app:monthly-report')
    ->monthly()
    ->onOneServer();

スケジュールのテスト

# スケジュール一覧を確認
php artisan schedule:list

# 特定のコマンドをすぐに実行(スケジュールなしで)
php artisan app:send-daily-report --date=2024-01-15

# ドライランで確認
php artisan app:send-daily-report --dry-run

# スケジューラーをローカルで連続実行
php artisan schedule:work

まとめ

Laravelのスケジューラーはcrontabを1行に集約し、全てのスケジュール定義をコードで管理できます。withoutOverlappingonOneServer・失敗時コールバックなどを組み合わせることで、本番運用に耐える定期バッチ処理を実装できます。弊社では価格収集・レポート生成・データ同期などの定期処理をスケジューラーで管理しています。

LaravelによるWebシステム開発のご相談はお気軽にどうぞ。

Category 技術ブログ

Related Posts

関連記事

開発・技術のご相談はお気軽に

お見積りは無料です。まずはお気軽にご相談ください。

お問い合わせ →