# 機能設計書 28-PropertyAccess

## 概要

本ドキュメントは、Symfony PropertyAccessコンポーネントの機能設計を記述する。このコンポーネントはオブジェクトや配列のプロパティにプロパティパス式を使ってアクセスする機能を提供する。

### 本機能の処理概要

**業務上の目的・背景**：Webアプリケーション開発において、オブジェクトや配列のプロパティに動的にアクセスする必要性は頻繁に発生する。フォーム処理、データバインディング、テンプレートエンジンなど、プロパティパスの文字列表現からオブジェクトの値を読み書きする機能は基盤的な機能である。本コンポーネントはプロパティパス式（例：`address.city`、`[0].name`、`user?.address`）による統一的なアクセス手段を提供する。

**機能の利用シーン**：Formコンポーネントでのデータバインディング、Serializerでのオブジェクトプロパティアクセス、Validatorでの制約対象プロパティアクセス、Twigテンプレートでの変数アクセス、設定値のネスト構造へのアクセス。

**主要な処理内容**：
1. PropertyAccessorによるプロパティパスを使った値の読み取り（getValue）と書き込み（setValue）
2. PropertyPathによるプロパティパス文字列の解析（ドット記法、ブラケット記法、null-safe記法対応）
3. ReadInfo/WriteInfoによるプロパティの読み書き方法の自動検出（getter/setter、マジックメソッド、公開プロパティ）
4. キャッシュ機構（APCu、CacheItemPool）によるプロパティアクセス情報のキャッシング
5. Adder/Removerパターンによるコレクションプロパティの差分更新
6. Null-safe演算子（?）による安全なプロパティチェーン

**関連システム・外部連携**：PropertyInfo（プロパティ読み書き情報抽出）、Form（データバインディング）、Serializer（プロパティアクセス）、Cache（APCuキャッシュ）と連携する。

**権限による制御**：マジックメソッド（__get、__set、__call）の使用をMAGIC_GET/MAGIC_SET/MAGIC_CALLフラグで制御可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | （直接的な画面関連なし） | - | バックエンドのプロパティアクセス処理 |

## 機能種別

ユーティリティ / プロパティアクセス

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| objectOrArray | object/array | Yes | アクセス対象のオブジェクトまたは配列 | object型またはarray型 |
| propertyPath | string/PropertyPathInterface | Yes | プロパティパス式 | 有効なプロパティパス文字列 |
| value | mixed | Yes（setValue時） | 設定する値 | 対象プロパティの型に適合 |

### 入力データソース

オブジェクト、配列、プロパティパス文字列

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| getValue結果 | mixed | プロパティパスで指定されたプロパティの値 |
| isReadable結果 | bool | プロパティが読み取り可能かどうか |
| isWritable結果 | bool | プロパティが書き込み可能かどうか |

### 出力先

PHP値

## 処理フロー

### 処理シーケンス

```
1. プロパティパス解析
   └─ PropertyPathクラスで文字列をパース（要素リスト、インデックスフラグ、null-safeフラグ）
2. プロパティパスに沿って読み取り
   └─ readPropertiesUntil()でルートから順にプロパティをたどる
3. 各プロパティ要素の読み取り
   └─ isIndex: readIndex()で配列/ArrayAccessアクセス
   └─ !isIndex: readProperty()でオブジェクトプロパティアクセス
4. プロパティアクセス方法の決定
   └─ getReadInfo()でReflectionExtractorから読み取り方法を取得（キャッシュ付き）
5. 値の書き込み（setValue時）
   └─ 読み取りでパスをたどり、最後の要素から逆順にwrite実行
```

### フローチャート

```mermaid
flowchart TD
    A[getValue呼び出し] --> B{単純プロパティ?}
    B -->|Yes| C[readProperty直接呼び出し]
    B -->|No| D[PropertyPath解析]
    D --> E[readPropertiesUntil]
    E --> F{isIndex?}
    F -->|Yes| G[readIndex - 配列アクセス]
    F -->|No| H{isNullSafe?}
    H -->|Yes, not object| I[null返却]
    H -->|No or object| J[readProperty]
    J --> K{ReadInfo取得}
    K --> L{TYPE_METHOD?}
    L -->|Yes| M[object->method()]
    L -->|No| N{TYPE_PROPERTY?}
    N -->|Yes| O[object->property]
    G --> P[次の要素]
    M --> P
    O --> P
    I --> P
    P --> Q{最後の要素?}
    Q -->|No| E
    Q -->|Yes| R[値返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-28-01 | 単純パス最適化 | 特殊文字（.、[、?）を含まない場合、PropertyPathパースをスキップし直接readProperty | getValue/setValue時 |
| BR-28-02 | 参照チェーン最適化 | IS_REF_CHAINEDフラグにより、参照チェーンが途切れない限り不要なsetValue処理をスキップ | setValue時 |
| BR-28-03 | Adder/Remover | コレクションプロパティはaddXxx/removeXxxメソッドで差分更新 | setValueでiterable値の書き込み時 |
| BR-28-04 | DateTime自動変換 | setValueでDateTimeとDateTimeImmutableの自動相互変換を試行 | TypeError発生時 |
| BR-28-05 | Null-safe | プロパティパスに?付きの場合、対象がnullでも例外を投げずnullを返す | getValue/isReadable時 |
| BR-28-06 | マジックメソッド制御 | MAGIC_GET/MAGIC_SET/MAGIC_CALLフラグでマジックメソッドの使用を制限 | readProperty/writeProperty時 |

### 計算ロジック

特になし。

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

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

直接的なデータベース操作は行わない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | NoSuchPropertyException | プロパティが存在しないまたは非公開 | 正しいプロパティ名を指定、またはgetter/setterを追加 |
| - | NoSuchIndexException | 配列インデックスが存在しない | 有効なインデックスを指定 |
| - | UnexpectedTypeException | パス途中の値がobject/arrayでない | パス構造を修正 |
| - | UninitializedPropertyException | 型付きプロパティが未初期化 | プロパティを初期化するかデフォルト値を設定 |
| - | InvalidTypeException | 設定値の型がプロパティの型と不一致 | 正しい型の値を設定 |

### リトライ仕様

リトライは不要。

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

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

## パフォーマンス要件

PropertyPathのパース結果とReadInfo/WriteInfoの取得結果はインメモリキャッシュおよびCacheItemPool（APCu等）にキャッシュされる。これにより、同一クラスの同一プロパティへの繰り返しアクセスが高速化される。

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

- マジックメソッドフラグにより、意図しないマジックメソッド経由のプロパティアクセスを防止
- DISALLOW_MAGIC_METHODSで全マジックメソッドを無効化可能

## 備考

PropertyAccessコンポーネントはSymfonyのFormコンポーネントの基盤技術であり、フォームデータのオブジェクトバインディングに不可欠な機能を提供する。

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | PropertyPathInterface.php | `src/Symfony/Component/PropertyAccess/PropertyPathInterface.php` | プロパティパスの契約 |
| 1-2 | PropertyPath.php | `src/Symfony/Component/PropertyAccess/PropertyPath.php` | プロパティパスの解析実装 |
| 1-3 | PropertyAccessorInterface.php | `src/Symfony/Component/PropertyAccess/PropertyAccessorInterface.php` | getValue/setValue/isReadable/isWritableの契約 |

**読解のコツ**: PropertyPathは文字列をパースし、各要素（element）をリスト化する。各要素にはisIndex（配列アクセス）とisNullSafe（null-safe）のフラグが付く。例：`address.city` -> ["address", "city"]（isIndex: [false, false]）。`[0].name` -> ["0", "name"]（isIndex: [true, false]）。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | PropertyAccessor.php | `src/Symfony/Component/PropertyAccess/PropertyAccessor.php` | PropertyAccessorの中核実装 |

**主要処理フロー**:
1. **81-93行目**: コンストラクタでmagicMethodsFlags、ignoreInvalidIndices/Property、readInfoExtractor/writeInfoExtractorを初期化
2. **95-110行目**: getValue()で単純パス最適化判定後、readPropertiesUntilで値を辿る
3. **112-188行目**: setValue()でreadPropertiesUntilでパスを辿り、逆順にwriteIndex/writePropertyで値を設定。IS_REF_CHAINEDによる最適化
4. **278-348行目**: readPropertiesUntil()でパス要素を順次読み取り。isIndex分岐、null-safe対応、参照チェーン追跡
5. **383-465行目**: readProperty()でReadInfoに基づくメソッド呼び出しまたはプロパティ直接アクセス
6. **517-559行目**: writeProperty()でWriteInfoに基づく書き込み（メソッド、プロパティ、adder/remover）
7. **564-595行目**: writeCollection()でadder/removerによるコレクション差分更新

#### Step 3: キャッシュ機構を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | PropertyAccessor.php | `src/Symfony/Component/PropertyAccess/PropertyAccessor.php` | getReadInfo/getWriteInfo/getPropertyPathのキャッシュ |

**主要処理フロー**:
- **470-496行目**: getReadInfo()でインメモリキャッシュ -> CacheItemPool -> ReflectionExtractor の3段階
- **597-625行目**: getWriteInfo()で同様の3段階キャッシュ
- **653-678行目**: getPropertyPath()でPropertyPathインスタンスのキャッシュ
- **685-703行目**: createCache()でAPCuアダプターの作成

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

```
PropertyAccessor::getValue()
    |
    +-- [単純パス] readProperty()
    |
    +-- [複合パス] getPropertyPath()  [キャッシュ付き]
            |
            +-- readPropertiesUntil()
                   |
                   +-- readIndex()              [配列アクセス]
                   |       +-- ArrayAccess/array
                   |
                   +-- readProperty()           [プロパティアクセス]
                          +-- getReadInfo()     [キャッシュ付き]
                          |       +-- ReflectionExtractor
                          +-- TYPE_METHOD: object->method()
                          +-- TYPE_PROPERTY: object->property

PropertyAccessor::setValue()
    |
    +-- getPropertyPath()
    +-- readPropertiesUntil()
    +-- [逆順ループ]
           +-- writeIndex()                     [配列書き込み]
           +-- writeProperty()                  [プロパティ書き込み]
                  +-- getWriteInfo()            [キャッシュ付き]
                  +-- TYPE_METHOD: object->method(value)
                  +-- TYPE_PROPERTY: object->property = value
                  +-- TYPE_ADDER_AND_REMOVER: writeCollection()
```

### データフロー図

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

object/array + path ------> PropertyAccessor::getValue() --> mixed (値)
                                |
                           PropertyPath解析
                                |
                           readPropertiesUntil
                                |
                           readIndex / readProperty
                                |
                           getReadInfo [キャッシュ]

object/array + path + value > PropertyAccessor::setValue()
                                |
                           readPropertiesUntil
                                |
                           [逆順] writeIndex / writeProperty
                                |
                           getWriteInfo [キャッシュ]
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| PropertyAccessor.php | `src/Symfony/Component/PropertyAccess/PropertyAccessor.php` | ソース | PropertyAccessorの中核実装 |
| PropertyAccessorInterface.php | `src/Symfony/Component/PropertyAccess/PropertyAccessorInterface.php` | ソース | アクセサインターフェース |
| PropertyPath.php | `src/Symfony/Component/PropertyAccess/PropertyPath.php` | ソース | プロパティパス解析 |
| PropertyPathInterface.php | `src/Symfony/Component/PropertyAccess/PropertyPathInterface.php` | ソース | パスインターフェース |
| PropertyAccess.php | `src/Symfony/Component/PropertyAccess/PropertyAccess.php` | ソース | ビルダーファクトリ |
| PropertyAccessorBuilder.php | `src/Symfony/Component/PropertyAccess/PropertyAccessorBuilder.php` | ソース | ビルダークラス |
| Exception/ | `src/Symfony/Component/PropertyAccess/Exception/` | ソース | 例外クラス群 |
