# 機能設計書：HTTPクライアント

## 1. 機能概要

### 1.1 機能名
HTTPクライアント（HTTP Client）

### 1.2 機能説明
外部APIやWebサービスとのHTTP通信を行うためのクライアント機能。Guzzle HTTPクライアントをラップし、Fluent APIによる直感的なリクエスト構築、レスポンス処理、リトライ機能、テスト用のフェイク機能を提供する。

### 1.3 関連画面
- なし（バックエンド専用機能）

## 2. 機能詳細

### 2.1 処理フロー

```
[アプリケーション]
       |
       v
[Http Facade / Factory]
       |
       v
[PendingRequest] ──設定──> URL, Headers, Body, Options
       |
       v
[Guzzle HTTP Client]
       |
       v
[Response] ──変換──> JSON, Collection, Fluent
       |
       v
[結果をアプリケーションに返却]
```

### 2.2 クラス構成

| クラス名 | 役割 |
|---------|------|
| Factory | HTTPクライアントのファクトリー・エントリーポイント |
| PendingRequest | リクエスト構築用のFluent API |
| Response | レスポンスのラッパー |
| Pool | 並列リクエストのプール |
| ConnectionException | 接続エラー例外 |
| RequestException | リクエストエラー例外 |

### 2.3 主要メソッド

#### Factory

| メソッド | 説明 | 引数 | 戻り値 |
|---------|------|------|--------|
| `baseUrl()` | ベースURLを設定 | `$url` | `PendingRequest` |
| `withHeaders()` | ヘッダーを追加 | `$headers` | `PendingRequest` |
| `withToken()` | Bearer トークンを設定 | `$token, $type` | `PendingRequest` |
| `timeout()` | タイムアウトを設定 | `$seconds` | `PendingRequest` |
| `retry()` | リトライ設定 | `$times, $sleepMs, $when, $throw` | `PendingRequest` |
| `fake()` | テスト用フェイク登録 | `$callback` | `$this` |
| `pool()` | 並列リクエストプール作成 | `$callback` | `Pool` |

#### PendingRequest

| メソッド | 説明 | 引数 | 戻り値 |
|---------|------|------|--------|
| `get()` | GETリクエスト送信 | `$url, $query` | `Response` |
| `post()` | POSTリクエスト送信 | `$url, $data` | `Response` |
| `put()` | PUTリクエスト送信 | `$url, $data` | `Response` |
| `patch()` | PATCHリクエスト送信 | `$url, $data` | `Response` |
| `delete()` | DELETEリクエスト送信 | `$url, $data` | `Response` |
| `asJson()` | JSON形式でリクエスト | - | `$this` |
| `asForm()` | フォーム形式でリクエスト | - | `$this` |
| `attach()` | ファイル添付 | `$name, $contents, $filename, $headers` | `$this` |
| `withBody()` | リクエストボディを設定 | `$content, $contentType` | `$this` |
| `withOptions()` | Guzzleオプション設定 | `$options` | `$this` |

#### Response

| メソッド | 説明 | 引数 | 戻り値 |
|---------|------|------|--------|
| `body()` | レスポンスボディ取得 | - | `string` |
| `json()` | JSONとしてパース | `$key, $default` | `mixed` |
| `object()` | オブジェクトとしてパース | - | `object` |
| `collect()` | Collectionとして取得 | `$key` | `Collection` |
| `fluent()` | Fluentオブジェクトとして取得 | `$key` | `Fluent` |
| `status()` | ステータスコード取得 | - | `int` |
| `successful()` | 成功判定（2xx） | - | `bool` |
| `failed()` | 失敗判定 | - | `bool` |
| `throw()` | エラー時に例外スロー | - | `$this` |
| `throwIf()` | 条件付き例外スロー | `$condition` | `$this` |

### 2.4 ビジネスルール

1. **リクエスト構築ルール**
   - Fluent APIでチェーンメソッド呼び出し
   - 最終的に `get()`, `post()` 等のメソッドで送信
   - デフォルトタイムアウトは30秒

2. **リトライルール**
   - `retry()` で回数と待機時間を指定
   - 条件付きリトライが可能（`$when` コールバック）
   - エクスポネンシャルバックオフ対応

3. **レスポンス処理ルール**
   - ステータスコード 2xx は成功
   - 4xx, 5xx はエラー
   - `throw()` 呼び出しでエラー時に例外化

4. **並列リクエストルール**
   - `pool()` で複数リクエストを並列実行
   - Guzzle の Promise を内部利用

## 3. 入出力仕様

### 3.1 入力

| 項目 | 型 | 説明 |
|-----|-----|------|
| url | `string` | リクエスト先URL |
| data | `array` | リクエストデータ |
| headers | `array` | HTTPヘッダー |
| options | `array` | Guzzleオプション |

### 3.2 出力

| 項目 | 型 | 説明 |
|-----|-----|------|
| response | `Response` | レスポンスオブジェクト |

## 4. 依存関係

### 4.1 内部依存

| パッケージ | 用途 |
|-----------|------|
| Illuminate\Support\Collection | レスポンスデータのコレクション化 |
| Illuminate\Support\Fluent | レスポンスデータのFluent化 |
| Illuminate\Support\Traits\Macroable | マクロ拡張サポート |

### 4.2 外部依存

| パッケージ | 用途 |
|-----------|------|
| guzzlehttp/guzzle | HTTP通信の実装 |
| psr/http-message | PSR-7 HTTP メッセージインターフェース |

## 5. エラー処理

| エラー種別 | 発生条件 | 処理内容 |
|-----------|---------|---------|
| ConnectionException | 接続失敗・タイムアウト | 例外スロー（リトライ可能） |
| RequestException | HTTPエラーレスポンス | `throw()` 呼び出し時に例外化 |
| JsonException | JSONパース失敗 | 例外スロー |

## 6. 設定項目

```php
// PendingRequest デフォルト設定
[
    'timeout' => 30,
    'connect_timeout' => 10,
    'http_errors' => false,
]
```

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

### 7.1 推奨読解順序

1. **Factory クラス** (`Http/Client/Factory.php`)
   - **1-50行目**: クラス定義とプロパティ（fakeHandlers, recording 等）
   - **89-130行目**: `fake()` メソッド - テスト用フェイク登録
   - **200-250行目**: `pool()` メソッド - 並列リクエスト
   - **320-330行目**: `__call()` メソッド - PendingRequest への委譲

2. **PendingRequest クラス** (`Http/Client/PendingRequest.php`)
   - **1-100行目**: プロパティ定義（baseUrl, options, headers 等）
   - **150-200行目**: `asJson()`, `asForm()` 等のコンテンツタイプ設定
   - **300-400行目**: `withHeaders()`, `withToken()` 等のヘッダー設定
   - **500-600行目**: `get()`, `post()` 等のHTTPメソッド実装
   - **700-800行目**: `send()` メソッド - 実際のリクエスト送信
   - **900-1000行目**: `retry()` メソッド - リトライロジック

3. **Response クラス** (`Http/Client/Response.php`)
   - **17-80行目**: プロパティとコンストラクタ
   - **87-117行目**: `body()`, `json()` メソッド - ボディ取得
   - **125-152行目**: `object()`, `collect()`, `fluent()` メソッド - 変換
   - **192-195行目**: `status()` メソッド - ステータスコード取得

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

```
Http::get($url)
└── Factory::__call('get', [$url])
    └── Factory::newPendingRequest()
        └── PendingRequest::get($url)
            └── PendingRequest::send('GET', $url)
                └── PendingRequest::buildClient()
                    └── GuzzleHttp\Client::request()
                        └── Response::__construct($psrResponse)
```

### 7.3 データフロー図

```
[リクエスト設定]
    |
    +-- baseUrl, headers, token, timeout, etc.
    |
    v
[PendingRequest]
    |
    +-- オプションをマージ
    +-- Guzzle クライアント構築
    |
    v
[Guzzle HTTP Client]
    |
    +-- HTTP通信実行
    |
    v
[PSR-7 Response]
    |
    v
[Laravel Response ラッパー]
    |
    +-- json() -> 配列
    +-- collect() -> Collection
    +-- fluent() -> Fluent
    |
    v
[アプリケーションで利用]
```

### 7.4 読解のコツ

- **Fluent API パターン**: 各設定メソッドは `$this` を返却し、メソッドチェーンを実現
- **Macroable**: `Macroable` トレイトにより、独自メソッドを動的に追加可能
- **Factory パターン**: `Factory` クラスが `PendingRequest` を生成し、設定を委譲
- **PSR-7 ラッパー**: `Response` は PSR-7 レスポンスをラップし、Laravel らしいAPIを提供

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

| ファイルパス | 種別 | 役割 |
|------------|------|------|
| `src/Illuminate/Http/Client/Factory.php` | Factory | HTTPクライアントのエントリーポイント |
| `src/Illuminate/Http/Client/PendingRequest.php` | Request | リクエスト構築・送信 |
| `src/Illuminate/Http/Client/Response.php` | Response | レスポンスラッパー |
| `src/Illuminate/Http/Client/Pool.php` | Pool | 並列リクエスト管理 |
| `src/Illuminate/Http/Client/ConnectionException.php` | Exception | 接続例外 |
| `src/Illuminate/Http/Client/RequestException.php` | Exception | リクエスト例外 |
| `src/Illuminate/Http/Client/Events/RequestSending.php` | Event | リクエスト送信イベント |
| `src/Illuminate/Http/Client/Events/ResponseReceived.php` | Event | レスポンス受信イベント |

## 8. 使用例

```php
use Illuminate\Support\Facades\Http;

// 基本的なGETリクエスト
$response = Http::get('https://api.example.com/users');
$users = $response->json();

// POSTリクエスト（JSON）
$response = Http::post('https://api.example.com/users', [
    'name' => 'John Doe',
    'email' => 'john@example.com',
]);

// 認証付きリクエスト
$response = Http::withToken('your-api-token')
    ->timeout(60)
    ->get('https://api.example.com/protected');

// リトライ設定
$response = Http::retry(3, 100)
    ->get('https://api.example.com/unstable');

// ファイルアップロード
$response = Http::attach('file', file_get_contents('document.pdf'), 'document.pdf')
    ->post('https://api.example.com/upload');

// 並列リクエスト
$responses = Http::pool(fn ($pool) => [
    $pool->get('https://api.example.com/users'),
    $pool->get('https://api.example.com/posts'),
    $pool->get('https://api.example.com/comments'),
]);

// テスト用フェイク
Http::fake([
    'api.example.com/*' => Http::response(['data' => 'mocked'], 200),
]);
```

## 9. 改版履歴

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