# 機能設計書：Str/Arrヘルパー

## 1. 機能概要

### 1.1 機能名
Str/Arrヘルパー（String and Array Helpers）

### 1.2 機能説明
文字列操作（Str クラス）と配列操作（Arr クラス）のための静的メソッド群。一般的な文字列・配列処理を簡潔に記述でき、Laravel アプリケーション全体で広く使用される基盤ユーティリティ。

### 1.3 関連画面
- なし（全機能で横断的に使用されるユーティリティ）

## 2. 機能詳細

### 2.1 Str クラス（文字列操作）

#### 検索・判定

| メソッド | 説明 | 引数 | 戻り値 |
|---------|------|------|--------|
| `contains()` | 部分文字列を含むか | `$haystack, $needles, $ignoreCase` | `bool` |
| `containsAll()` | すべての部分文字列を含むか | `$haystack, $needles, $ignoreCase` | `bool` |
| `startsWith()` | 指定文字列で始まるか | `$haystack, $needles` | `bool` |
| `endsWith()` | 指定文字列で終わるか | `$haystack, $needles` | `bool` |
| `is()` | ワイルドカードパターンにマッチ | `$pattern, $value` | `bool` |
| `isJson()` | JSON形式か | `$value` | `bool` |
| `isUuid()` | UUID形式か | `$value` | `bool` |
| `isUlid()` | ULID形式か | `$value` | `bool` |

#### 抽出

| メソッド | 説明 | 引数 | 戻り値 |
|---------|------|------|--------|
| `before()` | 指定文字列の前を取得 | `$subject, $search` | `string` |
| `after()` | 指定文字列の後を取得 | `$subject, $search` | `string` |
| `beforeLast()` | 最後の指定文字列の前を取得 | `$subject, $search` | `string` |
| `afterLast()` | 最後の指定文字列の後を取得 | `$subject, $search` | `string` |
| `between()` | 2つの文字列の間を取得 | `$subject, $from, $to` | `string` |
| `substr()` | 部分文字列を取得 | `$string, $start, $length` | `string` |
| `charAt()` | 指定位置の文字を取得 | `$subject, $index` | `string\|false` |

#### 変換

| メソッド | 説明 | 引数 | 戻り値 |
|---------|------|------|--------|
| `camel()` | キャメルケースに変換 | `$value` | `string` |
| `studly()` | スタディケースに変換 | `$value` | `string` |
| `snake()` | スネークケースに変換 | `$value, $delimiter` | `string` |
| `kebab()` | ケバブケースに変換 | `$value` | `string` |
| `title()` | タイトルケースに変換 | `$value` | `string` |
| `headline()` | ヘッドラインに変換 | `$value` | `string` |
| `slug()` | スラッグに変換 | `$title, $separator, $language, $dictionary` | `string` |
| `upper()` | 大文字に変換 | `$value` | `string` |
| `lower()` | 小文字に変換 | `$value` | `string` |
| `ucfirst()` | 先頭を大文字に | `$string` | `string` |
| `lcfirst()` | 先頭を小文字に | `$string` | `string` |
| `ascii()` | ASCIIに変換 | `$value, $language` | `string` |

#### 加工

| メソッド | 説明 | 引数 | 戻り値 |
|---------|------|------|--------|
| `replace()` | 置換 | `$search, $replace, $subject, $caseSensitive` | `string` |
| `replaceFirst()` | 最初の一致を置換 | `$search, $replace, $subject` | `string` |
| `replaceLast()` | 最後の一致を置換 | `$search, $replace, $subject` | `string` |
| `limit()` | 文字数制限 | `$value, $limit, $end` | `string` |
| `words()` | 単語数制限 | `$value, $words, $end` | `string` |
| `padLeft()` | 左パディング | `$value, $length, $pad` | `string` |
| `padRight()` | 右パディング | `$value, $length, $pad` | `string` |
| `padBoth()` | 両側パディング | `$value, $length, $pad` | `string` |
| `trim()` | 両端トリム | `$value, $characters` | `string` |
| `ltrim()` | 左トリム | `$value, $characters` | `string` |
| `rtrim()` | 右トリム | `$value, $characters` | `string` |
| `chopStart()` | 先頭から除去 | `$subject, $needle` | `string` |
| `chopEnd()` | 末尾から除去 | `$subject, $needle` | `string` |
| `wrap()` | 前後を文字列で囲む | `$value, $before, $after` | `string` |
| `unwrap()` | 前後の文字列を除去 | `$value, $before, $after` | `string` |
| `finish()` | 末尾に追加（重複なし） | `$value, $cap` | `string` |
| `start()` | 先頭に追加（重複なし） | `$value, $prefix` | `string` |
| `deduplicate()` | 連続文字の重複除去 | `$string, $characters` | `string` |

#### 生成

| メソッド | 説明 | 引数 | 戻り値 |
|---------|------|------|--------|
| `uuid()` | UUID生成 | - | `UuidInterface` |
| `ulid()` | ULID生成 | - | `Ulid` |
| `random()` | ランダム文字列生成 | `$length` | `string` |
| `password()` | パスワード生成 | `$length, $letters, $numbers, $symbols, $spaces` | `string` |

#### Markdown

| メソッド | 説明 | 引数 | 戻り値 |
|---------|------|------|--------|
| `markdown()` | MarkdownをHTMLに変換 | `$string, $options` | `string` |
| `inlineMarkdown()` | インラインMarkdownをHTMLに変換 | `$string, $options` | `string` |

### 2.2 Arr クラス（配列操作）

#### アクセス

| メソッド | 説明 | 引数 | 戻り値 |
|---------|------|------|--------|
| `get()` | ドット記法で値取得 | `$array, $key, $default` | `mixed` |
| `set()` | ドット記法で値設定 | `$array, $key, $value` | `array` |
| `has()` | ドット記法でキー存在確認 | `$array, $keys` | `bool` |
| `first()` | 最初の要素取得 | `$array, $callback, $default` | `mixed` |
| `last()` | 最後の要素取得 | `$array, $callback, $default` | `mixed` |
| `pull()` | 値を取得して削除 | `$array, $key, $default` | `mixed` |

#### 変換

| メソッド | 説明 | 引数 | 戻り値 |
|---------|------|------|--------|
| `dot()` | ドット記法の配列に変換 | `$array, $prepend` | `array` |
| `undot()` | ドット記法から復元 | `$array` | `array` |
| `flatten()` | フラット化 | `$array, $depth` | `array` |
| `collapse()` | 配列の配列を結合 | `$array` | `array` |
| `pluck()` | 指定キーの値を抽出 | `$array, $value, $key` | `array` |
| `map()` | 各要素を変換 | `$array, $callback` | `array` |
| `mapWithKeys()` | キー付きで変換 | `$array, $callback` | `array` |
| `keyBy()` | 指定キーで再インデックス | `$array, $keyBy` | `array` |

#### 絞り込み

| メソッド | 説明 | 引数 | 戻り値 |
|---------|------|------|--------|
| `where()` | 条件で絞り込み | `$array, $callback` | `array` |
| `only()` | 指定キーのみ取得 | `$array, $keys` | `array` |
| `except()` | 指定キー以外取得 | `$array, $keys` | `array` |
| `forget()` | キーを削除（破壊的） | `$array, $keys` | `void` |

#### 判定

| メソッド | 説明 | 引数 | 戻り値 |
|---------|------|------|--------|
| `accessible()` | 配列アクセス可能か | `$value` | `bool` |
| `exists()` | キーが存在するか | `$array, $key` | `bool` |
| `isList()` | リスト形式か | `$array` | `bool` |
| `isAssoc()` | 連想配列か | `$array` | `bool` |

#### ソート

| メソッド | 説明 | 引数 | 戻り値 |
|---------|------|------|--------|
| `sort()` | ソート | `$array, $callback` | `array` |
| `sortDesc()` | 降順ソート | `$array, $callback` | `array` |
| `sortRecursive()` | 再帰ソート | `$array, $options, $descending` | `array` |
| `shuffle()` | シャッフル | `$array, $seed` | `array` |

#### その他

| メソッド | 説明 | 引数 | 戻り値 |
|---------|------|------|--------|
| `add()` | 存在しなければ追加 | `$array, $key, $value` | `array` |
| `prepend()` | 先頭に追加 | `$array, $value, $key` | `array` |
| `query()` | クエリ文字列に変換 | `$array` | `string` |
| `random()` | ランダム要素取得 | `$array, $number, $preserveKeys` | `mixed` |
| `wrap()` | 配列でラップ | `$value` | `array` |
| `crossJoin()` | クロス結合 | `...$arrays` | `array` |
| `divide()` | キーと値を分離 | `$array` | `array` |
| `take()` | 先頭/末尾から取得 | `$array, $limit` | `array` |
| `from()` | 配列に変換 | `$items` | `array` |

### 2.3 ビジネスルール

1. **静的メソッド**
   - すべてのメソッドは静的メソッドとして提供
   - インスタンス化不要で直接呼び出し可能

2. **ドット記法**
   - Arr クラスはドット記法（`user.profile.name`）でネストアクセス
   - 深いネストも安全にアクセス可能

3. **キャッシュ**
   - `camel()`, `snake()`, `studly()` は結果をキャッシュ
   - パフォーマンス向上のため同じ入力には同じ結果を返す

4. **Macroable**
   - 両クラスとも `Macroable` トレイトを使用
   - 独自メソッドを動的に追加可能

## 3. 入出力仕様

### 3.1 Str クラス入力

| 項目 | 型 | 説明 |
|-----|-----|------|
| string | `string` | 操作対象の文字列 |

### 3.2 Arr クラス入力

| 項目 | 型 | 説明 |
|-----|-----|------|
| array | `array\|ArrayAccess` | 操作対象の配列 |
| key | `string\|int` | アクセスキー（ドット記法対応） |

## 4. 依存関係

### 4.1 内部依存

| パッケージ | 用途 |
|-----------|------|
| Illuminate\Support\Traits\Macroable | マクロ拡張 |
| Illuminate\Support\Collection | コレクション連携 |
| Illuminate\Support\Stringable | Fluent 文字列 |

### 4.2 外部依存

| パッケージ | 用途 |
|-----------|------|
| ramsey/uuid | UUID生成 |
| symfony/uid | ULID生成 |
| voku/portable-ascii | ASCII変換 |
| league/commonmark | Markdown変換 |

## 5. エラー処理

| エラー種別 | 発生条件 | 処理内容 |
|-----------|---------|---------|
| InvalidArgumentException | 不正な引数 | 例外スロー |

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

### 6.1 推奨読解順序

#### Str クラス (`Support/Str.php`)

1. **クラス定義とキャッシュ**
   - **23-25行目**: クラス定義、Macroable トレイト
   - **32行目**: INVISIBLE_CHARACTERS 定数（不可視文字リスト）
   - **39-53行目**: キャッシュ用静的プロパティ（snake, camel, studly）

2. **文字列抽出**
   - **94-97行目**: `after()` - 文字列の後を取得
   - **106-119行目**: `afterLast()` - 最後の文字列の後を取得
   - **153-162行目**: `before()` - 文字列の前を取得
   - **171-184行目**: `beforeLast()` - 最後の文字列の前を取得
   - **194-201行目**: `between()` - 2つの文字列の間

3. **ケース変換**
   - **226-233行目**: `camel()` - キャメルケース（キャッシュ付き）
   - **260-269行目**: `chopStart()` - 先頭除去
   - **278-287行目**: `chopEnd()` - 末尾除去

4. **検索・判定**
   - **297-322行目**: `contains()` - 部分文字列チェック
   - **332-341行目**: `containsAll()` - すべての部分文字列チェック
   - **396-413行目**: `endsWith()` - 終端チェック

5. **変換・加工**
   - **364-367行目**: `convertCase()` - ケース変換
   - **376-387行目**: `deduplicate()` - 連続文字除去
   - **470-475行目**: `finish()` - 末尾追加
   - **485-488行目**: `wrap()` - 前後囲み
   - **498-507行目**: `unwrap()` - 前後除去

#### Arr クラス (`Collections/Arr.php`)

1. **基本操作**
   - **27-30行目**: `accessible()` - 配列アクセス判定
   - **67-74行目**: `add()` - 存在しなければ追加
   - **118-131行目**: `collapse()` - 配列結合

2. **ドット記法操作**
   - **183-205行目**: `dot()` - ドット記法変換
   - **213-222行目**: `undot()` - ドット記法復元
   - **479-502行目**: `get()` - ドット記法取得
   - **408-443行目**: `forget()` - ドット記法削除

3. **検索・絞り込み**
   - **291-314行目**: `first()` - 最初の要素
   - **328-335行目**: `last()` - 最後の要素
   - **344-351行目**: `take()` - 先頭/末尾から取得
   - **231-236行目**: `except()` - 指定キー以外

4. **変換**
   - **456-469行目**: `from()` - 配列変換

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

```
Str::camel($value)
├── キャッシュ確認 (static::$camelCache)
├── キャッシュヒット -> 返却
└── キャッシュミス
    └── static::studly($value)
        └── lcfirst() -> キャッシュ保存 -> 返却

Arr::get($array, 'user.profile.name')
├── 直接キー存在確認
├── 存在 -> 返却
└── 不存在
    └── ドット分割
        └── 各セグメントで再帰的にアクセス
            └── 見つかれば返却 / なければデフォルト

Arr::dot($array)
└── $flatten (再帰クロージャ)
    ├── 各キー・値をループ
    ├── 値が配列なら再帰
    └── 値がスカラーなら結果に追加
```

### 6.3 データフロー図

```
[入力文字列/配列]
    |
    v
[Str/Arr 静的メソッド]
    |
    +-- Str: 文字列変換・抽出・判定
    +-- Arr: 配列アクセス・変換・絞り込み
    |
    v
[結果（文字列/配列/bool）]
```

### 6.4 読解のコツ

- **キャッシュ**: `camel()`, `snake()`, `studly()` は静的プロパティでキャッシュ
- **ドット記法**: `explode('.', $key)` で分割し、再帰的にアクセス
- **PHP 8 機能**: `array_find_key()`, `array_any()`, `array_first()` などの新関数を活用
- **外部ライブラリ**: UUID/ULID生成は ramsey/uuid, symfony/uid に委譲

### 6.5 関連ファイル一覧

| ファイルパス | 種別 | 役割 |
|------------|------|------|
| `src/Illuminate/Support/Str.php` | Class | 文字列操作 |
| `src/Illuminate/Collections/Arr.php` | Class | 配列操作 |
| `src/Illuminate/Support/Stringable.php` | Class | Fluent文字列 |

## 7. 使用例

```php
use Illuminate\Support\Str;
use Illuminate\Support\Arr;

// === Str クラス ===

// ケース変換
Str::camel('hello_world');     // 'helloWorld'
Str::studly('hello_world');    // 'HelloWorld'
Str::snake('helloWorld');      // 'hello_world'
Str::kebab('helloWorld');      // 'hello-world'

// 文字列抽出
Str::before('hello@world.com', '@');  // 'hello'
Str::after('hello@world.com', '@');   // 'world.com'
Str::between('[Hello]', '[', ']');     // 'Hello'

// 判定
Str::contains('Hello World', 'World');     // true
Str::startsWith('Hello World', 'Hello');   // true
Str::endsWith('Hello World', 'World');     // true

// 加工
Str::limit('Long text here', 10);    // 'Long text...'
Str::slug('Hello World!');           // 'hello-world'
Str::finish('/path', '/');           // '/path/'
Str::wrap('value', '"');             // '"value"'

// 生成
Str::uuid();        // UUID
Str::ulid();        // ULID
Str::random(16);    // ランダム文字列

// === Arr クラス ===

// ドット記法アクセス
$array = ['user' => ['profile' => ['name' => 'John']]];
Arr::get($array, 'user.profile.name');  // 'John'
Arr::get($array, 'user.email', 'N/A');  // 'N/A'

// 配列操作
Arr::only(['a' => 1, 'b' => 2, 'c' => 3], ['a', 'b']);  // ['a' => 1, 'b' => 2]
Arr::except(['a' => 1, 'b' => 2], ['b']);               // ['a' => 1]
Arr::pluck([['id' => 1], ['id' => 2]], 'id');          // [1, 2]

// 変換
Arr::dot(['user' => ['name' => 'John']]);  // ['user.name' => 'John']
Arr::flatten([1, [2, [3, 4]]]);            // [1, 2, 3, 4]

// 判定
Arr::has($array, 'user.profile.name');  // true
Arr::isList([1, 2, 3]);                 // true
Arr::isAssoc(['a' => 1]);               // true
```

## 8. 改版履歴

| バージョン | 日付 | 変更内容 |
|-----------|------|---------|
| 1.0.0 | - | 初版作成 |
