EventDispatcherとは
アプリケーションが複雑になると、「ユーザー登録後にメールを送る」「注文完了後に在庫を更新する」といった処理が増えてきます。これらを1つのクラスに詰め込むと、コードが肥大化しメンテナンスが困難になります。
Symfonyの EventDispatcher コンポーネントは、こうした課題を解決するためのイベント駆動アーキテクチャを提供します。処理を「イベント」として発火し、それを受け取る「リスナー」に分離することで、疎結合な設計が実現できます。
インストール
Symfonyプロジェクトにまだ含まれていない場合は、Composerでインストールします。
composer require symfony/event-dispatcher
基本的な構成要素
EventDispatcherは以下の3つの要素で成り立っています。
| 要素 | 役割 |
|---|---|
| Event | 発生した出来事のデータを保持するクラス |
| Listener | イベントを受け取って処理を行うコールバック |
| EventDispatcher | イベントを発火し、リスナーへ届ける仲介役 |
イベントクラスを定義する
まずはイベントクラスを作成します。Eventを継承して、必要なデータをプロパティとして持たせます。
<?php
// src/Event/UserRegisteredEvent.php
namespace App\Event;
use Symfony\Contracts\EventDispatcher\Event;
class UserRegisteredEvent extends Event
{
public const NAME = 'user.registered';
public function __construct(
private readonly string $email,
private readonly string $username,
) {}
public function getEmail(): string
{
return $this->email;
}
public function getUsername(): string
{
return $this->username;
}
}
NAME 定数はイベントを識別する文字列です。後でリスナーを登録するときに使います。
リスナーを作成する
リスナーはイベントを受け取って処理を行うクラスです。
<?php
// src/EventListener/SendWelcomeEmailListener.php
namespace App\EventListener;
use App\Event\UserRegisteredEvent;
class SendWelcomeEmailListener
{
public function onUserRegistered(UserRegisteredEvent $event): void
{
// 実際はメール送信処理を行う
echo sprintf(
"ウェルカムメールを送信しました: %s <%s>\n",
$event->getUsername(),
$event->getEmail()
);
}
}
メソッド名は自由ですが、on + イベント名の形にするのが慣習です。
EventDispatcherにリスナーを登録して発火する
<?php
// index.php
use App\Event\UserRegisteredEvent;
use App\EventListener\SendWelcomeEmailListener;
use Symfony\Component\EventDispatcher\EventDispatcher;
$dispatcher = new EventDispatcher();
$listener = new SendWelcomeEmailListener();
// リスナーを登録(イベント名・コールバック・優先度)
$dispatcher->addListener(
UserRegisteredEvent::NAME,
[$listener, 'onUserRegistered'],
priority: 0
);
// イベントを発火
$event = new UserRegisteredEvent('alice@example.com', 'Alice');
$dispatcher->dispatch($event, UserRegisteredEvent::NAME);
実行結果:
ウェルカムメールを送信しました: Alice <alice@example.com>
サブスクライバーを使う
リスナーが増えると、登録処理があちこちに散らばりがちです。EventSubscriber を使うと、1つのクラスに複数のリスナーをまとめられます。
<?php
// src/EventSubscriber/UserSubscriber.php
namespace App\EventSubscriber;
use App\Event\UserRegisteredEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class UserSubscriber implements EventSubscriberInterface
{
// このサブスクライバーが購読するイベントを返す
public static function getSubscribedEvents(): array
{
return [
UserRegisteredEvent::NAME => [
['sendWelcomeEmail', 10], // 優先度10(高い)
['logRegistration', 0], // 優先度0(低い)
],
];
}
public function sendWelcomeEmail(UserRegisteredEvent $event): void
{
echo "メール送信: {$event->getEmail()}\n";
}
public function logRegistration(UserRegisteredEvent $event): void
{
echo "ログ記録: {$event->getUsername()} が登録しました\n";
}
}
サブスクライバーの登録は addSubscriber 1行で完結します。
$dispatcher = new EventDispatcher();
$dispatcher->addSubscriber(new UserSubscriber());
$event = new UserRegisteredEvent('bob@example.com', 'Bob');
$dispatcher->dispatch($event, UserRegisteredEvent::NAME);
実行結果:
メール送信: bob@example.com
ログ記録: Bob が登録しました
優先度の数値が大きいほど先に実行されます。
イベントの伝播を止める
特定の条件でそれ以降のリスナーを実行したくない場合は、stopPropagation() を呼び出します。
public function sendWelcomeEmail(UserRegisteredEvent $event): void
{
if ($event->getEmail() === '') {
// メールアドレスが空なら以降のリスナーをスキップ
$event->stopPropagation();
return;
}
echo "メール送信: {$event->getEmail()}\n";
}
Symfonyフレームワークでの活用
Symfonyフルスタック環境では、services.yaml にタグを付けるだけで自動登録されます。
# config/services.yaml
services:
App\EventSubscriber\UserSubscriber:
tags:
- { name: kernel.event_subscriber }
autoconfigure: true が有効な場合、EventSubscriberInterface を実装していれば自動的にタグが付くため、明示的な記述すら不要です。
まとめ
- Event でデータを定義し、Listener/Subscriber で処理を分離する
addListenerで個別登録、addSubscriberでまとめて登録- 優先度でリスナーの実行順を制御できる
stopPropagation()でイベント伝播を途中で止められる
EventDispatcherを活用することで、機能追加のたびに既存コードを修正する必要がなくなり、オープン・クローズドの原則に沿った拡張しやすいアーキテクチャを実現できます。