以前の記事で Pub/Sub パターンの概念と C# での実装例を紹介しました。Laravel にはこのパターンをフレームワークレベルで実現する Event / Listener の仕組みが標準で備わっています。
この記事では、Pub/Sub の概念を Laravel に当てはめながら整理します。
Pub/Sub との対応関係
Pub/Sub パターンとの対応を先に確認しておきます。
| Pub/Sub の概念 | Laravel での対応 |
|---|---|
| Publisher(発行者) | Event::dispatch() を呼ぶ場所(Controller や Service など) |
| Broker(仲介者) | Laravel のイベントシステム(自動で仲介) |
| Subscriber(購読者) | Listener クラス |
| メッセージ | Event クラス |
自前で Broker を実装していた部分を Laravel が肩代わりしてくれる形です。
Event クラスの作成
artisan でひな形を生成します。
php artisan make:event OrderPlaced
生成されるクラスに、イベントが運ぶデータをプロパティとして持たせます。
namespace App\Events;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class OrderPlaced
{
use Dispatchable, SerializesModels;
public function __construct(
public readonly int $orderId,
public readonly string $productName,
) {}
}
Event クラスは純粋なデータコンテナです。ロジックは持ちません。
Listener クラスの作成
受け取り側も artisan で生成します。
php artisan make:listener SendOrderNotification --event=OrderPlaced
php artisan make:listener LogOrderActivity --event=OrderPlaced
handle() メソッドにイベントが渡されてきます。
namespace App\Listeners;
use App\Events\OrderPlaced;
class SendOrderNotification
{
public function handle(OrderPlaced $event): void
{
// メール送信などの処理
\Log::info("[Mail] 注文確認: {$event->productName} (ID: {$event->orderId})");
}
}
namespace App\Listeners;
use App\Events\OrderPlaced;
class LogOrderActivity
{
public function handle(OrderPlaced $event): void
{
\Log::info("[Log] 注文受付: {$event->productName} (ID: {$event->orderId})");
}
}
Event と Listener の登録
EventServiceProvider に対応関係を定義します。
namespace App\Providers;
use App\Events\OrderPlaced;
use App\Listeners\LogOrderActivity;
use App\Listeners\SendOrderNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider
{
protected $listen = [
OrderPlaced::class => [
SendOrderNotification::class,
LogOrderActivity::class,
],
];
}
1つのイベントに対して複数の Listener を登録できます。Publisher はここに何が登録されているかを知りません。
Event の発行
登録が済んだら、発行側のコードは非常にシンプルです。
use App\Events\OrderPlaced;
class OrderController extends Controller
{
public function store(StoreOrderRequest $request): RedirectResponse
{
$order = Order::create($request->validated());
// イベントを発行する
OrderPlaced::dispatch($order->id, $order->product_name);
return redirect()->route('orders.index');
}
}
dispatch() を呼ぶだけで、登録されているすべての Listener が動きます。Controller は Listener の存在を知りません。
非同期処理(キュー)への対応
Listener に ShouldQueue インターフェースを実装するだけで、そのリスナーの処理をキューに流せます。
use Illuminate\Contracts\Queue\ShouldQueue;
class SendOrderNotification implements ShouldQueue
{
public string $queue = 'notifications';
public function handle(OrderPlaced $event): void
{
// キューワーカーが非同期で処理する
}
}
メール送信や外部 API 呼び出しなど、レスポンスを遅らせたくない処理をキューに回す場合に使います。ShouldQueue をつけない Listener は同期実行されます。
まとめ
- Laravel の Event / Listener は Pub/Sub パターンの公式実装
- Event クラス → メッセージ(データコンテナ)
- Listener クラス → Subscriber(処理の担い手)
EventServiceProviderが Broker の役割を担い、対応関係を管理するShouldQueueを実装するだけで非同期処理に切り替えられる
Controller や Service に処理を直接書き続けると肥大化しやすいですが、Event / Listener を使うことで「何かが起きた → 必要な処理が動く」という形に整理できます。Pub/Sub の概念を知っていると、この仕組みがなぜこの形になっているのかが見えやすくなります。