vendor 内のディレクトリを自動検出に追加しなくても動く構成にすると、インストール・更新・キャッシュ作成時の前提を減らせます。
このページではLaravel Framework v13.36.0 の実装を確認し、イベントの基本操作をパッケージの長期保守に適用します。プロバイダーの読み込みはパッケージの自動検出、テスト環境はパッケージのテストを参照してください。
パッケージのリスナーを明示登録する
パッケージが標準で実行する処理は、通常のIlluminate\Support\ServiceProvider の boot() で Event::listen() に登録できます。Laravelの既定のリスナー自動検出先はアプリケーションの app/Listeners であり、パッケージ内の src/Listeners まで自動で走査するわけではありません。
例として、配送完了イベントをログに記録するパッケージを作ります。イベントは通知に必要な値を持つだけのクラスです。
handle() でイベントを処理します。
extra.laravel.providers または利用者による明示登録を用意してください。イベントがHTTP処理でもキューワーカーでも必要なら、runningInConsole() で登録を制限しません。
登録とインスタンス生成は別のタイミング
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 のキャッシュだけを見て、これらの登録まで省略されると判断しないでください。
自動検出との二重登録を避ける
利用者がwithEvents(discover: [...]) でパッケージのリスナーディレクトリを追加し、パッケージのプロバイダーも同じリスナーを明示登録すると、1つのイベントで処理が複数回動く可能性があります。
Dispatcher::listen() は通常のイベントに対して配列への追加を行い、Dispatcher全体で同じクラス名を自動的に重複排除しません。Foundationの EventServiceProvider が自身のマッピング内で行う array_unique() も、別のプロバイダーによる明示登録までまとめる仕組みではありません。
登録の責任を一方に決めます。
- パッケージが標準リスナーを登録する場合、利用者にそのディレクトリの自動検出追加を要求しません。
- 利用者が必要なリスナーを選ぶ場合、パッケージはイベントとリスナークラスを提供し、同じリスナーを標準で登録しません。
- 自動検出から明示登録へ移行する場合、利用者側の検出設定や古いイベントキャッシュも更新対象として案内します。
更新前後の動作を検証する
登録状態を調べるには、パッケージを導入した検証用アプリケーションでevent:list を実行します。キャッシュなし・ありの両方で対象のリスナーが存在することを確認します。
event:list の表示だけでは、リスナーが処理を完了することや1回だけ実行されることまでは保証できません。実際のイベント発火を使うテストも追加します。
例として、TestbenchのベースTestCaseで CourierServiceProvider を登録したうえで、ロガーが1回だけ呼ばれることを検証します。
Event::fake() はイベントの発火確認には便利ですが、通常は本物のリスナーの実行を止めます。このテストでは登録と処理を検証するため使用しません。自動検出との組み合わせやキャッシュありの起動も、別の検証用アプリ・新しいアプリケーションインスタンスで確認します。
長期保守のチェックリスト
- イベントクラス名、公開プロパティ、リスナーの登録方針を利用者向けの契約として文書化する。
- 利用者が購読するイベントの名前やペイロードを変えるときは、バージョン互換性管理に沿って変更を案内する。
- 自動検出や
$listenに変更がある更新では、event:cacheの再作成をデプロイ手順へ含める。 - パッケージの標準リスナーと利用者の独自リスナーを区別し、同じ標準リスナーを二重に登録させない。
- PHP・Laravelの対応バージョンごとに、リスナーが1回だけ実行されるテストを継続する。