【Laravel】イベント(Event)とリスナー(Listener)の使い方|疎結合な設計パターン
Laravel アプリケーションが複雑になるにつれ、「ある処理が終わったら別の処理を動かす」という連鎖をコントローラや Service クラス内に直接書いてしまいがちです。Event / Listener の仕組みを使うと、この依存関係を逆転させ、呼び出し側と処理側を切り離せます。本記事では実務でよく使うパターンを中心に整理します。
なお、挙動の細部はバージョンによって異なる場合があります。コード例は執筆時点で Laravel 10 を主な参照先とし、Laravel 11 以降での変更点にはその都度補足します。手元の composer.json の laravel/framework に合わせ、公式ドキュメントの Events 章を並行して参照することをおすすめします。
はじめに
「ユーザー登録が完了したら、ウェルカムメールを送信し、さらに分析イベントを記録する」という要件を追加する場面を考えます。コントローラで $mailer->send(...) と $analytics->record(...) を直接呼ぶと問題が起きます。コントローラがメール送信・分析記録の両方に依存するため、テストや変更の際に双方を意識しなければなりません。
Laravel の Event / Listener は、「何かが起きた(Event)」という事実を発火し、「それに反応する処理(Listener)」を別のクラスに分離する仕組みです。発火元はリスナーの存在を知らなくてよいため、クラス間の依存が薄くなります。
この記事では次の範囲を扱います。
- Event・Listener の作成と登録
- Event のディスパッチ
- Queueable Listener によるバックグラウンド実行
- よくある失敗とその対処
背景:Event と Listener の関係
Event は「何が起きたか」を表すデータクラスです。Listener は「その Event に反応して何をするか」を定義するクラスです。
| 役割 | クラス例 | 主なメソッド |
|---|---|---|
| Event | UserRegistered | なし(コンストラクタでデータを保持) |
| Listener | SendWelcomeEmail | handle(UserRegistered $event) |
1 つの Event に複数の Listener を登録でき、それぞれが独立して動作します。発火元のコードは event(new UserRegistered($user)) と書くだけで、どの Listener が動くかを意識しません。
実装と検証
Event と Listener の生成
Artisan コマンドでクラスを生成します。
php artisan make:event UserRegistered
php artisan make:listener SendWelcomeEmail --event=UserRegistered
生成された Event は、コンストラクタでデータを受け取る形になっています。
<?php
namespace App\\Events;
use App\\Models\\User;
use Illuminate\\Foundation\\Events\\Dispatchable;
use Illuminate\\Queue\\SerializesModels;
class UserRegistered
{
use Dispatchable, SerializesModels;
public function __construct(
public readonly User $user,
) {}
}
Dispatchable トレイトを使うと UserRegistered::dispatch($user) のような静的メソッドで Event を発火できます。SerializesModels トレイトは Eloquent モデルをキューに乗せる際のシリアライズ・デシリアライズを担当します。Queueable Listener と組み合わせる場合は必要です。
Listener の実装
Listener の handle メソッドが実際の処理を担います。引数の型宣言で受け取りたい Event クラスを指定します。
<?php
namespace App\\Listeners;
use App\\Events\\UserRegistered;
use App\\Mail\\WelcomeMail;
use Illuminate\\Support\\Facades\\Mail;
class SendWelcomeEmail
{
public function handle(UserRegistered $event): void
{
Mail::to($event->user)->send(new WelcomeMail($event->user));
}
}
Event と Listener の登録(Laravel 10 以前)
Laravel 10 以前では app/Providers/EventServiceProvider.php の $listen プロパティに対応を記述します。
protected $listen = [
UserRegistered::class => [
SendWelcomeEmail::class,
RecordAnalyticsEvent::class,
],
];
php artisan event:list で登録済みの Event と Listener の一覧を確認できます。
Event と Listener の登録(Laravel 11 以降)
Laravel 11 のデフォルト構成では EventServiceProvider がアプリケーションスケルトンから削除されました。代わりに app/Providers/AppServiceProvider.php の boot メソッドで登録するか、自動検出(Event Discovery)を利用します。
AppServiceProvider 内で登録する場合は次のように書きます。自動検出を使う場合は EventServiceProvider を新しく作成して shouldDiscoverEvents() を有効にする方法もあります。
use Illuminate\\Support\\Facades\\Event;
public function boot(): void
{
Event::listen(
UserRegistered::class,
SendWelcomeEmail::class,
);
}
バージョンごとの登録方法については 公式ドキュメントの Registering Events and Listeners 節 を参照してください。
Event のディスパッチ
コントローラやサービスから event ヘルパーまたは静的メソッドで発火します。
use App\\Events\\UserRegistered;
// ヘルパー関数を使う
event(new UserRegistered($user));
// Dispatchable トレイトが使えれば静的メソッドでも書ける
UserRegistered::dispatch($user);
Queueable Listener
処理に時間がかかる Listener はキューで非同期実行できます。ShouldQueue インターフェースを実装するだけで、ワーカーが処理を引き受けます。
<?php
namespace App\\Listeners;
use App\\Events\\UserRegistered;
use Illuminate\\Contracts\\Queue\\ShouldQueue;
class SendWelcomeEmail implements ShouldQueue
{
public function handle(UserRegistered $event): void
{
// ワーカーが非同期で実行する
}
}
ShouldQueue を実装した Listener は、キューに Job として載ります。キューの設定(ドライバ・接続)や queue:work の起動は Queues の章 に従ってください。
実装時の注意点
Event に大量のデータを持たせない
Event がキューに載る際はシリアライズされます。大きなコレクションやバイナリを直接持たせるとペイロードが肥大化します。識別子のみを渡してリスナー内で再取得する設計を検討してください。
Listener の失敗とリトライ
ShouldQueue を実装した Listener は Job と同様にリトライ設定($tries、$backoff)を持てます。failed メソッドで失敗時の処理を書くこともできます。
同期実行と非同期実行の切り替え
ShouldQueue を実装していない Listener は同期実行されます。テストでキューの動作を検証したい場合は Queue::fake() と Event::assertDispatched() を組み合わせます。
失敗例:EventServiceProvider への登録を忘れて Event が無反応になる
make:event と make:listener でクラスを生成しただけでは、Event と Listener はまだ結びついていません。EventServiceProvider の $listen 配列(または Laravel 11 以降の同等の手順)への追加を省略すると、リスナーは一切動きません。event(new UserRegistered($user)) を呼んでも無反応になります。
この状態はエラーにならず無音で失敗するため、気づきにくいです。php artisan event:list で目的のリスナーが表示されるか確認する習慣をつけると、この問題を早期に検出できます。
学び:疎結合がもたらす設計上のメリット
Event / Listener を導入すると、次の点で設計が整理しやすくなります。
呼び出し側の責務を絞れる
「ユーザー登録が完了した」という事実だけを発火し、メール送信・ログ記録・外部サービス連携を別の Listener に委ねられます。発火元のクラスが肥大化しにくくなります。
Listener の追加・削除が発火元に影響しない
新しい処理を追加したいときは Listener を作って登録するだけです。発火元のコードを変更する必要がありません。
テストしやすくなる
Event::fake() を使うとディスパッチの検証ができ、実際の Listener を動かさずに発火の有無を確認できます。
// テスト例
Event::fake();
// 登録処理を実行
$this->post('/register', [...]);
// Event が発火されたかを検証
Event::assertDispatched(UserRegistered::class, function ($event) use ($user) {
return $event->user->id === $user->id;
});
まとめ
- Event はデータを保持するクラス、Listener は処理を担うクラスである。
EventServiceProviderの$listen配列(または Laravel 11 以降の登録方法)で Event と Listener を結びつける。ShouldQueueを実装すると Listener をキューで非同期実行できる。- 登録忘れはエラーなしで無反応になるため、
php artisan event:listで確認する。
次に試せること
UserRegisteredのような具体的なドメインイベントを 1 つ作り、Listener を 2 つ以上登録して動作を確認する。ShouldQueueを実装した Listener でqueue:workとの連携を試す。- Laravel 公式ドキュメントの Event Subscribers 節 を読み、複数の Event を 1 つのクラスでまとめて扱う Event Subscriber パターンを把握する。




