# 通知設計書 18-NotificationLoggerListener

## 概要

本ドキュメントは、Symfony Notifierコンポーネントにおける `NotificationLoggerListener` イベントリスナーの設計仕様を定義する。通知メッセージイベント（MessageEvent）をログとして記録するイベントサブスクライバーであり、NotificationEventsオブジェクトに全イベントを蓄積し、NotificationDataCollector等からアクセス可能にする。

### 本通知の処理概要

NotificationLoggerListenerは、Notifierコンポーネントが送信する全ての通知メッセージのイベントを記録するためのイベントサブスクライバーである。MessageEvent（Notifierコンポーネント）を最低優先度（-255）で購読し、NotificationEventsオブジェクトにイベントを蓄積する。蓄積されたイベントは、WebProfilerのDataCollectorやテスト用のアサーション等から参照される。

**業務上の目的・背景**：開発・デバッグにおいて、アプリケーションが送信した通知の一覧を把握することは重要である。NotificationLoggerListenerは、リクエスト中に送信された全通知メッセージのイベントを記録し、Symfony WebProfilerの通知パネルやテストでの通知送信確認に活用される。これにより、通知の可観測性（Observability）が大幅に向上し、開発効率とデバッグ効率が改善される。

**通知の送信タイミング**：NotifierコンポーネントのAbstractTransport::send()内でMessageEventがディスパッチされるたびに、NotificationLoggerListenerのonNotification()メソッドが呼び出される。最低優先度で購読するため、他の全てのリスナーが処理を完了した後に実行される。

**通知の受信者**：NotificationLoggerListener自体が受信者である。蓄積されたイベントデータは、NotificationDataCollector（WebProfiler用）や、テストコード内のアサーション機能を通じて間接的に参照される。

**通知内容の概要**：MessageEventオブジェクト（送信されたメッセージとキューイング状態）をNotificationEventsオブジェクトに蓄積する。トランスポート名でのフィルタリングも可能。

**期待されるアクション**：開発者がWebProfilerの通知パネルで送信された通知を確認する、テストコードでNotificationEventsを取得して通知送信を検証する等。

## 通知種別

イベントログ収集（Symfonyイベントサブスクライバーベース）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（EventSubscriberInterfaceによる購読） |
| 優先度 | 最低（-255） |
| リトライ | なし |

### 送信先決定ロジック

EventDispatcherに `MessageEvent::class` として登録される。getSubscribedEvents()で `['onNotification', -255]` として定義されており、他の全てのリスナーの後に実行される。

## 通知テンプレート

### メール通知の場合

本リスナーはメール通知ではなく、イベントを記録するリスナーであるため、メールテンプレートは適用されない。

### 本文テンプレート

```
該当なし（イベントリスナーとしてデータを蓄積するのみ）
```

### 添付ファイル

| ファイル名 | 形式 | 条件 | 説明 |
|----------|------|------|------|
| 該当なし | - | - | イベントリスナーのため添付ファイルなし |

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| event | 通知メッセージイベント | MessageEvent（Notifier） | Yes |
| events | 蓄積されたイベントコレクション | NotificationEvents | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| 通知メッセージ送信 | Notifier AbstractTransport::send()内のMessageEventディスパッチ | NotificationLoggerListenerがサービスとして登録されていること | 全てのNotifier通知送信時に発火 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| リスナー未登録 | NotificationLoggerListenerがEventDispatcherに登録されていない場合 |
| dev/test環境以外 | 通常、WebProfilerと共にdev/test環境でのみ有効化される |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[Notifier: 通知送信] --> B[AbstractTransport::send]
    B --> C[MessageEventディスパッチ]
    C --> D[他のリスナー処理（優先度 > -255）]
    D --> E[NotificationLoggerListener::onNotification]
    E --> F[NotificationEvents::add]
    F --> G[events配列にイベント追加]
    F --> H[transports配列にトランスポート名追加]
    G --> I[リクエスト終了時]
    H --> I
    I --> J[NotificationDataCollector::collect]
    J --> K[WebProfiler通知パネル表示]
```

## データベース参照・更新仕様

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| 該当なし | - | インメモリでイベントを蓄積するためDB参照なし |

### テーブル別参照項目詳細

該当なし。NotificationLoggerListenerはインメモリのNotificationEventsオブジェクトにイベントを蓄積する。

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| 該当なし | - | インメモリ蓄積のためDB更新なし |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| メモリ不足 | 大量の通知イベントが蓄積された場合 | ResetInterfaceのreset()でイベントコレクションをクリア |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | なし（ログ記録のため不要） |
| リトライ間隔 | - |
| リトライ対象エラー | - |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | なし |
| 1日あたり上限 | なし |

### 配信時間帯

制限なし。通知が送信されるたびにイベントが記録される。

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

- NotificationEventsにはメッセージオブジェクトが蓄積されるため、通知先情報（電話番号、メールアドレス等）が含まれる可能性がある。WebProfilerは通常dev環境でのみ有効化されるが、誤って本番環境で有効化しないよう注意すること
- ResetInterface実装によりリクエスト間でイベントがクリアされるため、前リクエストの情報がリークすることはない

## 備考

- NotificationLoggerListenerは `Symfony\Component\Notifier\EventListener\NotificationLoggerListener` に定義されている
- EventSubscriberInterfaceとResetInterfaceの両方を実装
- 購読イベント: `MessageEvent::class => ['onNotification', -255]` - 最低優先度で全てのリスナーの後に実行
- reset()メソッドでNotificationEventsを新規インスタンスで初期化（リクエスト間のクリーンアップ）
- NotificationDataCollectorが本リスナーをDI経由で参照し、WebProfilerにデータを提供
- NotificationEventsクラスはイベントをevents配列とtransports連想配列で管理し、トランスポート名でのフィルタリングが可能

---

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

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

### 推奨読解順序

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

NotificationLoggerListenerが蓄積するNotificationEventsの構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | NotificationEvents.php | `src/Symfony/Component/Notifier/Event/NotificationEvents.php` | イベントコレクション。events配列とtransports連想配列で管理。add()、getTransports()、getEvents()、getMessages()メソッドを提供（行19-67） |
| 1-2 | MessageEvent.php | `src/Symfony/Component/Notifier/Event/MessageEvent.php` | 蓄積されるイベント。MessageInterfaceとqueuedフラグを保持（行20-37） |

**読解のコツ**: NotificationEvents::add()（行24-28）で、イベントを配列に追加すると同時に、メッセージのトランスポート名をtransports連想配列に記録する。getEvents()はトランスポート名でフィルタリング可能。

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

NotificationLoggerListenerのイベント購読と処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | NotificationLoggerListener.php | `src/Symfony/Component/Notifier/EventListener/NotificationLoggerListener.php` | 本体。EventSubscriberInterface + ResetInterface実装。getSubscribedEvents()で購読定義、onNotification()でイベント記録、reset()でクリア（行22-52） |

**主要処理フロー**:
1. **行27-29**: コンストラクタで `new NotificationEvents()` を初期化
2. **行36-39**: `onNotification(MessageEvent $event)` でイベントをNotificationEventsに追加
3. **行41-44**: `getEvents()` で蓄積されたNotificationEventsを返却
4. **行46-51**: `getSubscribedEvents()` で `MessageEvent::class => ['onNotification', -255]` を定義
5. **行31-34**: `reset()` で `new NotificationEvents()` で初期化（リクエスト間クリア）

#### Step 3: DataCollectorとの連携を理解する

蓄積されたイベントがWebProfilerにどのように渡されるかを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | NotificationDataCollector.php | `src/Symfony/Component/Notifier/DataCollector/NotificationDataCollector.php` | DataCollector実装。コンストラクタでNotificationLoggerListenerを受け取り、collect()でgetEvents()を呼んでデータ取得（行23-49） |

**主要処理フロー**:
- **行26-28**: コンストラクタで `NotificationLoggerListener` を依存注入
- **行30-33**: `collect()` で `$this->logger->getEvents()` を呼び、`$this->data['events']` に格納
- **行35-38**: `getEvents()` で格納されたNotificationEventsを返却

#### Step 4: イベントの発生元を理解する

MessageEventがどこでディスパッチされるかを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | AbstractTransport.php | `src/Symfony/Component/Notifier/Transport/AbstractTransport.php` | send()メソッド（行67-86）内でMessageEventをディスパッチ。これがNotificationLoggerListenerに到達する |

**主要処理フロー**:
- **行73**: `$this->dispatcher->dispatch(new MessageEvent($message))` でMessageEventディスパッチ

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

```
Notifier AbstractTransport::send($message)
    |
    +-- EventDispatcher::dispatch(new MessageEvent($message))
            |
            +-- [優先度順] 他のリスナー処理
            |
            +-- [優先度 -255] NotificationLoggerListener::onNotification($event)
                    |
                    +-- NotificationEvents::add($event)
                            |
                            +-- $this->events[] = $event
                            +-- $this->transports[$transport] = true

[リクエスト終了時]
NotificationDataCollector::collect($request, $response)
    |
    +-- NotificationLoggerListener::getEvents()
            |
            +-- return $this->events  (NotificationEvents)

[次のリクエスト前]
NotificationLoggerListener::reset()
    |
    +-- $this->events = new NotificationEvents()
```

### データフロー図

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

MessageEvent ---------> NotificationLoggerListener
  (Notifier送信時)          ::onNotification()
                                |
                                +----> NotificationEvents::add()
                                |          |
                                |          +-- events配列に追加
                                |          +-- transports配列に追加
                                |
                                +----> [リクエスト終了時]
                                |          |
                                |          +-- NotificationDataCollector::collect()
                                |                     |
                                |                     +-- getEvents()
                                |                     |
                                |                     +-- WebProfiler表示
                                |
                                +----> [次リクエスト前]
                                           |
                                           +-- reset() → events初期化
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| NotificationLoggerListener.php | `src/Symfony/Component/Notifier/EventListener/NotificationLoggerListener.php` | ソース | イベントログリスナー本体 |
| NotificationEvents.php | `src/Symfony/Component/Notifier/Event/NotificationEvents.php` | ソース | イベントコレクション |
| MessageEvent.php | `src/Symfony/Component/Notifier/Event/MessageEvent.php` | ソース | 購読対象のイベント |
| NotificationDataCollector.php | `src/Symfony/Component/Notifier/DataCollector/NotificationDataCollector.php` | ソース | WebProfiler用DataCollector |
| MessageInterface.php | `src/Symfony/Component/Notifier/Message/MessageInterface.php` | ソース | メッセージの共通インターフェース |
| AbstractTransport.php | `src/Symfony/Component/Notifier/Transport/AbstractTransport.php` | ソース | MessageEventのディスパッチ元 |
| NotificationCount.php | `src/Symfony/Component/Notifier/Test/Constraint/NotificationCount.php` | ソース | テスト用制約クラス（通知件数アサーション） |
