# 機能設計書 29-search_last

## 概要

本ドキュメントは、Hack Standard Library（HSL）の `Str\search_last` 関数の機能設計書です。文字列内の部分文字列（needle）の最後の出現位置を検索する機能を提供します。

### 本機能の処理概要

`Str\search_last` 関数は、haystack文字列内でneedle文字列が最後に出現する位置を返す関数です。見つからない場合はnullを返します。PHPの `strrpos` 関数に相当しますが、型安全な実装となっています。

**業務上の目的・背景**：文字列処理において、特定のパターンが最後に出現する位置を特定することは様々な場面で必要となります。例えば、ファイルパスからファイル名を抽出（最後の `/` の位置）、拡張子を抽出（最後の `.` の位置）、最後の区切り文字以降のデータ取得などに使用されます。この関数は大文字小文字を区別した検索を行います。

**機能の利用シーン**：
- ファイルパスからファイル名を抽出（最後の `/` または `\` の位置）
- ファイル名から拡張子を抽出（最後の `.` の位置）
- URLからクエリストリング以降を抽出
- 最後の区切り文字の位置特定
- 重複するパターンの最終出現位置の検出

**主要な処理内容**：
1. haystack文字列、needle文字列、オプションのoffsetを受け取る
2. 内部実装関数 `_Str\strrpos_l` に処理を委譲
3. needleが見つかった場合はその位置（0始まりのインデックス）を返す
4. needleが見つからない場合はnullを返す

**関連システム・外部連携**：外部システムとの連携はありません。純粋な文字列操作関数です。

**権限による制御**：特に権限による制御はありません。純粋関数（Pure function）として実装されており、副作用はありません。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | 画面なし | - | 本プロジェクト（Hack Standard Library）はUIを持たないライブラリプロジェクトのため、画面機能マッピングは該当しません。 |

## 機能種別

計算処理 / 文字列検索

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| $haystack | string | Yes | 検索対象の文字列 | なし（空文字列も許可） |
| $needle | string | Yes | 検索する部分文字列 | なし（空文字列も許可） |
| $offset | int | No | 検索開始位置（デフォルト: 0） | 範囲外の場合例外 |

### 入力データソース

関数呼び出し時の引数として直接渡される文字列データ。

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| 戻り値 | ?int | needleの最後の出現位置（0始まり）。見つからない場合はnull |

### 出力先

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

## 処理フロー

### 処理シーケンス

```
1. 関数呼び出し
   └─ $haystack, $needle, $offset を引数として受け取る
2. オフセット検証
   └─ offsetが範囲外の場合は例外をスロー
3. 内部実装への委譲
   └─ _Str\strrpos_l($haystack, $needle, $offset) を呼び出し
4. 結果の変換
   └─ 負の戻り値をnullに変換
5. 結果の返却
   └─ ?int 値を返す
```

### フローチャート

```mermaid
flowchart TD
    A[開始: search_last呼び出し] --> B{offsetは有効範囲内?}
    B -->|No| C[InvalidArgumentException]
    B -->|Yes| D[strrpos_lを呼び出し]
    D --> E{position >= 0?}
    E -->|Yes| F[positionを返す]
    E -->|No| G[nullを返す]
    F --> H[終了]
    G --> H
    C --> H
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | 大文字小文字区別 | 検索は大文字小文字を区別する | 常時 |
| BR-02 | 0始まりインデックス | 位置は0始まりで返される | 常時 |
| BR-03 | 負のオフセット | 負のoffsetは末尾からの検索開始位置を示し、後方検索 | $offset < 0 |
| BR-04 | 範囲外オフセット | offsetが範囲外の場合は例外 | abs($offset) > length |
| BR-05 | 最後の出現 | 複数マッチがある場合、最も後ろの位置を返す | 常時 |

### 計算ロジック

- 正のoffset: 先頭からoffsetバイト目以降で最後に出現する位置を検索
- 負のoffset: 末尾からabs(offset)バイト目から後方に向かって検索

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

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

本機能はデータベース操作を行いません。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | InvalidArgumentException | offsetが範囲外 | 有効な範囲のoffsetを指定 |

### リトライ仕様

該当なし。

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

該当なし（データベース操作なし）。

## パフォーマンス要件

- 計算量: O(n * m) ここで n はhaystack長、m はneedle長
- 純粋関数として実装されており、キャッシュや状態を持たない
- コンテキスト係数 `[]:` が指定されており、副作用なしで実行可能

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

- 純粋関数のため、セキュリティ上の懸念は最小限
- 入力文字列のサニタイズは呼び出し元の責任

## 備考

- 単に含まれているかを確認するだけなら `Str\contains()` を使用すること
- 最初の出現位置を検索するには `Str\search()` を使用すること
- PHPの `strrpos` と異なり、見つからない場合は `false` ではなく `null` を返す

---

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

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

### 推奨読解順序

#### Step 1: 公開APIを理解する

まず、外部から呼び出される公開関数のインターフェースを理解します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | introspect.php | `src/str/introspect.php` | 公開関数 `search_last` の定義とドキュメントコメント |

**主要処理フロー**:
- **231-246行目**: 関数のドキュメントコメント
- **231-233行目**: 機能概要。needleの最後の出現位置を返し、見つからない場合はnull
- **235-238行目**: offsetの説明。負の値の場合は末尾から後方検索
- **240-241行目**: 関連関数への参照（contains, search）
- **243行目**: PHPの `strrpos` に相当することを明記
- **247-258行目**: 関数シグネチャと実装
- **252行目**: haystack_lengthを計算（未使用だが将来の拡張用か）
- **253行目**: `_Str\strrpos_l` への委譲
- **254-257行目**: 負の戻り値をnullに変換

**読解のコツ**: 252行目で `$haystack_length` を計算していますが、現在の実装では直接使用されていません。内部実装での境界チェック用と考えられます。

#### Step 2: ロケール対応版を確認

ロケール対応版の実装を確認します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | introspect_l.php | `src/str/introspect_l.php` | ロケール対応版 `search_last_l` の実装 |

**主要処理フロー**:
- **246-274行目**: `search_last_l` の定義
- **268行目**: `length_l` でロケール対応の長さを取得
- **269行目**: `_Str\strrpos_l` にlocaleを渡して呼び出し

#### Step 3: テストケースを理解する

テストコードから具体的な動作仕様を確認します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | StrIntrospectTest.php | `tests/str/StrIntrospectTest.php` | テストケースから具体的な入出力パターンを理解 |

**主要処理フロー**:
- **303-316行目**: `provideSearchLast` メソッドでテストデータを定義
- **304行目**: `'foofoofoo'` で `'foo'` を検索すると最後の位置 `6`
- **305行目**: 存在しないneedleでは `null`
- **306行目**: offset 3からの検索では見つからない（`null`）
- **307行目**: 大文字小文字が異なると `null`
- **309-314行目**: 負のoffsetと`'hello world'`での `'world'` 検索テスト
- **318-327行目**: テスト実行メソッド（var_dumpでデバッグ出力あり）
- **340-343行目**: 例外テスト

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

```
Str\search_last($haystack, $needle, $offset)
    │
    └─ _Str\strrpos_l($haystack, $needle, $offset)
           │
           └─ [HHVM内部実装]
                  └─ 後方検索アルゴリズム
```

### データフロー図

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

$haystack (string) ───┬──▶ search_last() ───▶ ?int (position or null)
                      │         │
$needle (string) ─────┤         │
                      │         ▼
$offset (int) ────────┘   _Str\strrpos_l()
                              │
                              ▼
                        [後方検索]
                              │
                              ▼
                        [位置 or -1]
                              │
                              ▼
                        [負なら null に変換]
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| introspect.php | `src/str/introspect.php` | ソース | 公開API定義 |
| introspect_l.php | `src/str/introspect_l.php` | ソース | ロケール対応実装 |
| StrIntrospectTest.php | `tests/str/StrIntrospectTest.php` | テスト | ユニットテスト |
