# 機能設計書 40-replace_every

## 概要

本ドキュメントは、Hack Standard Library (HSL) の `Str\replace_every` 関数の機能設計について記載する。この関数は複数の検索/置換ペアを一括で適用し、文字列内のすべての該当箇所を置換する機能を提供する。

### 本機能の処理概要

`Str\replace_every` 関数は、対象文字列（haystack）に対して、複数の検索文字列と置換文字列のペア（dict形式）を一括で適用するための関数である。置換は指定された順序で適用され、置換後の値も後続の検索対象となる（再帰的置換）。

**業務上の目的・背景**：複数の文字列置換を効率的に実行する必要がある場面は多く存在する。例えば、HTMLエンティティの変換、特殊文字のエスケープ、複数の略語展開などが挙げられる。個別に `Str\replace` を呼び出すよりも、この関数を使うことでコードが簡潔になり、パフォーマンスも向上する可能性がある。

**機能の利用シーン**：
- HTMLエンティティの一括変換
- 特殊文字のエスケープ処理
- テンプレート変数の一括置換
- 複数の略語を同時に展開
- データマイグレーションでの値変換

**主要な処理内容**：
1. 対象文字列と置換ペアのdictを受け取る
2. dictの順序で検索/置換を適用
3. 置換後の値も後続の検索対象となる（再帰的）
4. すべての置換が完了した文字列を返却

**関連システム・外部連携**：本関数は内部で `_Str\replace_every_l` 組み込み関数を使用する。大文字小文字を無視した置換には `Str\replace_every_ci` 関数を使用する。

**権限による制御**：本機能は純粋関数であり、権限による制御は不要である。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | 画面なし | - | 本プロジェクトはUIを持たないライブラリのため該当なし |

## 機能種別

計算処理 / 文字列変換

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| $haystack | string | Yes | 検索・置換対象の文字列 | なし |
| $replacements | KeyedContainer<string, string> | Yes | 検索文字列をキー、置換文字列を値とするdict | なし |

### 入力データソース

関数呼び出し時の引数として直接指定される。

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| 戻り値 | string | すべての置換が適用された文字列 |

### 出力先

関数の戻り値として呼び出し元に返却される。

## 処理フロー

### 処理シーケンス

```
1. 入力パラメータを受け取る
2. $replacementsをdict型に変換
3. _Str\replace_every_l関数を呼び出し
   └─ dictの順序で置換を適用
   └─ 置換後の値も後続の検索対象となる
4. 結果を返却
```

### フローチャート

```mermaid
flowchart TD
    A[開始] --> B{置換dictが空?}
    B -->|Yes| C[元の文字列を返却]
    B -->|No| D[dictの順序で置換を適用]
    D --> E[置換後の値も検索対象]
    E --> F[すべての置換が完了]
    F --> G[置換後の文字列を返却]
    C --> H[終了]
    G --> H
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | 大文字小文字区別 | 検索は大文字小文字を区別する | 常時 |
| BR-02 | 順序依存 | dictの順序で置換が適用される | 常時 |
| BR-03 | 再帰的置換 | 置換後の値も後続の検索対象となる | 常時 |
| BR-04 | 重複マッチ優先 | 複数のキーが同じ位置でマッチする場合、dict内で先に定義されたキーが優先 | 常時 |
| BR-05 | 空dict | 空のdictの場合は元の文字列を返却 | $replacements === dict[] |

### 計算ロジック

- dictの各エントリを順番に処理
- `dict['a' => 'b', 'b' => 'c']` は `dict['a' => 'c']` と等価（'a' -> 'b' の後、'b' -> 'c' が適用される）
- `dict['b' => 'c', 'a' => 'b']` では 'a' -> 'b' のみ適用される（順序が逆のため）

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

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

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | - | 本関数は例外をスローしない | - |

### リトライ仕様

リトライ処理は不要（純粋関数のため）。

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

トランザクション管理は不要（データベース操作なし）。

## パフォーマンス要件

- 計算量: O(n * m * k) - nは対象文字列長、mは置換ペア数、kは各キーの出現回数
- 純粋関数属性 `[]:` により副作用なしが保証され、最適化の対象となる
- 多数の置換ペアを指定する場合はパフォーマンスに注意

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

- 入力文字列はそのまま処理されるため、エスケープ処理は呼び出し元の責任
- 意図しない再帰的置換に注意（例: 'a' -> 'aa' で無限ループのような挙動）
- 純粋関数のため、その他のセキュリティリスクはない

## 備考

- 大文字小文字を無視した置換には `Str\replace_every_ci` を使用
- 再帰的置換を避けたい場合は `Str\replace_every_nonrecursive` を使用
- ロケール対応版として `Str\replace_every_l` も提供されている
- 置換の順序が重要なため、dictの定義順に注意が必要

---

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

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

### 推奨読解順序

#### Step 1: メイン実装を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | transform.php | `src/str/transform.php` | replace_every関数のメイン実装 |

**主要処理フロー**:
- **199-217行目**: 関数のドキュメントコメント（動作の詳細説明）
- **218-223行目**: 関数シグネチャと実装

**読解のコツ**:
- `KeyedContainer<string, string>` 型はdictやMapを受け入れる
- 内部で `dict($replacements)` を呼び出してdict型に変換している
- 再帰的置換の挙動についてはドキュメントコメントに詳細な説明がある

#### Step 2: 非再帰版を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | transform.php | `src/str/transform.php` | replace_every_nonrecursive関数の実装 |

**主要処理フロー**:
- **251-270行目**: 非再帰版の実装（置換後の値は再検索されない）

#### Step 3: ロケール対応版を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | transform_l.php | `src/str/transform_l.php` | replace_every_l関数のロケール対応版実装 |

**主要処理フロー**:
- **164-192行目**: Localeオブジェクトを受け取り、ロケール対応の一括置換を実行

#### Step 4: テストコードで動作を確認する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | StrTransformTest.php | `tests/str/StrTransformTest.php` | replace_everyのテストケース |

**主要処理フロー**:
- **328-406行目**: provideReplaceEveryテストデータ（順序依存、再帰的置換の例を含む）
- **399-406行目**: testReplaceEveryテストメソッド

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

```
Str\replace_every($haystack, $replacements)
    │
    ├─ dict($replacements)
    │      └─ KeyedContainerをdict型に変換
    │
    └─ _Str\replace_every_l($haystack, dict)
           └─ HHVM組み込み関数
```

### データフロー図

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

$haystack ───────────────┬──▶
                         │
$replacements ───────────┼──▶ dict変換 ──▶ _Str\replace_every_l() ──▶ 置換後の文字列
  dict[                  │        (順序通りに適用、再帰的)
    'hello' => 'goodbye',│
    'world' => 'cruel world'
  ]                      │

例: "hello world" ──▶ "goodbye cruel world"

再帰的置換の例:
  dict['a' => 'b', 'b' => 'c'] を "abc" に適用
  → まず 'a' -> 'b' で "bbc"
  → 次に 'b' -> 'c' で "ccc"
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| transform.php | `src/str/transform.php` | ソース | replace_every関数のメイン実装 |
| transform_l.php | `src/str/transform_l.php` | ソース | ロケール対応版replace_every_l関数の実装 |
| StrTransformTest.php | `tests/str/StrTransformTest.php` | テスト | replace_everyのユニットテスト |
