# 機能設計書 12-EventDispatcher

## 概要

本ドキュメントは、Symfony EventDispatcherコンポーネントの機能設計を記述する。EventDispatcherは、イベントのディスパッチとリスニングによるコンポーネント間通信機能を提供し、PSR-14準拠のイベントディスパッチャーとして動作する。

### 本機能の処理概要

**業務上の目的・背景**：アプリケーション内の各コンポーネントを疎結合に保ちながら、特定のタイミングで横断的な処理を実行する必要がある。EventDispatcherは、Observerパターンとメディエーターパターンを組み合わせ、イベント駆動アーキテクチャを実現する。HttpKernelのリクエスト処理パイプライン、セキュリティ認証フロー、フォームイベント等、Symfonyの中核的な拡張ポイントを提供する。

**機能の利用シーン**：HTTPリクエスト処理のライフサイクル（kernel.request、kernel.response等）、コンソールコマンドの実行前後、セキュリティ認証時、カスタムビジネスイベントの発火・ハンドリングに利用される。

**主要な処理内容**：
1. イベントリスナーの登録と優先度管理（addListener、addSubscriber）
2. イベントのディスパッチと伝搬制御（dispatch、StoppableEventInterface対応）
3. リスナーの優先度順ソートと最適化（sortListeners、optimizeListeners）
4. イベントサブスクライバーによる一括登録（EventSubscriberInterface）
5. リスナーの削除とイベント存在チェック（removeListener、hasListeners）

**関連システム・外部連携**：HttpKernel（リクエスト処理パイプライン）、SecurityBundle（認証イベント）、Console（コマンドイベント）、Form（フォームイベント）等、Symfonyのほぼすべてのコンポーネントと連携する。PSR-14 EventDispatcherInterfaceに準拠する。

**権限による制御**：EventDispatcher自体は権限制御を行わないが、SecurityBundleのイベントリスナーを通じて間接的にアクセス制御に関与する。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 19 | イベントパネル | 主機能 | EventDispatcherコンポーネントで記録されたイベント情報の表示 |

## 機能種別

イベント駆動通信処理 / コンポーネント間メッセージング

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| event | object | Yes | ディスパッチするイベントオブジェクト | PSR-14準拠オブジェクト |
| eventName | string/null | No | イベント名（省略時はイベントクラス名） | 文字列 |
| listener | callable/array | Yes | イベントリスナー（callableまたは[object, method]） | 呼び出し可能であること |
| priority | int | No | リスナーの優先度（高い値ほど先に実行、デフォルト0） | 整数値 |
| subscriber | EventSubscriberInterface | Yes | イベントサブスクライバー | getSubscribedEventsを実装 |

### 入力データソース

アプリケーションコード、Bundleのサービス定義（DI経由のリスナー/サブスクライバー登録）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| event | object | ディスパッチ後のイベントオブジェクト（リスナーにより変更される可能性あり） |
| listeners | callable[] | 指定イベント名に登録されたリスナーの優先度順配列 |
| priority | int/null | 指定リスナーの優先度（未登録時null） |
| hasListeners | bool | 指定イベントにリスナーが登録されているか |

### 出力先

呼び出し元への返却値。イベントオブジェクトの状態変更を通じたコンポーネント間の暗黙的なデータ受け渡し。

## 処理フロー

### 処理シーケンス

```
1. イベントディスパッチ要求（dispatch）
   └─ イベント名がnullの場合、イベントオブジェクトのクラス名をイベント名として使用
2. リスナー取得（optimizeListeners / getListeners）
   └─ 最適化済みリスナー配列が存在すればそれを使用、なければ構築
3. リスナーソート（sortListeners / optimizeListeners）
   └─ 優先度による降順ソート（krsort）、Closureベースの遅延解決
4. リスナー実行（callListeners）
   └─ 各リスナーを順次呼び出し、StoppableEvent対応の伝搬停止チェック
5. イベントオブジェクト返却
   └─ リスナーにより変更されたイベントオブジェクトを返却
```

### フローチャート

```mermaid
flowchart TD
    A[dispatch呼び出し] --> B{eventName指定?}
    B -->|No| C[event::classをイベント名に使用]
    B -->|Yes| D[指定イベント名を使用]
    C --> E{最適化済みリスナーあり?}
    D --> E
    E -->|Yes| F[最適化済みリスナー取得]
    E -->|No| G{リスナー登録あり?}
    G -->|No| H[空配列]
    G -->|Yes| I[optimizeListeners実行]
    I --> F
    F --> J[callListeners実行]
    H --> K[イベントオブジェクト返却]
    J --> L{StoppableEvent?}
    L -->|Yes| M{伝搬停止?}
    M -->|Yes| K
    M -->|No| N[次のリスナー実行]
    L -->|No| N
    N --> J
    J --> K
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-12-01 | 優先度ソート | リスナーは優先度の降順（高い値が先）で実行される | dispatch実行時 |
| BR-12-02 | 伝搬停止 | StoppableEventInterfaceを実装したイベントでisPropagationStopped()がtrueの場合、後続リスナーはスキップ | callListeners実行中 |
| BR-12-03 | 遅延解決 | Closureベースのリスナーは最初の呼び出し時に解決（遅延ロード） | addListener/リスナー実行時 |
| BR-12-04 | サブスクライバー登録 | EventSubscriberInterface::getSubscribedEventsの戻り値に基づき、複数イベント・優先度で一括登録 | addSubscriber実行時 |

### 計算ロジック

リスナーソート: PHP組み込みのkrsort（キーの降順ソート）を使用。優先度がキー、リスナー配列が値の連想配列を降順にソートし、フラット化する。

## データベース操作仕様

### 操作別データベース影響一覧

本コンポーネントはデータベース操作を行わない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | 該当なし | EventDispatcher自体は例外をスローしない | リスナー内の例外は呼び出し元に伝搬する |

### リトライ仕様

リトライは行わない。リスナーの例外は呼び出し元にそのまま伝搬する。

## トランザクション仕様

トランザクション管理は行わない。リスナーの実行は同期的であり、各リスナーの処理が完了してから次のリスナーが実行される。

## パフォーマンス要件

- 最適化モード（EventDispatcherクラス直接使用時）: Closureベースの最適化済みリスナー配列を使用し、メソッド呼び出しのオーバーヘッドを削減
- 遅延解決: Closureラッパーによるリスナーの遅延インスタンス化で、未使用リスナーのメモリ消費を抑制
- キャッシュ: ソート済みリスナー配列はイベント名ごとにキャッシュされ、リスナー追加/削除時にのみ再構築

## セキュリティ考慮事項

- リスナーの登録は信頼されたコード（通常はDIコンテナ経由）から行われることを前提とする
- 任意のcallableが登録可能なため、サービスコンテナ経由での登録を推奨

## 備考

- ImmutableEventDispatcherラッパーにより、リスナーの追加/削除を禁止した読み取り専用ディスパッチャーを構築可能
- GenericEventクラスにより、専用イベントクラスを作成せずにイベントデータを受け渡し可能
- Debug名前空間にWrappedListenerがあり、プロファイリング用のリスナーラッピングを提供

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | EventDispatcherInterface.php | `src/Symfony/Component/EventDispatcher/EventDispatcherInterface.php` | Symfony固有のディスパッチャーインターフェース。addListener、addSubscriber、getListeners等の拡張メソッド定義 |
| 1-2 | EventSubscriberInterface.php | `src/Symfony/Component/EventDispatcher/EventSubscriberInterface.php` | サブスクライバーの契約。getSubscribedEventsの戻り値形式（3パターン） |
| 1-3 | GenericEvent.php | `src/Symfony/Component/EventDispatcher/GenericEvent.php` | 汎用イベント。subject+arguments構造、ArrayAccess/IteratorAggregate実装 |

**読解のコツ**: EventDispatcherInterfaceはPSR-14のEventDispatcherInterfaceを拡張している。PSR-14はdispatch()のみ定義するが、Symfony版はリスナー管理メソッドを追加している。

#### Step 2: エントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | EventDispatcher.php | `src/Symfony/Component/EventDispatcher/EventDispatcher.php` | 中核実装。listeners、sorted、optimizedの3つの内部配列の関係を理解する |

**主要処理フロー**:
1. **45-60行目**: dispatch()メソッド。eventNameの決定、最適化リスナーの取得、callListeners呼び出し
2. **126-130行目**: addListener()メソッド。priority付きでlisteners配列に追加、sorted/optimizedキャッシュを無効化
3. **160-173行目**: addSubscriber()メソッド。getSubscribedEventsの3つの戻り値パターンに対応
4. **198-208行目**: callListeners()メソッド。StoppableEvent対応の伝搬停止チェックを含むリスナー順次実行
5. **213-227行目**: sortListeners()メソッド。krsortによる優先度降順ソート、Closureの遅延解決
6. **232-255行目**: optimizeListeners()メソッド。Closureラッパーによる最適化リスナー配列の構築

#### Step 3: 補助クラスを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | ImmutableEventDispatcher.php | `src/Symfony/Component/EventDispatcher/ImmutableEventDispatcher.php` | 読み取り専用ラッパー |
| 3-2 | GenericEvent.php | `src/Symfony/Component/EventDispatcher/GenericEvent.php` | 汎用イベントクラス |

**主要処理フロー**:
- **34-38行目（GenericEvent）**: コンストラクタ。subjectとargumentsを受け取る
- **109-154行目（GenericEvent）**: ArrayAccess実装による配列風アクセス

### プログラム呼び出し階層図

```
EventDispatcher::dispatch()
    │
    ├─ optimizeListeners() / getListeners()
    │      └─ sortListeners()
    │             └─ krsort()（優先度降順ソート）
    │
    └─ callListeners()
           ├─ StoppableEventInterface::isPropagationStopped()
           └─ listener($event, $eventName, $this)

EventDispatcher::addSubscriber()
    │
    └─ EventSubscriberInterface::getSubscribedEvents()
           └─ addListener()（各イベント・優先度で登録）
```

### データフロー図

```
[入力]                    [処理]                           [出力]

イベントオブジェクト ───▶ EventDispatcher::dispatch()  ───▶ 変更済みイベントオブジェクト
                              │
                              ├─ リスナー取得・ソート
                              │
                              └─ リスナー順次実行
                                    │
                                    ├─ listener1($event)
                                    ├─ listener2($event)
                                    └─ listenerN($event)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| EventDispatcher.php | `src/Symfony/Component/EventDispatcher/EventDispatcher.php` | ソース | イベントディスパッチャー中核実装 |
| EventDispatcherInterface.php | `src/Symfony/Component/EventDispatcher/EventDispatcherInterface.php` | ソース | Symfony拡張インターフェース |
| EventSubscriberInterface.php | `src/Symfony/Component/EventDispatcher/EventSubscriberInterface.php` | ソース | サブスクライバーインターフェース |
| GenericEvent.php | `src/Symfony/Component/EventDispatcher/GenericEvent.php` | ソース | 汎用イベントクラス |
| ImmutableEventDispatcher.php | `src/Symfony/Component/EventDispatcher/ImmutableEventDispatcher.php` | ソース | 読み取り専用ラッパー |
| Attribute/ | `src/Symfony/Component/EventDispatcher/Attribute/` | ソース | イベントリスナーアトリビュート |
| Debug/ | `src/Symfony/Component/EventDispatcher/Debug/` | ソース | デバッグ用ラッパー（WrappedListener等） |
| DependencyInjection/ | `src/Symfony/Component/EventDispatcher/DependencyInjection/` | ソース | DI統合（コンパイラパス等） |
