# 帳票設計書 34-JSONストリームライター

## 概要

本ドキュメントは、Symfony JsonStreamerコンポーネントにおけるJSONストリーミング出力の仕様を定義する帳票設計書である。`JsonStreamWriter`クラスによるオブジェクトのJSON形式ストリーミング出力について、処理フロー、データ構造、出力形式を詳細に記載する。

### 本帳票の処理概要

PHPオブジェクトをJSON形式でストリーミング出力する処理である。`JsonStreamWriter`はオブジェクトの型情報に基づいてコンパイル済みのPHPストリームライターを動的に生成・キャッシュし、`yield`ベースのジェネレーターで JSON文字列をチャンク単位で出力する。これにより、大量データのJSON変換時のメモリ使用量を抑制する。

**業務上の目的・背景**：大規模なデータセットをJSON形式でシリアライズする際、`json_encode()`による一括変換ではメモリ上にJSON文字列全体を保持する必要があるため、メモリ使用量が問題となる。`JsonStreamWriter`はデータを小さなチャンクに分割してストリーミング出力することで、メモリ効率を大幅に改善する。また、型情報に基づいてコンパイル済みのPHPコードを生成・キャッシュするため、繰り返し実行時のパフォーマンスにも優れている。

**帳票の利用シーン**：APIレスポンスの生成、大量データのエクスポート、ストリーミングレスポンスの構築など、JSON出力が必要なあらゆる場面で利用される。特に`StreamedJsonResponse`などのHTTPストリーミングレスポンスと組み合わせて使用されることが多い。

**主要な出力内容**：
1. PHPオブジェクトのプロパティをJSONオブジェクトのキー・バリューとして出力
2. コレクション（配列）をJSON配列またはJSONオブジェクトとして出力
3. スカラー値（文字列、数値、ブール値、null）をJSONプリミティブとして出力
4. BackedEnumの値（`->value`プロパティ）をJSONプリミティブとして出力
5. DateTimeInterface型を文字列に自動変換して出力
6. Union型のデータを実行時型判定により適切な形式で出力

**帳票の出力タイミング**：アプリケーションの実行時に`write()`メソッドが呼び出されたとき。初回呼び出し時にストリームライターPHPファイルが生成・キャッシュされ、以降はキャッシュされたファイルが再利用される。

**帳票の利用者**：Symfonyアプリケーションの開発者、APIエンドポイントの実装者、大量データの出力が必要な全てのPHP開発者。

## 帳票種別

ストリーミングデータ出力（JSONシリアライズ）

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| N/A | プログラムAPI | N/A | `JsonStreamWriter::write()`メソッド呼び出し |
| N/A | プログラムAPI | N/A | `JsonStreamWriter::create()`ファクトリメソッドによるインスタンス生成 |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | JSON（ストリーミング出力） |
| 用紙サイズ | N/A（メモリ上のストリーム） |
| 向き | N/A |
| ファイル名 | N/A（`Traversable&Stringable`オブジェクトとして返却） |
| 出力方法 | ジェネレーター（`yield`）ベースのストリーミング出力 |
| 文字コード | UTF-8 |

### JSON固有設定

| 項目 | 内容 |
|-----|------|
| エンコードオプション | `JSON_THROW_ON_ERROR`（エラー時に例外をスロー） |
| 最大ネスト深度 | 512（再帰的な構造のネスト上限） |
| null値の出力 | `include_null_properties`オプションで制御（デフォルト: false = null値を省略） |
| ストリーミング粒度 | プロパティ・要素単位のyield |

## 帳票レイアウト

### レイアウト概要

JSON形式のストリーミング出力。入力オブジェクトの構造に応じて、JSONオブジェクト、JSON配列、またはスカラー値として出力される。

```
┌─────────────────────────────────────┐
│  ストリーミングチャンク出力            │
│  yield '{"name":'                    │
│  yield '"John"'                      │
│  yield ',"age":'                     │
│  yield '30'                          │
│  yield ',"tags":'                    │
│  yield '['                           │
│  yield '"php"'                       │
│  yield ',"symfony"'                  │
│  yield ']'                           │
│  yield '}'                           │
│                                      │
│  結合結果:                            │
│  {"name":"John","age":30,            │
│   "tags":["php","symfony"]}          │
└─────────────────────────────────────┘
```

### ヘッダー部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| N/A | N/A | JSONストリーミング出力にヘッダー部は存在しない | N/A | N/A |

### 明細部

| No | 項目名 | 説明 | データ取得元 | 表示形式 | 列幅 |
|----|-------|------|-------------|---------|-----|
| 1 | オブジェクトプロパティ | PHPオブジェクトのプロパティ名と値 | 入力オブジェクト | `"key":value` | N/A |
| 2 | コレクション要素 | 配列/イテラブルの要素 | 入力コレクション | `[value1,value2,...]` or `{"key":value,...}` | N/A |
| 3 | スカラー値 | 文字列、数値、ブール値、null | 入力値 | JSONプリミティブ | N/A |
| 4 | BackedEnum値 | PHP BackedEnumのvalue | 入力Enum | `enum->value`のJSONエンコード | N/A |

### フッター部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| N/A | N/A | JSONストリーミング出力にフッター部は存在しない | N/A | N/A |

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| 入力データ | シリアライズ対象のPHPオブジェクト/値 | Yes |
| 型情報 | `Symfony\Component\TypeInfo\Type`オブジェクトで指定する出力対象の型 | Yes |
| オプション | `include_null_properties`等のオプション配列 | No |

### ソート順

| 優先度 | 項目 | 昇順/降順 |
|-------|------|---------|
| 1 | オブジェクトプロパティ | プロパティ定義順 |
| 2 | コレクション要素 | 入力データの順序を保持 |

### 改ページ条件

N/A（ストリーミング出力のため改ページなし）

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

### 参照テーブル一覧

| テーブル名 | 用途 | 結合条件 |
|-----------|------|---------|
| N/A | 本帳票はデータベースを直接参照しない。PHPオブジェクトを入力とする | N/A |

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

N/A

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| ストリームライターパス | `hash('xxh128', (string) $type)` + `.json.php` | N/A | 型情報のハッシュ値をファイル名に使用 |
| ネスト深度制限 | `512 - $context['depth']` | N/A | json_encodeの深度パラメータとして使用 |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[JsonStreamWriter::write呼び出し] --> B[デフォルトオプションのマージ]
    B --> C[StreamWriterGenerator::generate呼び出し]
    C --> D{キャッシュファイル存在?}
    D -->|Yes| E[キャッシュ済みPHPファイル読み込み]
    D -->|No| F[PhpGenerator: データモデル構築]
    F --> G[PhpGenerator: PHPコード生成]
    G --> H[StreamerDumper: PHPファイル書き出し]
    H --> E
    E --> I[require で PHP callable をロード]
    I --> J[callable実行: yield ベースでJSON出力]
    J --> K[Traversable & Stringable オブジェクト返却]
    K --> L[呼び出し元でイテレーション or 文字列変換]
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| NotEncodableValueException | json_encodeが失敗した場合（JsonException発生時） | `NotEncodableValueException` with original message | 入力データのJSON互換性を確認 |
| NotEncodableValueException | 最大ネスト深度（512）を超えた場合 | "Maximum stack depth exceeded" | データ構造のネストを浅くする |
| UnexpectedValueException | CompositeNode（Union型）で予期しない型の値が渡された場合 | `Unexpected "{type}" value.` | Union型の定義と実際のデータの型を確認 |
| UnsupportedException | サポートされていない型が指定された場合 | `"{type}" type is not supported.` | サポートされている型を使用する |
| RuntimeException | ReflectionClassの生成に失敗した場合 | 元のReflectionExceptionのメッセージ | クラスの存在とオートロードを確認 |
| InvalidArgumentException | 存在しないValueTransformerサービスが指定された場合 | "You have requested a non-existent value transformer service" | ValueTransformerの登録を確認 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | 制限なし（ストリーミングのためメモリ制約に依存しない） |
| 目標出力時間 | 初回: コード生成に数十ms、以降: キャッシュ済みで高速 |
| 同時出力数上限 | 制限なし（ステートレスな処理） |

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

- `JSON_THROW_ON_ERROR`フラグにより、不正なデータのサイレントな出力を防止
- ValueTransformerはPSR-11 ContainerInterface経由でアクセスされ、登録済みのサービスのみ利用可能
- 生成されたPHPファイルはキャッシュディレクトリに保存されるため、キャッシュディレクトリのアクセス制御が必要
- `ConfigCacheFactoryInterface`を使用する場合、`ReflectionClassResource`によりクラス変更時にキャッシュが自動無効化される
- 入力データはjson_encodeで処理されるため、XSS等の出力エスケープはJSONの仕様に依存する

## 備考

- `JsonStreamWriter`は`StreamWriterInterface`を実装し、`write()`メソッドで`Traversable&Stringable`オブジェクトを返す
- 返却オブジェクトは匿名クラスで`IteratorAggregate`と`Stringable`を実装。`foreach`でのチャンク単位のイテレーション、または`(string)`キャストでの一括文字列変換の両方に対応
- `create()`ファクトリメソッドにより、簡易的なインスタンス生成が可能。デフォルトで`DateTimeToStringValueTransformer`が登録される
- ストリームライターPHPファイルのパスは型情報のxxh128ハッシュで決定される
- PropertyMetadataLoaderのデコレータチェーン: `GenericTypePropertyMetadataLoader` → `DateTimeTypePropertyMetadataLoader` → `AttributePropertyMetadataLoader` → `PropertyMetadataLoader`
- `StreamedName`アトリビュートによるJSONキー名のカスタマイズに対応
- `ValueTransformer`アトリビュートによるカスタム値変換に対応

---

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

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

### 推奨読解順序

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

まず、StreamWriterが受け取る型情報とインターフェースを理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | StreamWriterInterface.php | `src/Symfony/Component/JsonStreamer/StreamWriterInterface.php` | `write(mixed $data, Type $type, array $options): Traversable&Stringable`メソッドのシグネチャ。テンプレートパラメータ`T`でオプション型を定義（行22-31） |

**読解のコツ**: `Type`はSymfony TypeInfoコンポーネントの型表現クラス。PHPの型システムを抽象化しており、`BuiltinType`, `ObjectType`, `CollectionType`, `UnionType`などのサブタイプがある。

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

`JsonStreamWriter`のメインクラスと、`write()`メソッドの処理フローを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | JsonStreamWriter.php | `src/Symfony/Component/JsonStreamer/JsonStreamWriter.php` | `write()`メソッド（行62-96）がメインの処理フロー。コード生成→キャッシュ→実行→匿名クラス返却の流れ |

**主要処理フロー**:
1. **行64**: `$options += $this->defaultOptions` - デフォルトオプションをマージ
2. **行65**: `$path = $this->streamWriterGenerator->generate($type, $options)` - ストリームライターPHPコードの生成（初回のみ）・パス取得
3. **行66**: `($this->streamWriters[$path] ??= require $path)($data, ...)` - PHPファイルをrequireしてcallableをキャッシュし実行
4. **行68-95**: 匿名クラスの定義と返却。`IteratorAggregate`（行81-84: チャンク単位イテレーション）と`Stringable`（行86-94: 一括文字列変換）を実装

#### Step 3: コード生成処理を理解する

`StreamWriterGenerator`によるPHPコードの動的生成を読み解く。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | StreamWriterGenerator.php | `src/Symfony/Component/JsonStreamer/Write/StreamWriterGenerator.php` | `generate()`メソッド（行59-71）でファイルパス計算→データモデル構築→PHPコード生成→ダンプの流れ |

**主要処理フロー**:
- **行61**: `hash('xxh128', (string) $type)` - 型情報からハッシュ値でファイル名を生成
- **行63-65**: `PhpGenerator`を遅延初期化し、データモデルを構築してPHPコードを生成
- **行68**: `StreamerDumper::dump()` - 生成したPHPコードをファイルに書き出し
- **行77-160**: `createDataModel()` - 型情報を再帰的にトラバースしてデータモデルノードツリーを構築

#### Step 4: PHPコード生成の詳細を理解する

`PhpGenerator`による実際のPHPコード生成ロジックを読み解く。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | PhpGenerator.php | `src/Symfony/Component/JsonStreamer/Write/PhpGenerator.php` | `generate()`メソッド（行46-76）とyieldベースのPHPコード生成。各データモデルノード型に応じた出力コードの生成 |

**主要処理フロー**:
- **行46-76**: `generate()` - トップレベルのPHPファイル構造（return static function + try/catch）を生成
- **行149-327**: `generateYields()` - 各ノード型に応じたyield文を生成
  - ScalarNode → `json_encode`またはリテラル（null, true/false）
  - BackedEnumNode → `enum->value`の`json_encode`
  - CompositeNode → if/elseifによる型判定分岐
  - CollectionNode → foreachループによるJSON配列/オブジェクト出力
  - ObjectNode → プロパティごとのキー・バリュー出力

#### Step 5: プロパティメタデータローダーチェーンを理解する

型情報からプロパティメタデータを読み込むデコレータチェーンを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | DateTimeTypePropertyMetadataLoader.php | `src/Symfony/Component/JsonStreamer/Mapping/Write/DateTimeTypePropertyMetadataLoader.php` | DateTimeInterface型のプロパティを自動的に文字列変換用ValueTransformerに紐付ける（行36-44） |
| 5-2 | AttributePropertyMetadataLoader.php | `src/Symfony/Component/JsonStreamer/Mapping/Write/AttributePropertyMetadataLoader.php` | `StreamedName`アトリビュートによるJSONキー名変更、`ValueTransformer`アトリビュートによるカスタム変換を処理（行40-86） |

#### Step 6: キャッシュ・ダンプ処理を理解する

`StreamerDumper`による生成コードの永続化とキャッシュ制御を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 6-1 | StreamerDumper.php | `src/Symfony/Component/JsonStreamer/StreamerDumper.php` | `dump()`メソッド（行44-70）。ConfigCacheFactory利用時はReflectionClassResourceでキャッシュ無効化を制御、非利用時はファイル存在チェックで重複生成を防止 |

**主要処理フロー**:
- **行46-59**: ConfigCacheFactory利用パス - `ReflectionClassResource`を関連クラスごとに作成し、クラス変更時の自動再生成に対応
- **行61-69**: 通常パス - ディレクトリ作成→ファイル存在チェック→存在しない場合のみ`dumpFile`

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

```
JsonStreamWriter::write()
    |
    +-- StreamWriterGenerator::generate()
    |       |
    |       +-- hash('xxh128', type) [ファイルパス決定]
    |       |
    |       +-- createDataModel() [型→データモデルノードツリー構築]
    |       |       |
    |       |       +-- PropertyMetadataLoaderInterface::load() [プロパティ情報取得]
    |       |       |       |
    |       |       |       +-- GenericTypePropertyMetadataLoader
    |       |       |       |       +-- DateTimeTypePropertyMetadataLoader
    |       |       |       |               +-- AttributePropertyMetadataLoader
    |       |       |       |                       +-- PropertyMetadataLoader
    |       |       |
    |       |       +-- [再帰: ObjectNode, CollectionNode, CompositeNode, ScalarNode, BackedEnumNode]
    |       |
    |       +-- PhpGenerator::generate() [データモデル→PHPコード文字列]
    |       |       |
    |       |       +-- generateObjectGenerators() [オブジェクト用ジェネレーター関数]
    |       |       +-- generateYields() [yield文の生成]
    |       |
    |       +-- StreamerDumper::dump() [PHPコード→ファイル書き出し]
    |               |
    |               +-- ConfigCacheFactory (optional)
    |               +-- Filesystem::dumpFile()
    |
    +-- require $path [キャッシュ済みPHPファイルの読み込み]
    |
    +-- $callable($data, $valueTransformers, $options) [JSON生成実行]
    |
    +-- 匿名クラス(Traversable & Stringable) [結果返却]
```

### データフロー図

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

PHPオブジェクト ─────────> JsonStreamWriter::write()
Type型情報      ─────────>        |
オプション配列  ─────────>        |
                                  v
                         StreamWriterGenerator
                         (初回のみコード生成)
                                  |
                     +------------+-------------+
                     |                          |
              createDataModel()          PhpGenerator
              (型→ノードツリー)          (ノード→PHPコード)
                     |                          |
                     v                          v
              DataModelNode              PHPコード文字列
              ツリー構造                        |
                                               v
                                        StreamerDumper
                                        (ファイル書き出し)
                                               |
                                               v
                                     {hash}.json.php ファイル
                                               |
                                               v
                                     require → callable取得
                                               |
                                               v
                                     callable($data, ...) 実行
                                               |
                                               v
                                     yield チャンク文字列
                                               |
                                               v
                                     Traversable & Stringable
                                     オブジェクト（JSON出力）
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| JsonStreamWriter.php | `src/Symfony/Component/JsonStreamer/JsonStreamWriter.php` | ソース | JSONストリーミング出力のメインクラス |
| StreamWriterInterface.php | `src/Symfony/Component/JsonStreamer/StreamWriterInterface.php` | ソース | ストリームライターの共通インターフェース |
| StreamWriterGenerator.php | `src/Symfony/Component/JsonStreamer/Write/StreamWriterGenerator.php` | ソース | 型情報からPHPストリームライターコードを生成 |
| PhpGenerator.php | `src/Symfony/Component/JsonStreamer/Write/PhpGenerator.php` | ソース | データモデルからPHPコード文字列を生成 |
| StreamerDumper.php | `src/Symfony/Component/JsonStreamer/StreamerDumper.php` | ソース | 生成コードのファイル書き出しとキャッシュ制御 |
| AttributePropertyMetadataLoader.php | `src/Symfony/Component/JsonStreamer/Mapping/Write/AttributePropertyMetadataLoader.php` | ソース | アトリビュートベースのプロパティメタデータ拡張 |
| DateTimeTypePropertyMetadataLoader.php | `src/Symfony/Component/JsonStreamer/Mapping/Write/DateTimeTypePropertyMetadataLoader.php` | ソース | DateTime型のValueTransformer自動適用 |
| DateTimeToStringValueTransformer.php | `src/Symfony/Component/JsonStreamer/ValueTransformer/DateTimeToStringValueTransformer.php` | ソース | DateTime→文字列変換のデフォルトValueTransformer |
| PropertyMetadataLoader.php | `src/Symfony/Component/JsonStreamer/Mapping/PropertyMetadataLoader.php` | ソース | TypeResolverベースの基盤プロパティメタデータローダー |
| GenericTypePropertyMetadataLoader.php | `src/Symfony/Component/JsonStreamer/Mapping/GenericTypePropertyMetadataLoader.php` | ソース | ジェネリック型のプロパティメタデータ解決 |
| PropertyMetadataLoaderInterface.php | `src/Symfony/Component/JsonStreamer/Mapping/PropertyMetadataLoaderInterface.php` | ソース | プロパティメタデータローダーの共通インターフェース |
| ValueTransformerInterface.php | `src/Symfony/Component/JsonStreamer/ValueTransformer/ValueTransformerInterface.php` | ソース | 値変換の共通インターフェース |
