# 機能設計書 28-バリアント切替

## 概要

本ドキュメントは、Shopwareストアフロントにおけるバリアント切替機能の設計を記述したものである。本機能は、商品のバリアント（サイズ、色などのオプション）を切り替える際に、対応するバリアント商品を検索し、その商品ページURLを返却する機能を提供する。

### 本機能の処理概要

バリアント切替機能は、ユーザーが商品詳細ページでオプション（サイズ、色など）を選択した際に、選択されたオプションの組み合わせに一致するバリアント商品を検索し、そのバリアントの詳細ページURLをJSON形式で返却する。

**業務上の目的・背景**：ECサイトにおいて、同一商品で異なるバリエーション（サイズ違い、色違いなど）を持つ商品は非常に多い。顧客がスムーズにバリアントを切り替えられることで、商品選択の利便性が向上し、購入転換率の向上に寄与する。また、各バリアントが独自のURLを持つことで、SEO効果も期待できる。

**機能の利用シーン**：
- 商品詳細ページでサイズを変更する場合
- 商品詳細ページで色を変更する場合
- 複数のオプションを組み合わせて選択する場合
- 在庫のあるバリアントを探す場合

**主要な処理内容**：
1. 親商品IDと選択オプションを受け取る
2. オプションの組み合わせに一致するバリアントを検索
3. 完全一致するバリアントがない場合、オプションを減らして再検索
4. 見つかったバリアントの詳細ページURLを生成
5. JSON形式でURLとproductIdを返却

**関連システム・外部連携**：
- AbstractFindProductVariantRoute: バリアント検索API
- SeoUrlPlaceholderHandler: SEO URL生成
- CacheTagCollector: キャッシュタグ管理

**権限による制御**：
- 公開商品は認証不要でアクセス可能
- HTTPキャッシュ対応

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 3 | 商品詳細ページ | 主画面 | サイズや色などのバリアント切り替え（ProductController::switch） |
| 36 | プロパティ管理 | 参照画面 | 商品プロパティ（色、サイズなど）の管理 |

## 機能種別

CRUD操作（Read） / AJAX / JSON API

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| productId | string | Yes | 親商品ID（URLパラメータ） | UUID形式 |
| switched | string | No | 切り替えられたオプショングループID | UUID形式 |
| options | string (JSON) | No | 選択されたオプションのJSON配列 | JSON形式、各値はUUID |

### オプションの形式

```json
{
  "グループID1": "オプションID1",
  "グループID2": "オプションID2"
}
```

### 入力データソース

- URLルートパラメータ（productId）
- HTTPリクエストパラメータ（Query: switched, options）
- SalesChannelContext（販売チャネル情報）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| url | string | バリアント商品の詳細ページURL（SEO URL） |
| productId | string | 見つかったバリアント商品のID |

### 出力形式

```json
{
  "url": "https://example.com/product/variant-slug",
  "productId": "uuid-of-variant"
}
```

### 出力先

- JSONレスポンス（Content-Type: application/json）

## 処理フロー

### 処理シーケンス

```
1. switch（GET /detail/{productId}/switch）
   └─ productIdをURLから取得
   └─ switchedグループIDを取得
   └─ optionsをJSONデコード
   └─ AbstractFindProductVariantRoute.loadを呼び出し
      └─ 指定オプションでバリアント検索
      └─ 見つからない場合、オプションを減らして再検索
      └─ VariantNotFoundExceptionの場合は元のproductIdを使用
   └─ SeoUrlPlaceholderHandlerでURLを生成
   └─ JsonResponseを返却
```

### フローチャート

```mermaid
flowchart TD
    A[バリアント切替リクエスト] --> B[productIdをURLから取得]
    B --> C[switchedグループIDを取得]
    C --> D[optionsをJSONデコード]
    D --> E{JSONデコード成功?}
    E -->|No| F[options = 空配列]
    E -->|Yes| G[FindProductVariantRoute.load]
    F --> G

    G --> H{バリアント見つかった?}
    H -->|Yes| I[variantIdを取得]
    H -->|No| J[親productIdを使用]

    I --> K[SEO URL生成]
    J --> K

    K --> L[JsonResponse生成]
    L --> M[url, productIdを返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-28-01 | 完全一致優先 | 指定されたオプションの組み合わせに完全一致するバリアントを優先的に返却 | 常時 |
| BR-28-02 | フォールバック検索 | 完全一致がない場合、switchedグループ以外のオプションを除外して再検索 | 完全一致なしの場合 |
| BR-28-03 | 親商品フォールバック | バリアントが見つからない場合は親商品IDを返却 | バリアント不在の場合 |
| BR-28-04 | HTTPキャッシュ | HTTP_CACHE属性によりキャッシュ可能 | 常時 |
| BR-28-05 | XmlHttpRequest | AJAX呼び出しとして動作 | 常時 |

### 計算ロジック

#### バリアント検索アルゴリズム

```
1. 全オプションでproduct.optionIdsを検索
2. 見つからない場合:
   while (options.count > 1) {
     switchedGroup以外のオプションを1つ除外
     再検索
     見つかった場合はbreak
   }
3. 見つからない場合はVariantNotFoundException
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| バリアント検索 | product | SELECT | parentIdと optionIdsでバリアント検索 |

### テーブル別操作詳細

#### product

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | id | parentId = {productId} AND optionIds CONTAINS {optionId} | 各オプションIDに対してフィルタ |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| PRODUCT_VARIANT_NOT_FOUND | バリアント不在 | 指定オプションに一致するバリアントがない | 親商品IDをフォールバックとして返却 |
| PRODUCT_NOT_FOUND | 商品不在 | 親商品が存在しない | 例外として処理 |
| JSON_DECODE_ERROR | JSONエラー | optionsパラメータのJSONが不正 | 空配列として処理 |
| PRODUCT_INVALID_OPTIONS_PARAMETER | パラメータ不正 | optionsの各値が文字列でない | 例外スロー |

### リトライ仕様

リトライ処理は行わない。バリアントが見つからない場合は親商品にフォールバック。

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

本機能は参照のみのため、トランザクション制御は不要。

## パフォーマンス要件

- レスポンス: 300ms以内
- HTTPキャッシュ対応により、キャッシュヒット時は高速レスポンス
- 検索はproductテーブルのインデックスを使用

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

- 公開商品のみ検索対象（販売チャネルの設定に従う）
- HTTPキャッシュ対応（パーソナライズされていない情報のみ）
- JSONデコードエラーは安全にハンドリング

## 備考

- XmlHttpRequest（AJAX）として動作し、画面遷移なしでバリアント情報を取得
- 返却されたURLにJavaScriptでリダイレクトすることで、バリアントページに遷移
- v6.8.0.0以降、CACHE_REWORK機能によりキャッシュタグの管理方法が変更される可能性あり

---

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

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

### 推奨読解順序

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

バリアント検索に関わるデータ構造を理解。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | FoundCombination.php | `src/Core/Content/Product/SalesChannel/FindVariant/FoundCombination.php` | 検索結果の構造（variantId, options） |
| 1-2 | FindProductVariantRouteResponse.php | `src/Core/Content/Product/SalesChannel/FindVariant/FindProductVariantRouteResponse.php` | APIレスポンス構造 |

**読解のコツ**: FoundCombinationは見つかったバリアントIDと、実際にマッチしたオプションの組み合わせを保持する。

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

処理の起点となるコントローラーを確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | ProductController.php | `src/Storefront/Controller/ProductController.php` | switchメソッド |

**主要処理フロー**:
1. **74-128行目**: `switch`メソッド - バリアント切替処理
2. **83行目**: HTTP_CACHE属性、XmlHttpRequest属性
3. **85行目**: switchedグループIDの取得
4. **87-91行目**: optionsのJSONデコード
5. **93-98行目**: variantRequestDataの構築
6. **100-110行目**: FindProductVariantRoute.load呼び出しとエラーハンドリング
7. **112-122行目**: SEO URL生成
8. **124-127行目**: JsonResponse返却

#### Step 3: バリアント検索ルートを理解する

バリアント検索の実装を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | FindProductVariantRoute.php | `src/Core/Content/Product/SalesChannel/FindVariant/FindProductVariantRoute.php` | バリアント検索の実装 |

**主要処理フロー**:
- **49-88行目**: `load`メソッド - バリアント検索処理
- **51-52行目**: switchedGroup, optionsの取得
- **54-59行目**: optionsのバリデーション
- **65-68行目**: 完全一致検索
- **71-85行目**: フォールバック検索（オプションを減らして再検索）
- **87行目**: VariantNotFoundException
- **93-107行目**: `searchForOptions`メソッド - 実際の検索処理

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

```
ProductController
    │
    └─ switch (GET /detail/{productId}/switch)
           │
           ├─ switchedグループID取得
           │
           ├─ optionsをJSONデコード
           │
           ├─ variantRequestData構築
           │
           ├─ FindProductVariantRoute.load
           │      │
           │      ├─ optionsバリデーション
           │      │
           │      ├─ searchForOptions（全オプション）
           │      │      └─ product検索（parentId + optionIds）
           │      │
           │      ├─ 見つからない場合: フォールバック検索
           │      │      └─ switchedGroup以外を除外して再検索
           │      │
           │      └─ FoundCombination返却
           │
           ├─ SeoUrlPlaceholderHandler.generate
           │
           ├─ SeoUrlPlaceholderHandler.replace
           │
           └─ JsonResponse返却
```

### データフロー図

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

GET /detail/{productId}/switch ┬──────────────────────────────────► JSONレスポンス
?switched={groupId}            │                                    {url, productId}
&options={json}                │
                               ▼
                    ┌─────────────────────┐
                    │ ProductController   │
                    │ ::switch            │
                    └─────────┬───────────┘
                              │
                              ▼
                    ┌─────────────────────┐
                    │ FindProductVariant  │
                    │ Route.load          │◄──── product
                    └─────────┬───────────┘      (parentId + optionIds検索)
                              │
                              ▼
                    ┌─────────────────────┐
                    │ FoundCombination    │
                    │ (variantId, options)│
                    └─────────┬───────────┘
                              │
                              ▼
                    ┌─────────────────────┐
                    │ SeoUrlPlaceholder   │
                    │ Handler             │
                    └─────────┬───────────┘
                              │
                              ▼
                    ┌─────────────────────┐
                    │ JsonResponse        │─────────────────────────► {url, productId}
                    └─────────────────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| ProductController.php | `src/Storefront/Controller/ProductController.php` | ソース | コントローラー（エントリーポイント） |
| AbstractFindProductVariantRoute.php | `src/Core/Content/Product/SalesChannel/FindVariant/AbstractFindProductVariantRoute.php` | ソース | バリアント検索API抽象クラス |
| FindProductVariantRoute.php | `src/Core/Content/Product/SalesChannel/FindVariant/FindProductVariantRoute.php` | ソース | バリアント検索API実装 |
| FoundCombination.php | `src/Core/Content/Product/SalesChannel/FindVariant/FoundCombination.php` | ソース | 検索結果構造 |
| FindProductVariantRouteResponse.php | `src/Core/Content/Product/SalesChannel/FindVariant/FindProductVariantRouteResponse.php` | ソース | APIレスポンス構造 |
| SeoUrlPlaceholderHandlerInterface.php | `src/Core/Content/Seo/SeoUrlPlaceholderHandlerInterface.php` | ソース | SEO URL生成インターフェース |
