【PHP】Laravelでイベントとリスナーを実装する方法とその利用シーン

対象バージョン: Laravel 13.x / PHP 8.3+(2026年時点のシリーズ基準)
一次ソース: Events

注文が完了した、ユーザーが登録した——こうした「アプリ内の出来事」に対して、メール送信やログ記録など複数の後処理を直書きすると、コントローラーやサービスが肥大化し、変更のたびに影響範囲が広がります。Laravel のイベントとリスナーは、出来事(イベント)と反応(リスナー)を分離し、1つのイベントに複数の処理をぶら下げられる仕組みです。この記事では、なぜ使うか、どう作るか、どう登録・配送するか、キュー化と運用メモまでを Laravel 13.x の公式手順に沿って整理します。

この記事で分かること

  • イベント/リスナーを使う理由(疎結合と複数反応)
  • Laravel 13.x での作成(make:event / make:listener --event)
  • 登録の主経路(Event Discovery)と任意の手動登録(Event::listen)
  • ディスパッチ(::dispatch / event())と確認(event:list)
  • キュー可能なリスナー(ShouldQueue)の要点
  • 向き・不向き(利用シーン)と運用メモ

なぜイベントとリスナーを使うか

Laravel のイベントは、アプリケーション内で起きる事象をクラスとして表現します。リスナーはその事象への反応を定義します。公式ドキュメントが示すとおり、これはシンプルなオブザーバーパターンの実装です(Events)。

典型例は「注文が出荷された」ときに Slack 通知やメールを送るケースです。出荷処理のコードに通知コードを埋め込む代わりに、OrderShipped イベントを発火し、通知用リスナーが受け取ります。出荷ロジックと通知ロジックが互いに依存しないため、あとから「監査ログも残す」といったリスナーを追加しても、出荷側の変更は不要です。

向いていること

  • 1つのアクションのあとに、互いに独立した複数の後処理がある
  • ドメインの「事実」を明示し、呼び出し側を薄く保ちたい

向いていない/別手段がよいこと

  • 定期的な DB バックアップなど、時刻駆動の処理 → タスクスケジューラ向き
  • 単一モデルのライフサイクルフックだけが必要 → Eloquent オブザーバーで足りる場合がある
  • 「あとで重い処理をまとめて実行」が主目的で、ドメインイベントとしての意味が薄い → Job の直接ディスパッチで十分な場合がある

本記事では、イベントとしての意味がはっきりしている「出荷完了 → 通知」系を主例にします。


対象バージョンと登録方法の変遷

本記事の前提は Laravel 13.x / PHP 8.3+ です。Laravel 13 は 2026年3月17日リリースの現行メジャーで、サポート表上 PHP 8.3〜8.5 を対象とします(Release Notes)。

Laravel イベント/リスナー登録の主経路
10.x 以前の典型 app/Providers/EventServiceProvider.php の $listen 配列
11.x / 13.x(現行) Event Discovery(app/Listeners を自動スキャン)。手動なら AppServiceProvider で Event::listen

fresh な Laravel 13 スケルトンには EventServiceProvider 自体がありません。旧記事で見かける $listen = [ OrderShipped::class => [SendShipmentNotification::class] ] は、10.x 向けの手順です。現行で同じ手順を「必須」として書くと、ファイルが無くて詰まります。

手動で明示登録したい場合の現行経路は、AppServiceProvider::boot での Event::listen です。本記事は公式どおり Discovery を主、手動登録を補助として扱います。


イベントの作成

イベントクラスは、出来事に必要なデータを運ぶコンテナです。Artisan で生成します。

php artisan make:event OrderShipped

生成後、コンストラクタで公開プロパティを受け取る形が一般的です(公式例も同趣旨)。

<?php

namespace App\Events;

use App\Models\Order;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class OrderShipped
{
    use Dispatchable, SerializesModels;

    public function __construct(
        public Order $order,
    ) {}
}

Dispatchable により後述の OrderShipped::dispatch(...) が使えます。SerializesModels は、キュー化されたリスナーで Eloquent モデルを安全に直列化するときに役立ちます。


リスナーの作成

リスナーはイベントへの反応です。--event で対象イベントを指定すると、handle の型ヒントまで揃えてくれます。

php artisan make:listener SendShipmentNotification --event=OrderShipped

生成されたリスナーの骨子は次のような形です。

<?php

namespace App\Listeners;

use App\Events\OrderShipped;

class SendShipmentNotification
{
    public function handle(OrderShipped $event): void
    {
        // $event->order を使って通知・メール送信など
    }
}

1つのイベントに複数リスナーを付けられます。例: 通知用と監査ログ用を別クラスにする。Discovery は handle または __invoke で始まるメソッドを、型ヒントされたイベントのリスナーとして登録します。


登録(Event Discovery が既定)

Laravel 13.x では、既定で app/Listeners をスキャンしてリスナーを自動登録します。make:listener --event=... で型ヒントが付いていれば、追加の $listen 配列は不要です。

登録内容の確認:

php artisan event:list

本番ではデプロイ時に php artisan event:cache(または optimize)でリスナー一覧をキャッシュすると起動が速くなります。クリアは event:clear です。

任意: 手動登録(AppServiceProvider)

Discovery 以外のディレクトリやクロージャで聴きたい場合は、AppServiceProvider::boot で明示できます。

use App\Events\OrderShipped;
use App\Listeners\SendShipmentNotification;
use Illuminate\Support\Facades\Event;

public function boot(): void
{
    Event::listen(
        OrderShipped::class,
        SendShipmentNotification::class,
    );
}

リスナーを別ディレクトリに置く場合は、bootstrap/app.php の withEvents(discover: [...]) でスキャン対象を広げられます。

歴史メモ: Laravel 10.x では EventServiceProvider の $listen が典型でした。アップグレード後に古い手順のまま探す必要はありません。


イベントのディスパッチ

処理の本体(例: 出荷確定)が終わったタイミングでイベントを発火します。公式の主例は静的 dispatch です。

use App\Events\OrderShipped;

// 出荷処理のあと
OrderShipped::dispatch($order);

event() ヘルパーでも同様です。

event(new OrderShipped($order));

どちらもイベントディスパッチャ経由で登録済みリスナーを呼び出します。条件付きなら dispatchIf / dispatchUnless もあります。

DB トランザクション内で発火し、コミット後にだけ届けたい場合は、イベント側で ShouldDispatchAfterCommit を実装する選択肢があります(詳細は公式 Events ドキュメント)。


キュー可能なリスナー(ShouldQueue)

メール送信や外部 API など時間がかかる反応は、同期実行だとリクエストを遅くします。リスナーに ShouldQueue を付けると、イベント発火時にリスナーがキューへ載り、ワーカーが非同期で処理します。

<?php

namespace App\Listeners;

use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;

class SendShipmentNotification implements ShouldQueue
{
    public function handle(OrderShipped $event): void
    {
        // メール送信など
    }
}

前提として、キュー接続の設定と queue:work(または相当)の稼働が必要です。make:listener 生成クラスには ShouldQueue の import が含まれることが多いですが、implements は自動では付かないため、必要なリスナーだけ明示してください。

失敗時の failed メソッド、試行回数、トランザクション後キュー(ShouldQueueAfterCommit)などは公式の「Queued Event Listeners」を参照してください。本記事では導入のみに留めます。


利用シーンの具体例

シナリオ: ユーザーが商品を購入し、出荷が確定したときに通知を送る。

  1. php artisan make:event OrderShipped
  2. php artisan make:listener SendShipmentNotification --event=OrderShipped
  3. (任意)重い通知ならリスナーに ShouldQueue を付与
  4. 出荷処理の成功後に OrderShipped::dispatch($order)(または event(new OrderShipped($order)))
  5. php artisan event:list で OrderShipped → SendShipmentNotification を確認

同様に「新規登録 → ウェルカムメール」も、登録完了イベント+メール用リスナーの組み合わせが向いています。一方、定期バックアップはイベントではなくスケジューラの領域です。時刻で動かす処理をイベント例に混ぜると、読者が手段を取り違えやすいため、本記事では扱いません。

定期バックアップなどの時刻駆動処理の実装手順は、Laravelでタスクスケジューリングを実装する方法で整理しています。


運用メモ

  • 確認: 開発中は event:list で Discovery/手動登録の結果を見る
  • 本番: event:cache をデプロイ手順に含める
  • テスト: Event::fake() でリスナー実行を止め、assertDispatched 等で発火だけ検証できる(Events — Testing)
  • 購読者(Subscriber): 複数イベントを1クラスで束ねたい場合は Event Subscribers。Discovery 規約に沿うか、Event::subscribe で登録
  • スコープ: 本記事は Observer/Notification/Job の深掘りには踏み込みません。手段の選択は「ドメインの出来事を明示したいか」「非同期が必要か」で切り分けると迷いが減ります

まとめ

  • Laravel 13.x では Event Discovery が既定。EventServiceProvider::$listen は旧典型であり、fresh にはプロバイダ自体が無い
  • make:event / make:listener --event で骨格を作り、::dispatch または event() で発火する
  • 重い処理はリスナーに ShouldQueue を付け、キュー基盤とセットで使う
  • 利用シーンは「1事実 → 複数の独立した後処理」。定期処理はスケジューラへ

公式の詳細・属性によるキュー制御・Subscriber は Events(Laravel 13.x) を参照してください。

コメントする