# 通知設計書 10-SentMessageEvent（Notifier）

## 概要

本ドキュメントは、Symfony NotifierコンポーネントにおけるSentMessageEvent（Notifier）の設計仕様を記述する。SentMessageEventは、通知メッセージの送信が正常に完了した際に発火するイベントであり、送信済みメッセージの情報を提供する。

### 本通知の処理概要

SentMessageEventは、Notifierコンポーネントのイベントシステムにおける「送信完了」イベントである。AbstractTransport::send()メソッド内でdoSend()が正常に完了した直後にディスパッチされる。このイベントを購読することで、送信成功の記録、メトリクスの収集、後続処理のトリガーなどが可能になる。

**業務上の目的・背景**：通知メッセージの送信結果を追跡・モニタリングするために、送信完了時のフックポイントが必要である。SentMessageEventにより、送信成功のログ記録、送信メトリクスの集計、送信完了後の後続処理（ステータス更新等）の実行などの横断的関心事を実装できる。プロファイリングツール（Symfony WebProfiler等）でのデータ収集にも使用される。

**通知の送信タイミング**：AbstractTransport::send()内で、doSend()が正常に完了しSentMessageオブジェクトが返された直後にディスパッチされる（AbstractTransport.php 行83）。Chatter/Texterクラスの非同期送信パス（Bus経由）では、このイベントはディスパッチされない。

**通知の受信者**：このイベントには「受信者」という概念はない。EventDispatcherに登録されたイベントリスナー/サブスクライバーがイベントを処理する。

**通知内容の概要**：SentMessageオブジェクトを保持する。SentMessageには元のメッセージ（original）、Transport名、メッセージID、追加情報（info）が含まれる。

**期待されるアクション**：イベントリスナーは送信結果のログ記録、メトリクスの更新、送信完了通知の発行などを行う。

## 通知種別

イベント（EventDispatcher経由）
- 外部への通知ではなく、アプリケーション内部のイベント通知メカニズム

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | EventDispatcher::dispatch()による同期イベントディスパッチ |
| 優先度 | EventDispatcherのリスナー優先度に依存 |
| リトライ | なし（イベントディスパッチのため） |

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

1. EventDispatcherに登録されたSentMessageEvent用リスナーが自動的に呼び出される
2. リスナーの登録はサービスコンテナの設定またはEventSubscriberInterfaceの実装で行う

## 通知テンプレート

### 本文テンプレート

該当なし（イベントオブジェクトとしてディスパッチされるため、テンプレートは使用しない）

### 添付ファイル

| ファイル名 | 形式 | 条件 | 説明 |
|----------|------|------|------|
| 該当なし | - | - | イベントオブジェクトのため添付ファイルなし |

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| message | 送信済みメッセージオブジェクト | SentMessageEvent::getMessage() | Yes |
| message.original | 元のメッセージオブジェクト | SentMessage::getOriginalMessage() | Yes |
| message.transport | 使用されたTransport名 | SentMessage::getTransport() | Yes |
| message.messageId | プロバイダから返されたメッセージID | SentMessage::getMessageId() | No |
| message.info | Transport関連の追加情報 | SentMessage::getInfo() | No |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| Transport送信完了 | AbstractTransport::send()内のdoSend()正常完了 | EventDispatcherが設定されていること、かつdoSend()が例外をスローしないこと | doSend()成功直後にディスパッチ（AbstractTransport.php 行83） |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| EventDispatcherが未設定（null） | dispatcher==nullの場合、イベントは一切ディスパッチされない（AbstractTransport.php 行69-71） |
| doSend()が失敗した場合 | 例外がスローされた場合はFailedMessageEventがディスパッチされ、SentMessageEventはディスパッチされない（AbstractTransport.php 行77-81） |
| Chatter/TexterのBus経由パス | 非同期送信の場合、SentMessageEventはディスパッチされない（実際の送信はワーカーで行われる） |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[AbstractTransport::send] --> B{dispatcher設定あり?}
    B -->|No| C[doSend直接実行 イベントなし]
    B -->|Yes| D[MessageEvent dispatch]
    D --> E[doSend実行]
    E --> F{送信成功?}
    F -->|Yes| G[SentMessageEvent dispatch]
    F -->|No| H[FailedMessageEvent dispatch]
    G --> I[SentMessage返却]
    H --> J[例外再スロー]
    C --> I
```

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

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| 該当なし | - | イベントオブジェクトはデータベースを参照しない |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| 該当なし | - | イベントオブジェクト自体はデータベースを更新しない |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| リスナー例外 | イベントリスナー内で例外がスローされた場合 | リスナーの実装でtry/catchを行う |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | なし（イベントディスパッチのため） |
| リトライ間隔 | - |
| リトライ対象エラー | - |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | なし（イベントディスパッチのため） |
| 1日あたり上限 | なし |

### 配信時間帯

制限なし。通知メッセージ送信完了のタイミングに連動。

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

- SentMessageEventにはSentMessageオブジェクトが含まれ、元のメッセージオブジェクトへの参照を持つため、リスナー内でメッセージ内容にアクセスする際は個人情報の取り扱いに注意すること
- メッセージIDやTransport情報をログ出力する場合は、プロバイダの認証情報が含まれないことを確認すること

## 備考

- SentMessageEventはfinalクラスとして定義されている（SentMessageEvent.php 行20）
- SentMessageEventはSymfony Contracts EventDispatcher\Eventを継承している
- getMessage()はSentMessageオブジェクトを返す（MessageEventのgetMessage()がMessageInterfaceを返すのとは異なる）
- SentMessageクラスはoriginal（MessageInterface）、transport（string）、messageId（nullable string）、info（array）を保持する
- SentMessage::getInfo()はキー指定で特定の情報を取得可能、キー未指定で全情報配列を返す（SentMessage.php 行56-63）
- AbstractTransport::send()の3段階イベントフロー（MessageEvent -> doSend -> SentMessageEvent/FailedMessageEvent）の最終段階として位置づけられる

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | SentMessageEvent.php | `src/Symfony/Component/Notifier/Event/SentMessageEvent.php` | finalクラス。コンストラクタ（行22-24）でSentMessageを受け取る。getMessage()でSentMessageを返す |
| 1-2 | SentMessage.php | `src/Symfony/Component/Notifier/Message/SentMessage.php` | コンストラクタ（行24-29）でoriginal, transport, infoを受け取る。messageIdはsetMessageId()で後から設定可能。getInfo()（行56-63）はキー指定でアクセス可能 |
| 1-3 | MessageInterface.php | `src/Symfony/Component/Notifier/Message/MessageInterface.php` | SentMessage::getOriginalMessage()が返すインターフェース |

**読解のコツ**: SentMessageEventはMessageEventと対になるイベント。MessageEventがMessageInterfaceを保持するのに対し、SentMessageEventはSentMessageを保持する。SentMessageは送信完了後の情報（messageId, transport名, 追加info）を含むラッパーである。

#### Step 2: ディスパッチ元を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | AbstractTransport.php | `src/Symfony/Component/Notifier/Transport/AbstractTransport.php` | send()メソッド（行67-86）。doSend()成功後の行83でSentMessageEventをディスパッチ |

**主要処理フロー**:
- **行73**: MessageEventディスパッチ（送信前）
- **行76**: doSend()で実際の送信を実行、SentMessageを取得
- **行83**: SentMessageEvent(sentMessage)をディスパッチ（送信完了）
- **行85**: sentMessageを呼び出し元に返却

#### Step 3: イベントの利用者を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | NotificationLoggerListener.php | `src/Symfony/Component/Notifier/EventListener/NotificationLoggerListener.php` | SentMessageEventを含むイベントをログとして記録する例 |

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

```
AbstractTransport::send(message)
    |
    +-- dispatch(new MessageEvent(message))     [送信前]
    |
    +-- doSend(message)                         [実際の送信]
    |       |
    |       +-- return SentMessage(original, transport, info)
    |
    +-- dispatch(new SentMessageEvent(sentMessage))  [送信完了]
    |
    +-- return sentMessage
```

### データフロー図

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

MessageInterface                AbstractTransport::send()
  （送信対象メッセージ）-------->   |
                                  v
                               doSend()
                                  |
                                  v
                               SentMessage生成              -------->  SentMessageEvent
                                - original                             -> EventDispatcher
                                - transport                            -> リスナー群
                                - messageId
                                - info
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| SentMessageEvent.php | `src/Symfony/Component/Notifier/Event/SentMessageEvent.php` | ソース | 送信完了イベント（本通知のメインファイル） |
| SentMessage.php | `src/Symfony/Component/Notifier/Message/SentMessage.php` | ソース | 送信済みメッセージクラス |
| MessageEvent.php | `src/Symfony/Component/Notifier/Event/MessageEvent.php` | ソース | 送信前イベント（対になるイベント） |
| FailedMessageEvent.php | `src/Symfony/Component/Notifier/Event/FailedMessageEvent.php` | ソース | 送信失敗イベント（対になるイベント） |
| AbstractTransport.php | `src/Symfony/Component/Notifier/Transport/AbstractTransport.php` | ソース | イベントディスパッチ元 |
| MessageInterface.php | `src/Symfony/Component/Notifier/Message/MessageInterface.php` | ソース | メッセージの共通インターフェース |
| NotificationLoggerListener.php | `src/Symfony/Component/Notifier/EventListener/NotificationLoggerListener.php` | ソース | イベントログ記録リスナー |
