あっぽログ
← 記事一覧に戻る

SymfonyのEventDispatcherを理解する:イベント駆動アーキテクチャの基本から実践まで

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を活用することで、機能追加のたびに既存コードを修正する必要がなくなり、オープン・クローズドの原則に沿った拡張しやすいアーキテクチャを実現できます。

← 記事一覧に戻る