# 機能設計書 31-イベントディスパッチャー

## 概要

本ドキュメントは、Laravel Frameworkにおけるイベントディスパッチャー（Event Dispatcher）機能の設計書である。イベント駆動アーキテクチャを実現するための中核コンポーネントであり、アプリケーション内の疎結合な通信を可能にする。

### 本機能の処理概要

**業務上の目的・背景**：
アプリケーション開発において、モジュール間の依存関係を最小限に抑えながら、状態変化や重要なアクションを他のコンポーネントに通知する必要がある。イベントディスパッチャーは、パブリッシャー/サブスクライバーパターンを実装し、イベント発生元と処理ハンドラ間の疎結合を実現する。これにより、機能追加時の既存コードへの影響を最小化し、保守性の高いアプリケーション設計が可能となる。

**機能の利用シーン**：
- ユーザー登録完了後のウェルカムメール送信
- 注文確定時の在庫更新と通知送信
- ログイン成功/失敗時のセキュリティログ記録
- キャッシュクリア時の関連処理の実行
- モデル保存時の監査ログ記録

**主要な処理内容**：
1. イベントリスナーの登録（`listen`メソッドによるリスナー登録、クロージャまたはクラスベース）
2. イベントサブスクライバーの登録（`subscribe`メソッドによる複数イベントの一括登録）
3. イベントのディスパッチ（`dispatch`メソッドによるイベント発火と全リスナーへの通知）
4. キューイング対応（`ShouldQueue`インターフェース実装リスナーの非同期実行）
5. ワイルドカードリスナー（`*`を使用したパターンマッチングによるイベント購読）
6. イベント遅延実行（`defer`メソッドによるイベント発火の遅延）
7. トランザクション連携（`ShouldDispatchAfterCommit`によるDB確定後のイベント発火）

**関連システム・外部連携**：
- キューシステム（Queue）: 非同期リスナーのジョブ投入
- ブロードキャストシステム（Broadcast）: リアルタイムイベント配信
- データベーストランザクションマネージャー: トランザクション確定後のイベント発火制御

**権限による制御**：
イベントディスパッチャー自体は権限制御を行わない。リスナー内部で必要に応じて認可チェックを実装する。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | 本機能はバックエンド処理のため、直接関連する画面はない |

## 機能種別

イベント処理 / データ連携 / 非同期処理管理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| events | string\|object\|array | Yes | イベント名またはイベントオブジェクト | - |
| listener | Closure\|string\|array\|null | No | イベントリスナー（クロージャ、クラス名、配列） | - |
| payload | mixed | No | イベントに渡すペイロードデータ | - |
| halt | bool | No | 最初の非nullレスポンスで停止するか | デフォルト: false |

### 入力データソース

- アプリケーションコードからの直接呼び出し
- サービスプロバイダーでのリスナー登録
- EventServiceProvider設定ファイル

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| responses | array\|null | 各リスナーからの戻り値の配列（halt=trueの場合は単一値） |

### 出力先

- リスナーへのイベント配信
- キューへのジョブ投入（非同期リスナーの場合）
- ブロードキャストチャネル（ShouldBroadcast実装の場合）

## 処理フロー

### 処理シーケンス

```
1. イベント発火（dispatch呼び出し）
   └─ イベントオブジェクトまたはイベント名とペイロードを受け取る

2. イベント解析
   └─ オブジェクトの場合はクラス名をイベント名として抽出
   └─ ペイロードを配列形式に正規化

3. 遅延チェック
   └─ deferringEvents=trueの場合、deferredEventsに追加して終了

4. トランザクション確定後実行チェック
   └─ ShouldDispatchAfterCommit実装の場合、トランザクションコールバックに登録

5. リスナー取得
   └─ 登録済みリスナーの収集
   └─ ワイルドカードリスナーのマッチング
   └─ インターフェースリスナーの追加

6. ブロードキャスト判定
   └─ ShouldBroadcast実装の場合、ブロードキャストキューに投入

7. リスナー実行
   └─ 各リスナーを順次実行
   └─ ShouldQueue実装の場合はキューにジョブ投入
   └─ halt=trueで非nullレスポンスの場合は即座に返却
   └─ リスナーがfalseを返した場合は伝播停止

8. レスポンス返却
   └─ 全リスナーのレスポンスを配列で返却
```

### フローチャート

```mermaid
flowchart TD
    A[dispatch呼び出し] --> B{イベントはオブジェクト?}
    B -->|Yes| C[クラス名をイベント名に]
    B -->|No| D[イベント名とペイロード分離]
    C --> E{遅延モード?}
    D --> E
    E -->|Yes| F[deferredEventsに追加]
    F --> G[null返却]
    E -->|No| H{AfterCommit?}
    H -->|Yes| I[トランザクションコールバック登録]
    I --> G
    H -->|No| J[リスナー取得]
    J --> K{ブロードキャスト?}
    K -->|Yes| L[ブロードキャストキュー投入]
    K -->|No| M[リスナーループ開始]
    L --> M
    M --> N{キュー対応リスナー?}
    N -->|Yes| O[キューにジョブ投入]
    N -->|No| P[同期実行]
    O --> Q{次のリスナー?}
    P --> R{halt & 非null?}
    R -->|Yes| S[レスポンス返却]
    R -->|No| T{false返却?}
    T -->|Yes| U[ループ終了]
    T -->|No| Q
    Q -->|Yes| M
    Q -->|No| V[全レスポンス返却]
    U --> V
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-31-01 | リスナー実行順序 | リスナーは登録順に実行される | 常時 |
| BR-31-02 | 伝播停止 | リスナーがfalseを返すとイベント伝播が停止する | リスナーがfalseを返した場合 |
| BR-31-03 | halt動作 | halt=trueの場合、最初の非nullレスポンスで処理終了 | dispatch第3引数がtrue |
| BR-31-04 | キューイング | ShouldQueue実装リスナーは非同期実行される | リスナークラスがShouldQueue実装 |
| BR-31-05 | ユニーク制約 | ShouldBeUnique実装リスナーは重複実行を防止 | リスナーがShouldBeUnique実装 |

### 計算ロジック

該当なし

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

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

本機能自体はデータベース操作を行わない。ただし、キューイングされたリスナーのジョブ情報はキューテーブルに保存される。

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| キュージョブ登録 | jobs | INSERT | 非同期リスナーのジョブ情報登録 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | Exception | リスナー内で例外発生 | 例外は伝播される。必要に応じてリスナー内でtry-catch |
| - | RuntimeException | ハッシュ関数未対応 | 適切なアルゴリズムを指定 |

### リトライ仕様

キューイングされたリスナーは、設定されたリトライ回数（`$tries`）およびリトライ間隔（`$backoff`）に従ってリトライされる。

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

- `ShouldDispatchAfterCommit`を実装したイベントは、データベーストランザクションが正常にコミットされた後にのみディスパッチされる
- `ShouldHandleEventsAfterCommit`を実装したリスナーは、トランザクションコミット後に実行される
- トランザクションがロールバックされた場合、上記のイベント/リスナーは実行されない

## パフォーマンス要件

- ワイルドカードリスナーのマッチング結果はキャッシュされる（`$wildcardsCache`）
- 同期リスナーは即座に実行されるため、重い処理は非同期化を推奨
- 大量のリスナーがある場合、登録順序による実行時間の考慮が必要

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

- リスナークラス名はサニタイズせずにコンテナから解決されるため、信頼できないソースからのイベント名は使用しない
- キューイングされるイベントデータはシリアライズされるため、機密情報の取り扱いに注意
- `ShouldBeEncrypted`実装リスナーはペイロードが暗号化される

## 備考

- Macroableトレイトにより、カスタムメソッドを動的に追加可能
- `NullDispatcher`クラスを使用してテスト時にイベント発火を抑制可能

---

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

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

### 推奨読解順序

#### Step 1: インターフェースを理解する

まず、Dispatcherの契約（インターフェース）を理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | Dispatcher.php | `src/Illuminate/Contracts/Events/Dispatcher.php` | イベントディスパッチャーが提供すべきメソッドの定義を確認 |

**読解のコツ**: インターフェースには`listen`, `hasListeners`, `subscribe`, `until`, `dispatch`, `push`, `flush`, `forget`, `forgetPushed`の9つのメソッドが定義されている。各メソッドのPHPDocを読み、期待される動作を把握する。

#### Step 2: サービスプロバイダーを理解する

サービスコンテナへの登録方法を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | EventServiceProvider.php | `src/Illuminate/Events/EventServiceProvider.php` | Dispatcherのシングルトン登録と初期設定 |

**主要処理フロー**:
- **17-25行目**: `events`としてDispatcherをシングルトン登録
- **18行目**: QueueResolverの設定
- **20-23行目**: TransactionManagerResolverの設定

#### Step 3: Dispatcherクラスの構造を理解する

メインの実装クラスを詳細に読み解く。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | Dispatcher.php | `src/Illuminate/Events/Dispatcher.php` | 全体構造、プロパティ定義 |

**主要処理フロー**:
- **46行目**: `$listeners`配列 - イベント名をキーとしたリスナー格納
- **53行目**: `$wildcards`配列 - ワイルドカードパターンのリスナー格納
- **60行目**: `$wildcardsCache`配列 - マッチング結果のキャッシュ
- **114-137行目**: `listen()`メソッド - リスナー登録のエントリーポイント
- **270-300行目**: `dispatch()`メソッド - イベント発火の中核処理
- **310-339行目**: `invokeListeners()`メソッド - リスナーの実際の実行
- **400-410行目**: `getListeners()`メソッド - イベントに対応するリスナー取得
- **477-494行目**: `makeListener()`メソッド - リスナーのクロージャ化
- **580-591行目**: `createQueuedHandlerCallable()` - キュー用コールバック生成

**読解のコツ**: Laravelのイベントシステムは、文字列ベースのイベント名とオブジェクトベースのイベントの両方をサポートしている。`parseEventAndPayload`メソッド（348-355行目）でこの変換が行われる。

#### Step 4: キューイング関連を理解する

非同期リスナーの仕組みを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | CallQueuedListener.php | `src/Illuminate/Events/CallQueuedListener.php` | キューに投入されるジョブクラス |

**主要処理フロー**:
- **113-118行目**: コンストラクタ - クラス名、メソッド名、データを保持
- **126-135行目**: `handle()`メソッド - キューワーカーからの呼び出しエントリーポイント
- **209-220行目**: `failed()`メソッド - 失敗時のコールバック処理

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

```
Event::dispatch($event)
    │
    ├─ Dispatcher::dispatch($event, $payload, $halt)
    │      │
    │      ├─ parseEventAndPayload() ... イベント名とペイロードの解析
    │      │
    │      ├─ shouldDeferEvent() ... 遅延チェック
    │      │
    │      ├─ ShouldDispatchAfterCommit判定
    │      │      └─ TransactionManager::addCallback()
    │      │
    │      └─ invokeListeners()
    │             │
    │             ├─ shouldBroadcast() ... ブロードキャスト判定
    │             │      └─ broadcastEvent()
    │             │
    │             ├─ getListeners() ... リスナー取得
    │             │      ├─ prepareListeners()
    │             │      ├─ getWildcardListeners()
    │             │      └─ addInterfaceListeners()
    │             │
    │             └─ リスナー実行ループ
    │                    ├─ makeListener() ... クロージャ化
    │                    └─ createClassListener()
    │                           └─ createClassCallable()
    │                                  ├─ handlerShouldBeQueued()
    │                                  │      └─ createQueuedHandlerCallable()
    │                                  │             └─ queueHandler()
    │                                  │                    └─ CallQueuedListener作成
    │                                  └─ handlerShouldBeDispatchedAfterDatabaseTransactions()
    │
    └─ Event::subscribe($subscriber)
           └─ Dispatcher::subscribe($subscriber)
                  └─ $subscriber->subscribe($dispatcher)
```

### データフロー図

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

イベント名/オブジェクト    Dispatcher::dispatch()
       │                        │
       ├──────────────────────▶ parseEventAndPayload()
       │                        │
ペイロード ──────────────────▶ │
                               │
                         getListeners()
                               │
                         ┌─────┴─────┐
                         │           │
                    同期リスナー  非同期リスナー
                         │           │
                    直接実行    queueHandler()
                         │           │
                         │     CallQueuedListener
                         │           │
                         │      キュー投入
                         │           │
                    レスポンス配列 ◀─┴─────────▶ ジョブテーブル
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| Dispatcher.php | `src/Illuminate/Events/Dispatcher.php` | ソース | イベントディスパッチャーの主実装 |
| EventServiceProvider.php | `src/Illuminate/Events/EventServiceProvider.php` | ソース | サービスプロバイダー |
| Dispatcher.php | `src/Illuminate/Contracts/Events/Dispatcher.php` | インターフェース | 契約定義 |
| CallQueuedListener.php | `src/Illuminate/Events/CallQueuedListener.php` | ソース | キューイング用ジョブクラス |
| QueuedClosure.php | `src/Illuminate/Events/QueuedClosure.php` | ソース | キュー対応クロージャラッパー |
| NullDispatcher.php | `src/Illuminate/Events/NullDispatcher.php` | ソース | テスト用Nullオブジェクト |
| InvokeQueuedClosure.php | `src/Illuminate/Events/InvokeQueuedClosure.php` | ソース | キュークロージャ実行 |
| ShouldDispatchAfterCommit.php | `src/Illuminate/Contracts/Events/ShouldDispatchAfterCommit.php` | インターフェース | トランザクション後実行マーカー |
| ShouldHandleEventsAfterCommit.php | `src/Illuminate/Contracts/Events/ShouldHandleEventsAfterCommit.php` | インターフェース | リスナーのトランザクション後実行マーカー |
