# 機能設計書 41-Notifier

## 概要

本ドキュメントは、Symfony Notifierコンポーネントの機能設計を記述する。Notifierは複数のチャネル（メール、SMS、チャット、ブラウザ、プッシュ、デスクトップ）を通じた通知送信機能を統一APIで提供するコンポーネントである。

### 本機能の処理概要

Notifierコンポーネントは、アプリケーションから多様な通知チャネルへメッセージを送信するための抽象化レイヤーを提供する。通知の重要度に基づくチャネルポリシーにより、適切なチャネルへの自動振り分けを実現する。

**業務上の目的・背景**：現代のアプリケーションでは、メール、SMS、Slack、Telegram等の多様なチャネルを通じてユーザーへ通知を送信する必要がある。Notifierコンポーネントは、これらの多様な通知チャネルを統一的なAPIで扱うことを可能にし、チャネルの追加・変更時のコード修正を最小限に抑える。また、通知の重要度に応じたチャネル選択ポリシーにより、運用上の柔軟性を確保する。

**機能の利用シーン**：ユーザー登録完了時のメール通知、システムアラートのSlack通知、認証コードのSMS送信、リアルタイム通知のブラウザプッシュ等、あらゆる通知要件において使用される。例外発生時の管理者通知など、運用監視にも活用される。

**主要な処理内容**：
1. Notificationオブジェクトの生成（件名、本文、重要度、チャネル指定）
2. チャネルポリシーに基づく送信先チャネルの決定
3. 各チャネル（Email、SMS、Chat、Browser、Push、Desktop）への通知ディスパッチ
4. Messenger統合による非同期送信対応
5. トランスポートファクトリを通じた80以上のサードパーティサービスとの連携
6. EventDispatcherによる送信イベントの発火とリスナー処理

**関連システム・外部連携**：Slack、Telegram、Discord、Twilio、Firebase、Amazon SNS、Microsoft Teams等80以上のサードパーティ通知サービスとDSN形式で連携する。MailerコンポーネントとはEmailChannelを通じて連携する。Messengerコンポーネントと連携して非同期送信が可能。

**権限による制御**：Notifier自体には権限制御機構はない。通知の送信制御はアプリケーション層で実装する。AdminRecipient機能により、管理者向け通知の送信先を一元管理できる。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 31 | 通知パネル | 主画面 | Notifierコンポーネントによる各チャネルの通知送信情報の表示 |
| 54 | メール通知（HTML - デフォルト） | 結果表示画面 | Notifierコンポーネントからのメール通知トリガーによるHTMLメールテンプレートのレンダリング |
| 55 | メール通知（テキスト - デフォルト） | 結果表示画面 | Notifierコンポーネントからのメール通知トリガーによるテキストメールテンプレートのレンダリング |
| 56 | メール通知（HTML - Zurb 2） | 結果表示画面 | Zurb 2対応HTMLメールテンプレートのレンダリング |
| 57 | メール通知（テキスト - Zurb 2） | 結果表示画面 | Zurb 2対応テキストメールテンプレートのレンダリング |

## 機能種別

データ連携 / メッセージング

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| notification | Notification | Yes | 送信する通知オブジェクト | subject、content、importanceを保持 |
| recipients | RecipientInterface[] | No | 通知の受信者（未指定時はNoRecipient） | EmailRecipientInterface、SmsRecipientInterface等を実装 |
| channels | string[] | No | 送信チャネルの指定（Notification経由） | email、sms、chat、browser、push、desktop |
| importance | string | No | 通知の重要度 | urgent、high、medium、lowのいずれか |
| transportName | string | No | チャネル内の特定トランスポート指定 | チャネル名/トランスポート名の形式 |

### 入力データソース

- アプリケーションコードからのNotificationオブジェクト生成
- ChannelPolicyによるチャネル設定（DIコンテナ経由）
- DSN文字列によるトランスポート設定（環境変数・設定ファイル）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| SentMessage | SentMessage|null | 同期送信時の送信結果（非同期時はnull） |
| MessageEvent | MessageEvent | 送信時に発火されるイベント |
| NotificationEvents | NotificationEvents | DataCollector経由で収集される通知イベント一覧 |

### 出力先

- 各種外部通知サービス（Slack、Telegram、SMS等）
- Mailerコンポーネント（メール送信時）
- Messengerバス（非同期送信時）
- WebProfilerBundle通知パネル（デバッグ時）

## 処理フロー

### 処理シーケンス

```
1. Notification オブジェクトの生成
   └─ subject、content、importance、channelsを設定
2. Notifier::send() の呼び出し
   └─ recipientが未指定の場合はNoRecipientを生成
3. 受信者ごとにチャネルの決定
   └─ Notification::getChannels() でチャネル取得
   └─ 未設定の場合はChannelPolicy::getChannels() で重要度に基づく決定
4. チャネル名からトランスポート名の分離
   └─ "chat/slack" の場合、チャネル=chat、トランスポート=slack
5. チャネルの取得と検証
   └─ ContainerInterface または配列からチャネルオブジェクトを取得
   └─ SmsChannelでNoRecipientの場合はLogicException
   └─ supports() で受信者サポートを確認
6. チャネル::notify() による通知送信
   └─ ChatNotificationInterface等の型チェックでカスタムメッセージ生成
   └─ MessageBus が設定されている場合は非同期ディスパッチ
   └─ 未設定の場合はTransportで同期送信
```

### フローチャート

```mermaid
flowchart TD
    A[Notification生成] --> B[Notifier::send]
    B --> C{recipients指定?}
    C -->|No| D[NoRecipient生成]
    C -->|Yes| E[各recipientに対してループ]
    D --> E
    E --> F{channels取得}
    F -->|Notification指定| G[指定チャネル使用]
    F -->|未指定| H{ChannelPolicy設定?}
    H -->|Yes| I[importanceに基づくチャネル決定]
    H -->|No| J[LogicException]
    G --> K[チャネル名/トランスポート名分離]
    I --> K
    K --> L{チャネル存在確認}
    L -->|No| M[LogicException]
    L -->|Yes| N{supports確認}
    N -->|No| O[LogicException]
    N -->|Yes| P[Channel::notify]
    P --> Q{MessageBus設定?}
    Q -->|Yes| R[非同期ディスパッチ]
    Q -->|No| S[Transport::send]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-41-01 | チャネルポリシー | 通知の重要度(importance)に基づいて送信先チャネルを自動選択する | Notificationにチャネルが未指定の場合 |
| BR-41-02 | SMS受信者必須 | SmsChannelを使用する場合、SmsRecipientInterfaceを実装した受信者が必要 | SmsChannelでNoRecipientの場合にLogicException |
| BR-41-03 | フェイルオーバー | DSN文字列でパイプ演算子(||)を使用した場合、FailoverTransportとして構成される | DSN文字列に"||"が含まれる場合 |
| BR-41-04 | ラウンドロビン | DSN文字列でアンパサンド(&&)を使用した場合、RoundRobinTransportとして構成される | DSN文字列に"&&"が含まれる場合 |
| BR-41-05 | 非同期送信 | MessageBusInterfaceが設定されている場合、通知は非同期でディスパッチされる | bus引数がnullでない場合 |

### 計算ロジック

特になし。

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

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

Notifierコンポーネント自体はデータベース操作を行わない。通知履歴の永続化が必要な場合はアプリケーション層で実装する。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | LogicException | チャネルが決定できない場合 | ChannelPolicyを設定するか、Notificationにchannelsを指定する |
| - | LogicException | 指定チャネルが存在しない場合 | DIコンテナにチャネルを登録する |
| - | LogicException | SmsChannelでrecipientが未指定の場合 | SmsRecipientInterfaceを実装した受信者を指定する |
| - | LogicException | チャネルがsupportsを返さない場合 | 適切なRecipientInterfaceを実装する |
| - | UnsupportedSchemeException | DSNスキームが対応していない場合 | 正しいDSN文字列を設定する |

### リトライ仕様

FailoverTransportを使用した場合、プライマリトランスポートの送信失敗時に次のトランスポートへフォールバックする。リトライ回数や間隔はTransport層の実装に依存する。

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

Notifierコンポーネントはデータベーストランザクションを管理しない。非同期送信時はMessengerのトランザクション管理に従う。

## パフォーマンス要件

- 同期送信時は外部サービスのレスポンス時間に依存する
- 非同期送信（Messenger統合）時はメッセージキューへのエンキュー時間のみ

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

- DSN文字列にはAPIキーやシークレットが含まれるため、#[\SensitiveParameter]アトリビュートで保護されている
- 外部サービスとの通信はHTTPSを使用する
- 受信者のメールアドレスや電話番号は個人情報として適切に管理する必要がある

## 備考

- 対応チャネル: email, sms, chat, browser, push, desktop
- 80以上のサードパーティ通知サービスに対応（Transport.phpのFACTORY_CLASSES定数に定義）
- NullTransportFactoryが常にデフォルトファクトリとして利用可能

---

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

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

### 推奨読解順序

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

まず、通知に関わるデータ構造を理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | Notification.php | `src/Symfony/Component/Notifier/Notification/Notification.php` | 通知の基本データ構造（subject、content、importance、channels）を理解する |
| 1-2 | RecipientInterface.php | `src/Symfony/Component/Notifier/Recipient/RecipientInterface.php` | 受信者のインターフェース定義を確認する |
| 1-3 | ChannelPolicyInterface.php | `src/Symfony/Component/Notifier/Channel/ChannelPolicyInterface.php` | チャネルポリシーのインターフェースを理解する |

**読解のコツ**: Notificationクラスの`IMPORTANCE_*`定数とPSR LogLevelとのマッピング（**23-32行目**）がチャネルポリシーの基盤となる。`fromThrowable()`（**59-62行目**）は例外からの通知生成パターンを示す。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | NotifierInterface.php | `src/Symfony/Component/Notifier/NotifierInterface.php` | send()メソッドのインターフェース定義 |
| 2-2 | Notifier.php | `src/Symfony/Component/Notifier/Notifier.php` | メインのエントリーポイント |

**主要処理フロー**:
1. **40-51行目**: send()メソッド - recipientが未指定時にNoRecipientを生成し、各recipientに対してチャネルを取得して通知を送信
2. **69-104行目**: getChannels()メソッド - Notification::getChannels()で取得できない場合、ChannelPolicyでimportanceに基づいて決定
3. **83-88行目**: チャネル名/トランスポート名の分離ロジック（"chat/slack"形式の解析）
4. **106-113行目**: getChannel()メソッド - ContainerInterfaceまたは配列からチャネルオブジェクトを取得

#### Step 3: チャネル層を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | ChannelInterface.php | `src/Symfony/Component/Notifier/Channel/ChannelInterface.php` | チャネルのインターフェース定義 |
| 3-2 | ChatChannel.php | `src/Symfony/Component/Notifier/Channel/ChatChannel.php` | チャット通知の実装パターン |
| 3-3 | EmailChannel.php | `src/Symfony/Component/Notifier/Channel/EmailChannel.php` | メール通知の実装（Mailer連携含む） |
| 3-4 | SmsChannel.php | `src/Symfony/Component/Notifier/Channel/SmsChannel.php` | SMS通知の実装 |

**主要処理フロー**:
- **ChatChannel 24-42行目**: ChatNotificationInterfaceの判定とChatMessage生成、MessageBus経由の非同期送信
- **EmailChannel 50-86行目**: EmailNotificationInterfaceの判定、From/Toヘッダの設定、Mailer連携による送信
- **SmsChannel 28-46行目**: SmsNotificationInterfaceの判定とSmsMessage生成

#### Step 4: トランスポート層を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | Transport.php | `src/Symfony/Component/Notifier/Transport.php` | トランスポートファクトリの集約とDSN解析 |
| 4-2 | Chatter.php | `src/Symfony/Component/Notifier/Chatter.php` | チャットメッセージ送信のエントリーポイント |
| 4-3 | Texter.php | `src/Symfony/Component/Notifier/Texter.php` | テキストメッセージ送信のエントリーポイント |

**主要処理フロー**:
- **Transport.php 145-158行目**: DSN文字列の解析。"||"でFailoverTransport、"&&"でRoundRobinTransportを構成
- **Transport.php 30-111行目**: FACTORY_CLASSES定数に80以上のサードパーティTransportFactoryが定義
- **Chatter.php 43-54行目**: MessageBus設定時は非同期、未設定時はTransport::send()で同期送信

#### Step 5: データ収集を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | NotificationDataCollector.php | `src/Symfony/Component/Notifier/DataCollector/NotificationDataCollector.php` | WebProfiler向けのデータ収集 |

**主要処理フロー**:
- **30-33行目**: NotificationLoggerListenerからイベントデータを収集

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

```
Notifier::send(Notification, ...RecipientInterface)
    |
    ├─ Notifier::getChannels(Notification, RecipientInterface)
    |      ├─ Notification::getChannels(RecipientInterface)
    |      └─ ChannelPolicy::getChannels(importance)
    |
    ├─ ChatChannel::notify(Notification, RecipientInterface, transportName)
    |      ├─ ChatNotificationInterface::asChatMessage()
    |      └─ Chatter::send(ChatMessage)
    |             ├─ Transport::send(message)    [同期]
    |             └─ MessageBus::dispatch(message) [非同期]
    |
    ├─ EmailChannel::notify(Notification, RecipientInterface, transportName)
    |      ├─ EmailNotificationInterface::asEmailMessage()
    |      └─ Mailer::send(Email) / MessageBus::dispatch(SendEmailMessage)
    |
    └─ SmsChannel::notify(Notification, RecipientInterface, transportName)
           ├─ SmsNotificationInterface::asSmsMessage()
           └─ Texter::send(SmsMessage)
                  ├─ Transport::send(message)    [同期]
                  └─ MessageBus::dispatch(message) [非同期]
```

### データフロー図

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

Notification            Notifier                         外部通知サービス
  (subject,             ├─ チャネル解決                    ├─ Slack
   content,             ├─ ポリシー適用                    ├─ Telegram
   importance)          └─ Channel::notify()              ├─ Twilio (SMS)
                                |                         ├─ Email (Mailer)
RecipientInterface       Transport                        └─ ...
  (email, phone)        ├─ DSN解析
                        ├─ FailoverTransport               MessageBus
DSN設定                 ├─ RoundRobinTransport             (非同期キュー)
  (slack://xxx)         └─ HTTP送信
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| Notifier.php | `src/Symfony/Component/Notifier/Notifier.php` | ソース | 通知送信のメインクラス |
| NotifierInterface.php | `src/Symfony/Component/Notifier/NotifierInterface.php` | ソース | Notifierのインターフェース定義 |
| Notification.php | `src/Symfony/Component/Notifier/Notification/Notification.php` | ソース | 通知データ構造 |
| Chatter.php | `src/Symfony/Component/Notifier/Chatter.php` | ソース | チャットメッセージ送信 |
| Texter.php | `src/Symfony/Component/Notifier/Texter.php` | ソース | テキストメッセージ送信 |
| Transport.php | `src/Symfony/Component/Notifier/Transport.php` | ソース | トランスポートファクトリ集約 |
| ChatChannel.php | `src/Symfony/Component/Notifier/Channel/ChatChannel.php` | ソース | チャットチャネル実装 |
| EmailChannel.php | `src/Symfony/Component/Notifier/Channel/EmailChannel.php` | ソース | メールチャネル実装 |
| SmsChannel.php | `src/Symfony/Component/Notifier/Channel/SmsChannel.php` | ソース | SMSチャネル実装 |
| ChannelPolicy.php | `src/Symfony/Component/Notifier/Channel/ChannelPolicy.php` | ソース | チャネルポリシー |
| NotificationDataCollector.php | `src/Symfony/Component/Notifier/DataCollector/NotificationDataCollector.php` | ソース | プロファイラー用データ収集 |
