# 帳票設計書 24-HTMLエラーページ

## 概要

本ドキュメントは、Symfony ErrorHandlerコンポーネントにおける `HtmlErrorRenderer` クラスの帳票設計書である。例外・エラー情報をHTML形式のエラーページとして出力し、デバッグモードでは詳細なスタックトレースを含むリッチなエラー表示を提供する機能について、出力仕様・処理フロー・データ構造を定義する。

### 本帳票の処理概要

`HtmlErrorRenderer` は、PHP例外（Throwable）をHTML形式のエラーページに変換するクラスである。`ErrorRendererInterface` を実装し、デバッグモードと本番モードで異なるエラーページを出力する。デバッグモードではスタックトレース・ソースコード抜粋・ログ情報・変数ダンプを含む詳細なエラーページ、本番モードではステータスコードとメッセージのみのシンプルなエラーページを生成する。

**業務上の目的・背景**：Webアプリケーションにおいてエラー発生時、開発者には詳細なデバッグ情報を、エンドユーザーには適切なエラーメッセージを表示する必要がある。HtmlErrorRendererは、この2つの要件をデバッグフラグで切り替えることで同時に満たす。Symfonyのエラーハンドリングの中核をなす機能である。

**帳票の利用シーン**：HTTPリクエスト処理中に未捕捉例外が発生した場合、ErrorHandlerまたはHttpKernelのエラーハンドリング機構から呼び出される。

**主要な出力内容**：
1. HTTPステータスコードとステータスメッセージ
2. 例外メッセージとクラス名
3. スタックトレース（デバッグモード時）
4. ソースコード抜粋（デバッグモード時、前後3行）
5. ログ出力（デバッグモード時、DebugLogger使用時）
6. Symfonyゴーストアイコン（特別日にはアドオン付き）

**帳票の出力タイミング**：未捕捉例外が発生し、ErrorHandlerまたはExceptionListenerが例外をレンダリングする際に出力される。

**帳票の利用者**：アプリケーション開発者（デバッグモード）、エンドユーザー（本番モード）

## 帳票種別

エラーレスポンスページ（HTML出力）

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| - | エラーページ | 任意のURL（例外発生時） | 自動表示（未捕捉例外） |
| - | デバッグエラーページ | 任意のURL（デバッグモード＋例外） | 自動表示 |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | HTML |
| 用紙サイズ | N/A（Web画面出力） |
| 向き | N/A |
| ファイル名 | N/A（HTTPレスポンスボディ） |
| 出力方法 | HTTPレスポンス（Content-Type: text/html） |
| 文字コード | UTF-8（デフォルト、変更可能） |

### PDF固有設定

該当なし

### Excel固有設定

該当なし

## 帳票レイアウト

### レイアウト概要

デバッグモードと本番モードで大きく異なるレイアウトを持つ。

**本番モード:**
```
┌─────────────────────────────────────┐
│     ステータスコード                   │
│     ステータスメッセージ               │
└─────────────────────────────────────┘
```

**デバッグモード:**
```
┌─────────────────────────────────────┐
│  例外クラス名 + メッセージ            │
│  ステータスコード                     │
├─────────────────────────────────────┤
│  スタックトレース一覧                 │
│    ├─ フレーム1: ファイル:行          │
│    │   ソースコード抜粋               │
│    │   引数表示                      │
│    ├─ フレーム2: ...                 │
│    └─ ...                           │
├─────────────────────────────────────┤
│  ログ出力（DebugLogger使用時）        │
├─────────────────────────────────────┤
│  出力バッファ内容                     │
└─────────────────────────────────────┘
```

### ヘッダー部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | ステータスコード | HTTPステータスコード | `FlattenException::getStatusCode()` | 数値 |
| 2 | ステータステキスト | HTTPステータスメッセージ | `FlattenException::getStatusText()` | テキスト |
| 3 | 例外メッセージ | 例外のメッセージ | `FlattenException::getMessage()` | HTMLエスケープ済みテキスト |
| 4 | X-Debug-Exceptionヘッダー | エンコード済み例外メッセージ | `$exception->getMessage()` | URLエンコード（先頭2000文字） |
| 5 | X-Debug-Exception-Fileヘッダー | 例外発生ファイルと行番号 | `$exception->getFile()`, `getLine()` | `{file}:{line}` |

### 明細部

| No | 項目名 | 説明 | データ取得元 | 表示形式 | 列幅 |
|----|-------|------|-------------|---------|-----|
| 1 | スタックトレース | 例外のスタックトレース各フレーム | `FlattenException::getTrace()` | ファイル名:行番号 + 関数呼び出し | 可変 |
| 2 | ソースコード抜粋 | 例外発生箇所のソースコード | `fileExcerpt()` メソッド | `highlight_file()`によるシンタックスハイライト | 可変 |
| 3 | 関数引数 | スタックフレームの引数情報 | `FlattenException::getTrace()[n]['args']` | `formatArgs()`によるフォーマット | 可変 |
| 4 | ログ出力 | DebugLoggerで収集されたログ | `DebugLoggerConfigurator::getDebugLogger()` | ログレベル + メッセージ | 可変 |
| 5 | 出力バッファ | ob_get_clean()の結果 | `$outputBuffer` | テキスト | 可変 |

### フッター部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | Symfonyゴースト | Symfonyロゴアイコン | SVGパス定数 | SVGインライン |
| 2 | 特別日アドオン | バレンタイン(ハート)/閏年(+)/誕生日(ギフト) | `GHOST_ADDONS` 定数（日付判定） | SVGパス |

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| Throwable | レンダリング対象の例外 | Yes |
| debug | デバッグモードフラグ（bool/callable） | No（デフォルト: false） |
| charset | 文字エンコーディング | No（デフォルト: UTF-8） |
| fileLinkFormat | ファイルリンクフォーマット | No |
| projectDir | プロジェクトルートディレクトリ | No |
| outputBuffer | 出力バッファ | No（デフォルト: 空文字列） |

### ソート順

| 優先度 | 項目 | 昇順/降順 |
|-------|------|---------|
| 1 | スタックトレース | 呼び出し順（最新が上） |

### 改ページ条件

該当なし

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

### 参照テーブル一覧

該当なし

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

該当なし

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| X-Debug-Exceptionヘッダー | `rawurlencode(substr($exception->getMessage(), 0, 2000))` | なし | 先頭2000文字をURLエンコード |
| ファイル相対パス | `ltrim(substr($file, strlen($projectDir)), '/')` | なし | projectDirからの相対パス |
| ソースコード抜粋範囲 | `max($line - $srcContext, 1)` 〜 `min($line + $srcContext, count($content))` | なし | デフォルト前後3行 |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[render Throwable] --> B[Content-Typeヘッダー設定]
    B --> C{デバッグモード?}
    C -->|Yes| D[X-Debug-Exceptionヘッダー追加]
    C -->|No| E[ヘッダー設定のみ]
    D --> F[FlattenException生成]
    E --> F
    F --> G[renderException呼び出し]
    G --> H{デバッグモード?}
    H -->|Yes| I[exception_full.html.phpテンプレート]
    H -->|No| J[error.html.phpテンプレート]
    I --> K[スタックトレース表示]
    I --> L[ソースコード抜粋]
    I --> M[ログ表示]
    I --> N[出力バッファ表示]
    J --> O[ステータスコード+メッセージ表示]
    K --> P[HTML文字列返却]
    L --> P
    M --> P
    N --> P
    O --> P
    P --> Q[FlattenException::setAsString]
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| テンプレート不在 | カスタムテンプレートファイルが存在しない | PHP Warning/Error | 正しいテンプレートパスを設定 |
| 出力バッファエラー | ob_get_clean()失敗 | なし（空文字列フォールバック） | 正常なOBレベルを確保 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | 1例外（ネストされた前例外含む） |
| 目標出力時間 | 200ms以内（highlight_file含む） |
| 同時出力数上限 | 1（エラーページは1リクエストに1つ） |

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

- デバッグモードを本番環境で有効にしてはならない（スタックトレース・ソースコード・変数情報が露出する）
- X-Debug-Exception/X-Debug-Exception-Fileヘッダーはデバッグモード時のみ出力される
- 例外メッセージは `htmlspecialchars()` でエスケープされる
- `fileLinkFormat` によりローカルファイルパスがリンクとして出力される

## 備考

- `setTemplate()` 静的メソッドで本番用テンプレートをカスタマイズ可能
- 特別日のゴーストアドオン：2月14日（ハート）、2月29日（+）、10月18日（ギフト）
- `isDebug()` 静的メソッドでRequestStackから動的にデバッグモードを判定可能
- `getAndCleanOutputBuffer()` でOBレベル管理付きの出力バッファクリーンアップを提供
- HtmlDumperを内部で使用し、変数データのHTML表示を生成

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | FlattenException.php | `src/Symfony/Component/ErrorHandler/Exception/FlattenException.php` | 例外のフラット化表現。getStatusCode(), getStatusText(), getTrace()等 |
| 1-2 | ErrorRendererInterface.php | `src/Symfony/Component/ErrorHandler/ErrorRenderer/ErrorRendererInterface.php` | render()メソッドの契約。IDE_LINK_FORMATS定数 |

**読解のコツ**: FlattenExceptionは例外をシリアライズ可能な形式に変換する。元の例外のスタックトレース・メッセージ・コードを保持しつつ、HTTPステータスコードを付与する。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | HtmlErrorRenderer.php | `src/Symfony/Component/ErrorHandler/ErrorRenderer/HtmlErrorRenderer.php` | render()メソッド（62-73行目）がエントリーポイント |

**主要処理フロー**:
1. **62行目**: `render()` - Content-Typeヘッダー設定、デバッグヘッダー追加
2. **70行目**: `FlattenException::createWithDataRepresentation()` - 例外のフラット化
3. **72行目**: `renderException()` で HTML文字列を生成し `setAsString()` で設定

#### Step 3: レンダリング処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | HtmlErrorRenderer.php | `src/Symfony/Component/ErrorHandler/ErrorRenderer/HtmlErrorRenderer.php` | renderException()（125-148行目）、formatFile()（220-243行目）、fileExcerpt()（252-277行目） |

**主要処理フロー**:
- **48-60行目**: コンストラクタ - debug/charset/fileLinkFormat/projectDir/outputBuffer/loggerの設定
- **125-148行目**: `renderException()` - デバッグモードで`exception_full.html.php`、本番で`error.html.php`テンプレートを使用
- **150-156行目**: `dumpValue()` - HtmlDumper(lightテーマ)で変数をHTML出力
- **158-182行目**: `formatArgs()` - スタックトレース引数のHTML整形
- **220-243行目**: `formatFile()` - ファイルパスのリンク化（fileLinkFormat使用）
- **252-277行目**: `fileExcerpt()` - highlight_file()でソースコード抜粋生成
- **321-328行目**: `addElementToGhost()` - 特別日判定（02-14, 02-29, 10-18）

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

```
HtmlErrorRenderer::render(Throwable)
    |
    +-- FlattenException::createWithDataRepresentation()
    |
    +-- HtmlErrorRenderer::renderException(FlattenException)
            |
            +-- [デバッグモード]
            |       +-- include('exception_full.html.php')
            |       |       +-- formatFile(file, line)
            |       |       |       +-- FileLinkFormatter::format()
            |       |       +-- fileExcerpt(file, line)
            |       |       |       +-- highlight_file()
            |       |       +-- formatArgs(args)
            |       |       +-- dumpValue(Data)
            |       |       |       +-- HtmlDumper::dump()
            |       |       +-- formatLogMessage()
            |       |       +-- addElementToGhost()
            |       +-- escape()
            |
            +-- [本番モード]
                    +-- include('error.html.php')
                    +-- escape()
```

### データフロー図

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

Throwable -----> render()
                    |
                    +-- FlattenException生成 ----+
                    |                            |
                    v                            v
              renderException()           HTTP Headers
                    |                    (Content-Type, X-Debug-*)
                    |
              +-----+-----+
              |           |
         [Debug]    [Production]
              |           |
              v           v
     exception_full   error.html.php
     .html.php            |
         |                v
         +-- fileExcerpt  StatusCode + Message
         +-- formatFile       |
         +-- formatArgs       v
         +-- dumpValue   HTML Response
         |
         v
    HTML Response (with stack trace)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| HtmlErrorRenderer.php | `src/Symfony/Component/ErrorHandler/ErrorRenderer/HtmlErrorRenderer.php` | ソース | メインクラス（350行） |
| ErrorRendererInterface.php | `src/Symfony/Component/ErrorHandler/ErrorRenderer/ErrorRendererInterface.php` | ソース | インターフェース定義 |
| FlattenException.php | `src/Symfony/Component/ErrorHandler/Exception/FlattenException.php` | ソース | フラット化例外クラス |
| FileLinkFormatter.php | `src/Symfony/Component/ErrorHandler/ErrorRenderer/FileLinkFormatter.php` | ソース | ファイルリンク生成 |
| error.html.php | `src/Symfony/Component/ErrorHandler/Resources/views/error.html.php` | テンプレート | 本番用エラーページ |
| exception_full.html.php | `src/Symfony/Component/ErrorHandler/Resources/views/exception_full.html.php` | テンプレート | デバッグ用エラーページ |
| exception.html.php | `src/Symfony/Component/ErrorHandler/Resources/views/exception.html.php` | テンプレート | 例外ボディ部分 |
| error.css | `src/Symfony/Component/ErrorHandler/Resources/assets/css/error.css` | CSS | 本番用スタイルシート |
| exception.css | `src/Symfony/Component/ErrorHandler/Resources/assets/css/exception.css` | CSS | デバッグ用スタイルシート |
| HtmlDumper.php | `src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php` | ソース | 変数ダンプHTML出力 |
