# 帳票設計書 28-ストリーミングJSONレスポンス

## 概要

本ドキュメントは、Symfony HttpFoundationコンポーネントにおける `StreamedJsonResponse` クラスの帳票設計書である。大量データをJSONストリーミング形式でHTTPレスポンスとして出力し、メモリ効率に優れたGenerator対応の出力機能について、出力仕様・処理フロー・データ構造を定義する。

### 本帳票の処理概要

`StreamedJsonResponse` は、大量のデータを含むJSON構造をストリーミング形式でHTTPレスポンスとして出力するクラスである。`StreamedResponse` を継承し、PHP GeneratorやIterableオブジェクトを使用して、全データをメモリに保持することなくJSONレスポンスを逐次出力する。JSON構造の静的部分はjson_encode()で処理し、動的なイテラブル部分はストリーミングで出力する。

**業務上の目的・背景**：REST APIにおいて大量のレコード（数万〜数百万件）をJSON形式で返す場合、全データをメモリに載せてjson_encode()すると、メモリ不足やレスポンス遅延が発生する。StreamedJsonResponseは、Generatorパターンを活用してデータを逐次JSONエンコード・出力することで、メモリ使用量をO(1)に近い一定値に抑えながら大量データを配信する。

**帳票の利用シーン**：大量データのAPIエンドポイント、CSVデータのJSON変換配信、データベースからのストリーミングクエリ結果の配信等で使用される。

**主要な出力内容**：
1. JSON形式のストリーミング出力
2. 静的JSON構造（配列/オブジェクトの骨格）
3. Generatorから逐次取得されるデータ要素
4. Content-Type: application/json ヘッダー

**帳票の出力タイミング**：コントローラーからStreamedJsonResponseが返され、Symfony Kernelがレスポンスを送信する際に、ストリーミングコールバックが実行されて出力される。

**帳票の利用者**：APIクライアント（フロントエンドアプリケーション、データ分析ツール）

## 帳票種別

ストリーミングJSONレスポンス（HTTPストリーム出力）

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| - | APIエンドポイント | アプリケーション定義のルート | GETリクエスト |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | JSON（ストリーミング） |
| 用紙サイズ | N/A |
| 向き | N/A |
| ファイル名 | N/A（HTTPレスポンスボディ） |
| 出力方法 | HTTPストリーミングレスポンス |
| 文字コード | UTF-8（json_encodeデフォルト） |

### PDF固有設定

該当なし

### Excel固有設定

該当なし

## 帳票レイアウト

### レイアウト概要

JSON構造がストリーミングで段階的に出力される。静的部分とジェネレーター部分が交互に出力される。

```
┌─────────────────────────────────────┐
│  {"_embedded":{"articles":[          │  ← 静的JSON構造（前半）
│    {"title":"Article 1"},            │  ← Generator要素1
│    {"title":"Article 2"},            │  ← Generator要素2
│    {"title":"Article 3"}             │  ← Generator要素3
│  ]}}                                 │  ← 静的JSON構造（後半）
└─────────────────────────────────────┘
```

### ヘッダー部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | Content-Type | JSONのMIMEタイプ | デフォルト設定 | `application/json` |

### 明細部

| No | 項目名 | 説明 | データ取得元 | 表示形式 | 列幅 |
|----|-------|------|-------------|---------|-----|
| 1 | 静的JSON部分 | json_encode()で生成された構造骨格 | `$data` 配列のスカラー値部分 | JSON文字列 | 可変 |
| 2 | Generator要素 | イテレータから逐次取得される個別データ | `$data` 配列内のiterable | 各要素のjson_encode結果 | 可変 |

### フッター部

該当なし

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| data | JSON構造（配列+Generator/Iterable） | Yes |
| status | HTTPステータスコード | No（デフォルト: 200） |
| headers | HTTPレスポンスヘッダー | No |
| encodingOptions | json_encodeオプション | No（デフォルト: JsonResponse::DEFAULT_ENCODING_OPTIONS） |

### ソート順

| 優先度 | 項目 | 昇順/降順 |
|-------|------|---------|
| 1 | Generator出力順 | Generatorのyield順 |

### 改ページ条件

該当なし

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

### 参照テーブル一覧

該当なし（アプリケーション層からGeneratorで渡される）

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

該当なし

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| JSONエンコードオプション | `JSON_THROW_ON_ERROR \| $this->encodingOptions` | なし | エラー時に例外をスロー |
| キーエンコードオプション | `$jsonEncodingOptions & ~JSON_NUMERIC_CHECK` | なし | キーの数値チェックを無効化 |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[StreamedJsonResponse生成] --> B[StreamedResponse親生成]
    B --> C[Content-Type設定]
    C --> D[stream callback実行]
    D --> E[streamData呼び出し]
    E --> F{データタイプ?}
    F -->|配列| G[streamArray]
    F -->|Iterable非JsonSerializable| H[streamIterable]
    F -->|その他| I[json_encode直接出力]
    G --> J[Generatorをプレースホルダーに置換]
    J --> K[json_encode 静的部分]
    K --> L[explode プレースホルダー]
    L --> M[静的部分とGenerator交互出力]
    H --> N{最初のキー判定}
    N -->|0| O[リスト形式: 開始]
    N -->|非0| P[マップ形式: 開始]
    O --> Q[要素ごとにstreamData再帰]
    P --> Q
    Q --> R[閉じ括弧出力]
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| JSONエンコードエラー | json_encode失敗 | JsonException（JSON_THROW_ON_ERROR） | データのUTF-8エンコード確認 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | 数万〜数百万レコード |
| 目標出力時間 | データ量依存（ストリーミングのため初期応答は即時） |
| 同時出力数上限 | Webサーバー設定依存 |

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

- JSON出力にユーザー入力が含まれる場合、json_encodeが自動的にエスケープする
- API認証・認可は呼び出し側（コントローラー）で管理すること
- 大量データのストリーミングはDoS攻撃のベクターとなりうるため、適切なレート制限を設けること

## 備考

- `__symfony_json__` という内部プレースホルダーを使用してGenerator位置を追跡
- リスト/マップの自動判定は最初のキーが0かどうかで行われる
- Generator内で `flush()` を呼び出すことでバッファの即時送信が可能
- array_walk_recursive()でオブジェクト型を検出しプレースホルダーに置換する巧妙な仕組み
- JsonSerializableインターフェースを実装するオブジェクトはjson_encode()で直接処理される

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | StreamedResponse.php | `src/Symfony/Component/HttpFoundation/StreamedResponse.php` | 親クラス。ストリーミングレスポンスの基本構造 |
| 1-2 | JsonResponse.php | `src/Symfony/Component/HttpFoundation/JsonResponse.php` | DEFAULT_ENCODING_OPTIONS定数の参照元 |

**読解のコツ**: StreamedJsonResponseはStreamedResponseを継承し、コンストラクタでストリーミングコールバック（`$this->stream(...)`）を設定する。実際のJSON出力はこのコールバック実行時に行われる。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | StreamedJsonResponse.php | `src/Symfony/Component/HttpFoundation/StreamedJsonResponse.php` | コンストラクタ（55-66行目）、stream()（68-74行目） |

**主要処理フロー**:
1. **55-66行目**: コンストラクタ - `$this->stream(...)` をコールバックとして親に渡し、Content-Type設定
2. **68-74行目**: `stream()` - エンコードオプション設定、`streamData()` 呼び出し

#### Step 3: ストリーミング処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | StreamedJsonResponse.php | `src/Symfony/Component/HttpFoundation/StreamedJsonResponse.php` | streamData()（76-91行目）、streamArray()（93-126行目）、streamIterable()（128-161行目） |

**主要処理フロー**:
- **76-91行目**: `streamData()` - データ型による分岐（配列/iterable/その他）
- **93-126行目**: `streamArray()` - array_walk_recursiveでオブジェクトをプレースホルダー置換、json_encode+explodeで分割、Generator交互出力
- **128-161行目**: `streamIterable()` - 最初のキーでリスト/マップ判定、カンマ区切りで各要素をstreamData再帰呼び出し

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

```
StreamedJsonResponse::__construct($data, $status, $headers, $encodingOptions)
    |
    +-- StreamedResponse::__construct($this->stream, $status, $headers)
    |
    [送信時]
    |
    +-- StreamedJsonResponse::stream()
            |
            +-- streamData($this->data, $jsonEncodingOptions, $keyEncodingOptions)
                    |
                    +-- [配列] streamArray($data, ...)
                    |       +-- array_walk_recursive: オブジェクト→プレースホルダー
                    |       +-- json_encode($data) → 構造骨格JSON
                    |       +-- explode(PLACEHOLDER) → JSON部分配列
                    |       +-- foreach: echo $jsonParts[i] + streamData($generator)
                    |
                    +-- [Iterable] streamIterable($data, ...)
                    |       +-- 最初のキーでリスト[/マップ{判定
                    |       +-- foreach: echo separator + streamData($item)
                    |
                    +-- [スカラー/JsonSerializable] json_encode($data)
```

### データフロー図

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

PHP配列 + Generator -----> streamData()
                              |
                    +---------+---------+
                    |                   |
              streamArray()      streamIterable()
                    |                   |
                    v                   v
           json_encode(static)    foreach Generator
           explode(placeholder)        |
                    |                  v
                    v            json_encode(item) ----> echo
           echo static part                              |
           streamData(generator)                         v
                    |                          HTTP Response Stream
                    v                          Content-Type: application/json
           echo to php://output
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| StreamedJsonResponse.php | `src/Symfony/Component/HttpFoundation/StreamedJsonResponse.php` | ソース | メインクラス（162行） |
| StreamedResponse.php | `src/Symfony/Component/HttpFoundation/StreamedResponse.php` | ソース | 親クラス（ストリーミングレスポンス） |
| JsonResponse.php | `src/Symfony/Component/HttpFoundation/JsonResponse.php` | ソース | DEFAULT_ENCODING_OPTIONS参照 |
| Response.php | `src/Symfony/Component/HttpFoundation/Response.php` | ソース | 基底レスポンスクラス |
