Skip to main content
パッケージにイベント駆動の拡張点を用意したいときは、イベントクラスの提供とリスナーの登録を分けて設計します。利用者が vendor 内のディレクトリを自動検出に追加しなくても動く構成にすると、インストール・更新・キャッシュ作成時の前提を減らせます。 このページではLaravel Framework v13.36.0 の実装を確認し、イベントの基本操作をパッケージの長期保守に適用します。プロバイダーの読み込みはパッケージの自動検出、テスト環境はパッケージのテストを参照してください。

パッケージのリスナーを明示登録する

パッケージが標準で実行する処理は、通常の Illuminate\Support\ServiceProvider の boot() で Event::listen() に登録できます。Laravelの既定のリスナー自動検出先はアプリケーションの app/Listeners であり、パッケージ内の src/Listeners まで自動で走査するわけではありません。 例として、配送完了イベントをログに記録するパッケージを作ります。イベントは通知に必要な値を持つだけのクラスです。
リスナーは、コンストラクターでロガーを受け取り、handle() でイベントを処理します。
プロバイダーにはイベントとリスナーのクラス名を登録します。
この構成はプロバイダー自身が読み込まれることを前提にします。Composerの extra.laravel.providers または利用者による明示登録を用意してください。イベントがHTTP処理でもキューワーカーでも必要なら、runningInConsole() で登録を制限しません。
リスナー登録だけを理由に、このプロバイダーへ DeferrableProvider を実装しないでください。遅延対象のサービスが解決されなければ、リスナーも登録されません。重いサービスのバインディングは、必要に応じて別の遅延プロバイダーへ分離します。

登録とインスタンス生成は別のタイミング

Dispatcher::listen() は、通常のイベント名に対するリスナーを内部配列へ追加します。上のようにクラス名を渡す場合、ここではリスナーのインスタンスを生成しません。 同期リスナーでは、イベント発火後に createClassCallable() がコンテナの make() を使ってクラスを解決します。そのため、コンストラクターの依存注入を利用できます。 この経路は handle() に対してコンテナの call() を行うものではありません。任意のサービスはコンストラクターに注入し、handle() ではイベントを受け取る設計にします。Artisanコマンドの handle() への依存注入と同じ仕組みだと考えないでください。 ここで説明しているのは同期リスナーです。ShouldQueue を実装する場合はキュー用の別経路になるため、トランザクション、再試行、依存の解決についてイベントのキュー処理とキューも確認します。

event:cacheは明示登録を止めない

event:cache は、イベントDispatcherに登録されたすべてのリスナーを丸ごとシリアライズするコマンドではありません。 EventCacheCommand::getEvents() は、Illuminate\Foundation\Support\Providers\EventServiceProvider とその派生プロバイダーを列挙し、各プロバイダーの自動検出結果と listens() のマッピングを保存します。通常の ServiceProvider::boot() に書いた Event::listen() は、この収集対象には入りません。 一方、キャッシュがある起動でも通常のプロバイダーの boot() は実行されます。キャッシュに含まれないことと、キャッシュがあると登録されないことは別です。 Foundationの EventServiceProvider を継承したパッケージで $listen を使う方法もあります。ただし、確認した実装では shouldDiscoverEvents() の既定値はその基底クラス自身に対する判定です。継承するだけでは、そのパッケージのリスナーディレクトリが自動検出されるわけではありません。 また、同プロバイダーの $subscribe と $observers は、getEvents() の結果を登録するループとは別に処理されます。$listen のキャッシュだけを見て、これらの登録まで省略されると判断しないでください。
Event::subscribe(SomeSubscriber::class) は、登録時にコンテナでサブスクライバーを生成して subscribe() を呼びます。クラス名による Event::listen() と同じ遅延性ではありません。サブスクライバーのコンストラクターや subscribe() では外部通信などの副作用を避けます。

自動検出との二重登録を避ける

利用者が withEvents(discover: [...]) でパッケージのリスナーディレクトリを追加し、パッケージのプロバイダーも同じリスナーを明示登録すると、1つのイベントで処理が複数回動く可能性があります。 Dispatcher::listen() は通常のイベントに対して配列への追加を行い、Dispatcher全体で同じクラス名を自動的に重複排除しません。Foundationの EventServiceProvider が自身のマッピング内で行う array_unique() も、別のプロバイダーによる明示登録までまとめる仕組みではありません。 登録の責任を一方に決めます。
  • パッケージが標準リスナーを登録する場合、利用者にそのディレクトリの自動検出追加を要求しません。
  • 利用者が必要なリスナーを選ぶ場合、パッケージはイベントとリスナークラスを提供し、同じリスナーを標準で登録しません。
  • 自動検出から明示登録へ移行する場合、利用者側の検出設定や古いイベントキャッシュも更新対象として案内します。
業務処理の冪等性も重要ですが、二重登録を残したまま冪等性だけで隠すのではなく、登録経路を整理してください。

更新前後の動作を検証する

登録状態を調べるには、パッケージを導入した検証用アプリケーションで event:list を実行します。キャッシュなし・ありの両方で対象のリスナーが存在することを確認します。
これは検証用アプリでの手順です。event:list の表示だけでは、リスナーが処理を完了することや1回だけ実行されることまでは保証できません。実際のイベント発火を使うテストも追加します。 例として、TestbenchのベースTestCaseで CourierServiceProvider を登録したうえで、ロガーが1回だけ呼ばれることを検証します。
Event::fake() はイベントの発火確認には便利ですが、通常は本物のリスナーの実行を止めます。このテストでは登録と処理を検証するため使用しません。自動検出との組み合わせやキャッシュありの起動も、別の検証用アプリ・新しいアプリケーションインスタンスで確認します。
起動後に event:cache を実行しても、そのプロセス内に登録済みのDispatcherがキャッシュから再構築されるわけではありません。キャッシュありの動作確認は、新しい起動で行ってください。更新したリスナーを長時間動くワーカーへ反映するには、通常のデプロイ手順に沿った再起動も必要です。

長期保守のチェックリスト

  • イベントクラス名、公開プロパティ、リスナーの登録方針を利用者向けの契約として文書化する。
  • 利用者が購読するイベントの名前やペイロードを変えるときは、バージョン互換性管理に沿って変更を案内する。
  • 自動検出や $listen に変更がある更新では、event:cache の再作成をデプロイ手順へ含める。
  • パッケージの標準リスナーと利用者の独自リスナーを区別し、同じ標準リスナーを二重に登録させない。
  • PHP・Laravelの対応バージョンごとに、リスナーが1回だけ実行されるテストを継続する。

参照した一次情報

最終更新日 2026年10月13日