# 帳票設計書 26-Serializerエラー出力

## 概要

本ドキュメントは、Symfony ErrorHandlerコンポーネントにおける `SerializerErrorRenderer` クラスの帳票設計書である。例外・エラー情報をSerializerコンポーネント経由でJSON/XML等の形式で出力する機能について、出力仕様・処理フロー・データ構造を定義する。

### 本帳票の処理概要

`SerializerErrorRenderer` は、PHP例外（Throwable）をSymfony Serializerコンポーネントを使用してJSON、XML等のフォーマットに変換するクラスである。`ErrorRendererInterface` を実装し、リクエストのAcceptヘッダーやコンテンツネゴシエーション結果に基づいて適切な出力フォーマットを選択する。シリアライズ失敗時はフォールバックレンダラー（デフォルト: HtmlErrorRenderer）にフォールバックする。

**業務上の目的・背景**：REST APIやJSONベースのWebサービスにおいて、エラーレスポンスもHTMLではなくJSON/XML等の構造化されたフォーマットで返す必要がある。SerializerErrorRendererは、Serializerコンポーネントの機能を活用し、コンテンツネゴシエーションに基づいた適切なフォーマットでエラーレスポンスを生成する。

**帳票の利用シーン**：APIリクエスト処理中に例外が発生した場合、クライアントのAcceptヘッダーに応じたフォーマット（JSON/XML等）でエラーレスポンスを返す際に使用される。

**主要な出力内容**：
1. シリアライズされた例外情報（JSON/XML等）
2. HTTPステータスコードとステータスメッセージ
3. デバッグ情報（デバッグモード時）
4. Varyヘッダー（Accept）
5. 適切なContent-Typeヘッダー

**帳票の出力タイミング**：APIリクエストで例外が発生し、ErrorHandlerがSerializerErrorRendererを使用してエラーをレンダリングする際に出力される。

**帳票の利用者**：APIクライアント（フロントエンドアプリケーション、モバイルアプリ、サードパーティ連携）

## 帳票種別

エラーレスポンス（シリアライズ出力: JSON/XML等）

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| - | APIエンドポイント | 任意のAPIルート | 例外発生時の自動出力 |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | JSON / XML / その他Serializer対応フォーマット |
| 用紙サイズ | N/A |
| 向き | N/A |
| ファイル名 | N/A（HTTPレスポンスボディ） |
| 出力方法 | HTTPレスポンス |
| 文字コード | UTF-8 |

### PDF固有設定

該当なし

### Excel固有設定

該当なし

## 帳票レイアウト

### レイアウト概要

Serializerによるフォーマットに依存する。JSON形式の場合のレイアウト例：

```
{
    "type": "https://tools.ietf.org/html/rfc2616#section-10",
    "title": "An error occurred",
    "status": 500,
    "detail": "Internal Server Error"
}
```

### ヘッダー部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | Content-Type | フォーマットに応じたMIMEタイプ | `Request::getMimeTypes($format)` | `application/json`, `application/xml`等 |
| 2 | Vary | コンテンツネゴシエーション対応 | 固定値 | `Accept` |
| 3 | X-Debug-Exception | エンコード済み例外メッセージ（デバッグ時） | `$exception->getMessage()` | URLエンコード |
| 4 | X-Debug-Exception-File | 例外発生ファイル（デバッグ時） | `$exception->getFile():getLine()` | URLエンコード |

### 明細部

| No | 項目名 | 説明 | データ取得元 | 表示形式 | 列幅 |
|----|-------|------|-------------|---------|-----|
| 1 | 例外情報 | FlattenExceptionのシリアライズ結果 | `Serializer::serialize()` | フォーマット依存 | 可変 |

### フッター部

該当なし

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| Throwable | レンダリング対象の例外 | Yes |
| SerializerInterface | シリアライズ実行者 | Yes（コンストラクタ注入） |
| format | 出力フォーマット（string/callable） | Yes（コンストラクタ注入） |
| debug | デバッグモードフラグ | No（デフォルト: false） |
| fallbackErrorRenderer | フォールバックレンダラー | No（デフォルト: HtmlErrorRenderer） |

### ソート順

該当なし（単一例外のシリアライズ）

### 改ページ条件

該当なし

## データベース参照仕様

### 参照テーブル一覧

該当なし

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

該当なし

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| Content-Type | `Request::getMimeTypes($format)[0] ?? $format` | なし | フォーマット名からMIMEタイプを解決 |
| X-Debug-Exceptionヘッダー | `rawurlencode(substr($message, 0, 2000))` | なし | デバッグモード時のみ |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[render Throwable] --> B[Varyヘッダー設定]
    B --> C{デバッグモード?}
    C -->|Yes| D[X-Debug-*ヘッダー追加]
    C -->|No| E[ヘッダーのみ]
    D --> F[FlattenException生成]
    E --> F
    F --> G[フォーマット決定]
    G --> H[Content-Typeヘッダー設定]
    H --> I{シリアライズ試行}
    I -->|成功| J[serialize結果をsetAsString]
    I -->|NotEncodableValueException| K[フォールバックレンダラーで出力]
    J --> L[ヘッダーマージ]
    K --> L
    L --> M[FlattenException返却]
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| シリアライズ失敗 | Serializerがフォーマット未対応 | NotEncodableValueException | フォールバックレンダラー（HtmlErrorRenderer）にフォールバック |
| フォーマット不明 | callableがNotEncodableValueExceptionをスロー | NotEncodableValueException | フォールバック処理 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | 1例外 |
| 目標出力時間 | 100ms以内 |
| 同時出力数上限 | 1 |

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

- デバッグモード時のX-Debug-*ヘッダーにファイルパスやメッセージが含まれる
- 本番環境ではデバッグモードを無効にすること
- シリアライズされた例外情報にスタックトレースが含まれる場合がある

## 備考

- `getPreferredFormat()` 静的メソッドでRequestStackからリクエストの優先フォーマットを取得可能
- フォールバックレンダラーのデフォルトは `HtmlErrorRenderer`
- Serializerコンテキストには `exception`（元の例外）と `debug`（デバッグフラグ）が渡される
- フォーマットがMIMEタイプとして認識されない場合、フォーマット文字列がそのままContent-Typeとして使用される

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | FlattenException.php | `src/Symfony/Component/ErrorHandler/Exception/FlattenException.php` | シリアライズ対象のデータ構造 |
| 1-2 | ErrorRendererInterface.php | `src/Symfony/Component/ErrorHandler/ErrorRenderer/ErrorRendererInterface.php` | render()の契約 |

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | SerializerErrorRenderer.php | `src/Symfony/Component/ErrorHandler/ErrorRenderer/SerializerErrorRenderer.php` | render()メソッド（47-71行目）がメイン処理 |

**主要処理フロー**:
1. **36-45行目**: コンストラクタ - serializer, format, fallbackErrorRenderer, debugの設定
2. **49行目**: `Vary: Accept` ヘッダー設定
3. **50-54行目**: デバッグモード判定とX-Debug-*ヘッダー追加
4. **56行目**: `FlattenException::createFromThrowable()` で例外フラット化
5. **59行目**: フォーマット決定（string/callable）
6. **60行目**: Content-Typeヘッダー設定（`Request::getMimeTypes()`使用）
7. **62-65行目**: `$this->serializer->serialize()` でシリアライズ実行
8. **66-68行目**: `NotEncodableValueException` キャッチでフォールバック

#### Step 3: フォーマット決定ロジックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | SerializerErrorRenderer.php | `src/Symfony/Component/ErrorHandler/ErrorRenderer/SerializerErrorRenderer.php` | getPreferredFormat()静的メソッド（73-82行目） |

**主要処理フロー**:
- **73-82行目**: `getPreferredFormat()` - RequestStackから現在のリクエストの優先フォーマットを取得。リクエストがない場合はNotEncodableValueExceptionをスロー

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

```
SerializerErrorRenderer::render(Throwable)
    |
    +-- FlattenException::createFromThrowable()
    |
    +-- [format解決]
    |       +-- string: そのまま使用
    |       +-- callable: ($this->format)($flattenException)
    |               +-- getPreferredFormat(RequestStack)
    |                       +-- RequestStack::getCurrentRequest()
    |                       +-- Request::getPreferredFormat()
    |
    +-- Request::getMimeTypes($format) --> Content-Type
    |
    +-- SerializerInterface::serialize(FlattenException, $format, $context)
    |       +-- [成功時] setAsString()
    |       +-- [失敗時 NotEncodableValueException]
    |               +-- fallbackErrorRenderer::render()
    |                       +-- HtmlErrorRenderer::render() [デフォルト]
    |
    +-- FlattenException::setHeaders()
```

### データフロー図

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

Throwable -----> FlattenException::createFromThrowable()
                        |
                        v
FlattenException -----> Serializer::serialize($format)
                        |
                        +--[成功]--> JSON/XML文字列
                        |                  |
                        +--[失敗]--> fallbackRenderer
                        |                  |
                        v                  v
                 FlattenException    FlattenException
                 (serialized)       (HTML fallback)
                        |
                        v
                 HTTP Response
                 + Content-Type header
                 + Vary: Accept header
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| SerializerErrorRenderer.php | `src/Symfony/Component/ErrorHandler/ErrorRenderer/SerializerErrorRenderer.php` | ソース | メインクラス（83行） |
| ErrorRendererInterface.php | `src/Symfony/Component/ErrorHandler/ErrorRenderer/ErrorRendererInterface.php` | ソース | インターフェース |
| HtmlErrorRenderer.php | `src/Symfony/Component/ErrorHandler/ErrorRenderer/HtmlErrorRenderer.php` | ソース | フォールバックレンダラー |
| FlattenException.php | `src/Symfony/Component/ErrorHandler/Exception/FlattenException.php` | ソース | フラット化例外 |
| SerializerInterface.php | `src/Symfony/Component/Serializer/SerializerInterface.php` | ソース | シリアライザーインターフェース |
| NotEncodableValueException.php | `src/Symfony/Component/Serializer/Exception/NotEncodableValueException.php` | ソース | シリアライズ失敗例外 |
| Request.php | `src/Symfony/Component/HttpFoundation/Request.php` | ソース | MIMEタイプ解決 |
| RequestStack.php | `src/Symfony/Component/HttpFoundation/RequestStack.php` | ソース | リクエスト取得 |
