# 機能設計書 31-ObjectMapper

## 概要

本ドキュメントは、Symfony ObjectMapperコンポーネントの機能設計を記述する。ObjectMapperは、あるオブジェクトを別のオブジェクトにマッピング（変換）する機能を提供するコンポーネントである。

### 本機能の処理概要

ObjectMapperは、ソースオブジェクトからターゲットオブジェクトへのプロパティマッピングを、PHPアトリビュート（`#[Map]`）ベースのメタデータ定義に基づいて自動的に実行する機能を提供する。

**業務上の目的・背景**：アプリケーション開発において、異なるレイヤー間でのデータ変換（例：エンティティからDTO、APIレスポンスオブジェクトへの変換）は頻繁に発生する。ObjectMapperは、この変換ロジックをアトリビュートベースの宣言的な記述で実現し、手動のマッピングコードを削減する。これにより、レイヤー間の分離を維持しつつ、変換処理の保守性を向上させる。

**機能の利用シーン**：DTO（Data Transfer Object）パターンの実装において、エンティティからDTOへの変換、フォームデータからエンティティへの変換、APIリクエスト/レスポンスの変換など、オブジェクト間のデータマッピングが必要な場面で使用される。CQRS（Command Query Responsibility Segregation）パターンにおけるRead/Writeモデルの相互変換にも活用できる。

**主要な処理内容**：
1. ソースオブジェクトのクラスおよびプロパティに付与された`#[Map]`アトリビュートからメタデータを読み取る
2. メタデータに基づいてターゲットクラスを特定し、インスタンスを生成する
3. 条件（`if`パラメータ）に基づいてマッピングの適用可否を判定する
4. 変換関数（`transform`パラメータ）を適用してプロパティ値を変換する
5. ソースオブジェクトのプロパティ値をターゲットオブジェクトのプロパティに設定する
6. ネストされたオブジェクトの再帰的マッピングをレイジーゴーストを用いて遅延実行する

**関連システム・外部連携**：PropertyAccessコンポーネント（オプション）を使用してプロパティの読み書きを行う。VarExporterコンポーネントのLazyObjectInterfaceと連携してレイジーオブジェクトの初期化を処理する。DIコンテナ（PsrのContainerInterface）を通じたtransform/conditionのcallableの解決にも対応する。

**権限による制御**：本機能自体には権限制御の仕組みはない。マッピング対象のプロパティのアクセス可否はPHPのアクセス修飾子およびPropertyAccessorの設定に依存する。

## 関連画面

本機能はバックエンドのデータ変換処理であり、直接関連する画面はない。

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | 画面関連なし |

## 機能種別

データ変換処理（Object-to-Object Mapping）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| source | object | Yes | マッピング元のソースオブジェクト | オブジェクト型であること |
| target | object\|string\|null | No | マッピング先のオブジェクトまたはクラス名。nullの場合はメタデータから自動解決 | オブジェクトまたは存在するクラス名 |

### 入力データソース

- ソースオブジェクトのプロパティ値
- `#[Map]`アトリビュートによるメタデータ定義（クラスレベルおよびプロパティレベル）
- オプション: PropertyAccessorによるプロパティアクセス
- オプション: DIコンテナからのtransform/conditionのcallable解決

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| mappedTarget | object | マッピングされたターゲットオブジェクト |

### 出力先

呼び出し元のPHPコード（メソッドの戻り値として返却）

## 処理フロー

### 処理シーケンス

```
1. map()メソッドの呼び出し
   └─ WeakMapによる再帰参照トラッキングの初期化
2. doMap()による実際のマッピング処理
   ├─ ソースオブジェクトのメタデータ読み取り
   ├─ ターゲットクラスの特定とインスタンス生成
   ├─ クラスレベルのtransform適用
   ├─ コンストラクタ引数の収集
   ├─ プロパティごとのマッピング処理
   │   ├─ メタデータの読み取り
   │   ├─ 条件（if）の評価
   │   ├─ transform関数の適用
   │   └─ ネストされたオブジェクトの再帰マッピング（レイジーゴースト）
   ├─ コンストラクタの呼び出し
   └─ プロパティへの値の設定
3. マッピング済みオブジェクトの返却
```

### フローチャート

```mermaid
flowchart TD
    A[map開始] --> B[WeakMap初期化]
    B --> C[メタデータ読み取り]
    C --> D{ターゲット指定あり?}
    D -->|Yes| E[ターゲットクラス確認]
    D -->|No| F[メタデータからターゲット解決]
    F --> E
    E --> G[ターゲットインスタンス生成]
    G --> H{クラスレベルtransformあり?}
    H -->|Yes| I[transform適用]
    H -->|No| J[コンストラクタ引数収集]
    I --> J
    J --> K[プロパティループ開始]
    K --> L{メタデータあり?}
    L -->|Yes| M{条件 if 評価}
    L -->|No| N{ターゲットにプロパティ存在?}
    M -->|通過| O[transform適用・値取得]
    M -->|拒否| K
    N -->|Yes| P[プロパティ値コピー]
    N -->|No| Q{ネスト対象?}
    Q -->|Yes| R[再帰マッピング]
    Q -->|No| K
    O --> K
    P --> K
    R --> K
    K --> S[コンストラクタ呼び出し]
    S --> T[プロパティ設定]
    T --> U[マッピング済みオブジェクト返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-31-01 | ターゲット自動解決 | ターゲットが未指定の場合、ソースの`#[Map]`アトリビュートのtargetパラメータからターゲットクラスを自動解決する | target引数がnullの場合 |
| BR-31-02 | 条件付きマッピング | `if`パラメータで指定されたcallableがtrueを返す場合のみマッピングを実行する | Map属性にifパラメータが設定されている場合 |
| BR-31-03 | 値変換 | `transform`パラメータで指定されたcallableにより値を変換する | Map属性にtransformパラメータが設定されている場合 |
| BR-31-04 | 再帰参照防止 | WeakMapを使って循環参照を検出し、既にマッピング済みのオブジェクトを再利用する | ネストされたオブジェクトのマッピング時 |
| BR-31-05 | レイジーゴースト生成 | ネストされたオブジェクトのマッピングにReflectionClassのnewLazyGhostを使用し遅延初期化する | ネストされたオブジェクトの再帰マッピング時 |
| BR-31-06 | 曖昧マッピング検出 | 複数の`#[Map]`属性が同一ソースに一致する場合、MappingExceptionをスローする | enforceUniqueフラグが有効な場合 |

### 計算ロジック

特になし。

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

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

本機能はデータベース操作を直接行わない。

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| - | - | - | データベース操作なし |

### テーブル別操作詳細

該当なし。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | MappingException | ターゲットが見つからない | ソースクラスに`#[Map]`属性を付与してターゲットを指定する |
| - | MappingException | ターゲットクラスが存在しない | 正しいクラス名を指定する |
| - | MappingException | 複数のMap属性が曖昧にマッチ | ifパラメータで条件を指定する |
| - | MappingTransformException | transformの結果がオブジェクトでない | transform関数がオブジェクトを返すようにする |
| - | NoSuchPropertyException | ソースにプロパティが存在しない | プロパティ名の指定を修正する |
| - | NoSuchCallableException | callableが無効または見つからない | callable名を修正するか、DIコンテナに登録する |

### リトライ仕様

リトライは不要（同期処理のため）。

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

トランザクション管理は行わない。

## パフォーマンス要件

- ReflectionObjectMapperMetadataFactoryはメタデータをキャッシュすることで、同一クラスへのリフレクションアクセスを最小化している
- ネストされたオブジェクトのマッピングはレイジーゴースト（遅延初期化）を使用し、実際にアクセスされるまで初期化を遅延する
- WeakMapを使用することで、マッピング済みオブジェクトのメモリリークを防止する

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

- ソースオブジェクトの全てのpublicプロパティがデフォルトでマッピング対象となるため、機密情報の意図しない伝搬に注意が必要
- transform関数としてユーザー入力由来のcallableを指定しないこと

## 備考

- ObjectMapperはSymfony 7.1で導入された比較的新しいコンポーネントである
- PHP 8.4のレイジーゴースト機能（`ReflectionClass::newLazyGhost`）を活用している
- PropertyAccessコンポーネントとの連携はオプションであり、未指定の場合は直接プロパティアクセスを行う

---

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

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

### 推奨読解順序

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

まず、マッピングのメタデータ構造とアトリビュート定義を理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | Mapping.php | `src/Symfony/Component/ObjectMapper/Metadata/Mapping.php` | マッピング情報を保持するデータクラス。target, source, if, transformの4つのプロパティを持つ |
| 1-2 | Map.php | `src/Symfony/Component/ObjectMapper/Attribute/Map.php` | `#[Map]`アトリビュートの定義。クラスおよびプロパティに付与してマッピングルールを宣言する |
| 1-3 | ObjectMapperInterface.php | `src/Symfony/Component/ObjectMapper/ObjectMapperInterface.php` | マッパーの公開インターフェース。map()メソッドの型定義とドキュメント |

**読解のコツ**: PHP 8アトリビュートの仕組みを理解しておくと、`#[Map(target: TargetDTO::class)]`のようなメタデータ定義の意味が把握しやすい。

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

処理の起点となるObjectMapperクラスのmap()メソッドを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | ObjectMapper.php | `src/Symfony/Component/ObjectMapper/ObjectMapper.php` | メインのマッピング処理クラス |

**主要処理フロー**:
1. **38-45行目**: コンストラクタ。MetadataFactory、PropertyAccessor、callable用コンテナの注入
2. **47-59行目**: `map()`メソッド。WeakMapの初期化と`doMap()`への委譲
3. **61-228行目**: `doMap()`メソッド。メタデータ読み取り、ターゲット生成、プロパティマッピングの全フロー
4. **260-298行目**: `getSourceValue()`メソッド。ネストオブジェクトの再帰マッピングとレイジーゴースト生成

#### Step 3: メタデータファクトリを理解する

メタデータの読み取りロジックを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | ObjectMapperMetadataFactoryInterface.php | `src/Symfony/Component/ObjectMapper/Metadata/ObjectMapperMetadataFactoryInterface.php` | メタデータファクトリのインターフェース |
| 3-2 | ReflectionObjectMapperMetadataFactory.php | `src/Symfony/Component/ObjectMapper/Metadata/ReflectionObjectMapperMetadataFactory.php` | リフレクションベースのメタデータ読み取り実装 |

**主要処理フロー**:
- **27-48行目**: `create()`メソッド。ReflectionClassとReflectionPropertyから`#[Map]`アトリビュートを読み取り、Mappingオブジェクトの配列を返す。キャッシュ機構あり

#### Step 4: Transform/Conditionの仕組みを理解する

値変換と条件評価の仕組みを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | TransformCallableInterface.php | `src/Symfony/Component/ObjectMapper/TransformCallableInterface.php` | 値変換callableのインターフェース |
| 4-2 | ConditionCallableInterface.php | `src/Symfony/Component/ObjectMapper/ConditionCallableInterface.php` | 条件評価callableのインターフェース |

**主要処理フロー**:
- **351-372行目** (ObjectMapper.php): `applyTransforms()`メソッド。transform配列を順次適用
- **378-399行目** (ObjectMapper.php): `getCallable()`メソッド。文字列callableのDIコンテナ経由解決

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

```
ObjectMapper::map()
    |
    +-- ObjectMapper::doMap()
    |       |
    |       +-- ObjectMapperMetadataFactoryInterface::create()
    |       |       +-- ReflectionObjectMapperMetadataFactory::create()
    |       |
    |       +-- ObjectMapper::getMapTarget()
    |       |       +-- ObjectMapper::getCallable() [条件callable解決]
    |       |       +-- ObjectMapper::call() [条件評価]
    |       |
    |       +-- ObjectMapper::applyTransforms()
    |       |       +-- ObjectMapper::getCallable() [transform callable解決]
    |       |       +-- ObjectMapper::call() [値変換実行]
    |       |
    |       +-- ObjectMapper::getRawValue()
    |       |       +-- PropertyAccessorInterface::getValue() [オプション]
    |       |
    |       +-- ObjectMapper::getSourceValue()
    |       |       +-- ObjectMapper::applyTransforms()
    |       |       +-- ObjectMapper::doMap() [再帰呼び出し / レイジーゴースト]
    |       |
    |       +-- ObjectMapper::storeValue()
    |       +-- PropertyAccessorInterface::setValue() [オプション]
    |
    +-- WeakMap [再帰参照トラッキング]
```

### データフロー図

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

ソースオブジェクト ──────> ObjectMapper::map()
                              |
#[Map]アトリビュート ────> ReflectionMetadataFactory::create()
                              |
                              v
                        ターゲットクラス特定
                              |
                              v
                        インスタンス生成 (newInstanceWithoutConstructor)
                              |
                              v
                        プロパティマッピング ────────> マッピング済み
                         (transform/条件付き)           ターゲットオブジェクト
                              |
DIコンテナ ──────────> callable解決
(TransformCallable,           |
 ConditionCallable)     コンストラクタ呼び出し
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| ObjectMapper.php | `src/Symfony/Component/ObjectMapper/ObjectMapper.php` | ソース | メインのマッピング処理クラス |
| ObjectMapperInterface.php | `src/Symfony/Component/ObjectMapper/ObjectMapperInterface.php` | ソース | マッパーの公開インターフェース |
| ObjectMapperAwareInterface.php | `src/Symfony/Component/ObjectMapper/ObjectMapperAwareInterface.php` | ソース | ObjectMapper注入用インターフェース |
| Map.php | `src/Symfony/Component/ObjectMapper/Attribute/Map.php` | ソース | マッピングアトリビュート定義 |
| Mapping.php | `src/Symfony/Component/ObjectMapper/Metadata/Mapping.php` | ソース | マッピングメタデータクラス |
| ObjectMapperMetadataFactoryInterface.php | `src/Symfony/Component/ObjectMapper/Metadata/ObjectMapperMetadataFactoryInterface.php` | ソース | メタデータファクトリインターフェース |
| ReflectionObjectMapperMetadataFactory.php | `src/Symfony/Component/ObjectMapper/Metadata/ReflectionObjectMapperMetadataFactory.php` | ソース | リフレクションベースのメタデータ読み取り |
| TransformCallableInterface.php | `src/Symfony/Component/ObjectMapper/TransformCallableInterface.php` | ソース | 値変換callableインターフェース |
| ConditionCallableInterface.php | `src/Symfony/Component/ObjectMapper/ConditionCallableInterface.php` | ソース | 条件評価callableインターフェース |
| MappingException.php | `src/Symfony/Component/ObjectMapper/Exception/MappingException.php` | ソース | マッピング例外 |
| MappingTransformException.php | `src/Symfony/Component/ObjectMapper/Exception/MappingTransformException.php` | ソース | 変換例外 |
| NoSuchPropertyException.php | `src/Symfony/Component/ObjectMapper/Exception/NoSuchPropertyException.php` | ソース | プロパティ不在例外 |
| NoSuchCallableException.php | `src/Symfony/Component/ObjectMapper/Exception/NoSuchCallableException.php` | ソース | callable不在例外 |
