# 機能設計書 70-コンテキスト切替

## 概要

本ドキュメントは、Shopware Storefrontにおける言語、通貨、支払方法、配送方法などのコンテキストを切り替える機能について、その処理内容、入出力仕様、およびコード構造を記述した機能設計書である。

### 本機能の処理概要

本機能は、ユーザーのショッピングコンテキスト（言語、通貨、支払方法、配送方法、住所など）を動的に変更する機能を提供する。

**業務上の目的・背景**：多言語・多通貨対応のECサイトでは、ユーザーが自分の好みに合わせて表示言語や通貨を切り替えられる必要がある。また、チェックアウト時には支払方法や配送方法、配送先住所などを選択・変更できる必要がある。コンテキスト切替機能により、これらの設定をセッション単位で永続化し、シームレスなショッピング体験を提供する。

**機能の利用シーン**：
- ヘッダーの言語セレクターで言語を切り替える場合
- 通貨セレクターで通貨を切り替える場合
- チェックアウト時に支払方法を変更する場合
- チェックアウト時に配送方法を変更する場合
- 配送先・請求先住所を変更する場合

**主要な処理内容**：
1. 切り替えパラメータを受け取る（言語ID、通貨ID、支払方法ID等）
2. パラメータのバリデーション（型チェック、存在チェック）
3. コンテキスト情報をセッションに永続化
4. 言語切替の場合は適切なドメインURLにリダイレクト
5. 更新されたコンテキストを返却

**関連システム・外部連携**：Store APIのContextSwitchRoute、SalesChannelContextPersister

**権限による制御**：住所変更はログインユーザーのみ可能

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 47 | 販売チャネル管理 | 主機能 | ECサイト・APIチャネルの設定 |
| 49 | 基本設定 | 補助機能 | ショップ名・連絡先などの基本設定 |
| 51 | 通貨設定 | 補助機能 | 対応通貨の設定 |
| 52 | 言語設定 | 補助機能 | 多言語対応の設定 |
| 53 | 税設定 | 補助機能 | 税率・税ルールの設定 |
| 54 | 配送設定 | 補助機能 | 配送方法・送料の設定 |
| 55 | 支払設定 | 補助機能 | 決済方法の設定 |
| 57 | ルール設定 | 補助機能 | 条件ベースのビジネスルール設定 |
| 69 | 初期設定ウィザード | 主機能 | ショップ初回セットアップ |

## 機能種別

データ連携 / コンテキスト管理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| languageId | string | No | 言語のUUID | 有効なUUID、販売チャネルで有効な言語 |
| currencyId | string | No | 通貨のUUID | 有効なUUID、販売チャネルで有効な通貨 |
| shippingMethodId | string | No | 配送方法のUUID | 有効なUUID、販売チャネルで有効な配送方法 |
| paymentMethodId | string | No | 支払方法のUUID | 有効なUUID、販売チャネルで有効な支払方法 |
| billingAddressId | string | No | 請求先住所のUUID | ログイン時のみ、顧客の住所 |
| shippingAddressId | string | No | 配送先住所のUUID | ログイン時のみ、顧客の住所 |
| countryId | string | No | 国のUUID | 有効なUUID |
| countryStateId | string | No | 州のUUID | 有効なUUID |

### 入力データソース

- リクエストボディ（POST）: RequestDataBag
- Store API: AbstractContextSwitchRoute

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| contextToken | string | 更新後のコンテキストトークン |
| redirectUrl | string\|null | 言語切替時のリダイレクトURL |

### 出力先

- configure(): createActionResponse（通常のHTTPレスポンス/Ajax対応）
- switchLanguage(): RedirectResponse（新しい言語のドメインにリダイレクト）

## 処理フロー

### 処理シーケンス

```
1. ContextController がリクエストを受信
   ├─ configure(): /checkout/configure (POST)
   └─ switchLanguage(): /checkout/language (POST)

2. configure() の場合
   ├─ AbstractContextSwitchRoute::switchContext() を呼び出し
   └─ createActionResponse() でレスポンスを返却

3. switchLanguage() の場合
   ├─ languageIdパラメータの検証
   ├─ AbstractContextSwitchRoute::switchContext() を呼び出し
   ├─ リダイレクトパラメータの処理
   ├─ 新しい言語のドメインURLを解決
   └─ RedirectResponseで新しいURLにリダイレクト

4. ContextSwitchRoute::switchContext() の処理
   ├─ DataValidationDefinitionで型チェック
   ├─ SwitchContextEventをディスパッチ（CONSISTENT_CHECK）
   ├─ エンティティの存在チェック（販売チャネル限定）
   ├─ 住所IDの顧客所有チェック（ログイン時のみ）
   ├─ SwitchContextEventをディスパッチ（DATABASE_CHECK）
   ├─ SalesChannelContextPersisterでセッションに保存
   ├─ 新しいドメインURLをチェック（言語切替時）
   ├─ SalesChannelContextServiceで最新コンテキストを取得
   └─ SalesChannelContextSwitchEventをディスパッチ
```

### フローチャート

```mermaid
flowchart TD
    A[リクエスト受信] --> B{エンドポイント}
    B -->|/checkout/configure| C[configure]
    B -->|/checkout/language| D[switchLanguage]

    C --> E[ContextSwitchRoute::switchContext]
    D --> F{languageIdあり?}
    F -->|No| G[RoutingException]
    F -->|Yes| H{UUID形式?}
    H -->|No| I[InvalidRequestParameter]
    H -->|Yes| E

    E --> J[型バリデーション]
    J --> K[SwitchContextEvent:CONSISTENT]
    K --> L[エンティティ存在チェック]
    L --> M{住所ID指定 & 未ログイン?}
    M -->|Yes| N[CustomerNotLoggedIn例外]
    M -->|No| O[SwitchContextEvent:DATABASE]
    O --> P[SalesChannelContextPersister::save]
    P --> Q{言語変更?}
    Q -->|Yes| R[新ドメインURL解決]
    Q -->|No| S[null]
    R --> T[SalesChannelContextService::get]
    S --> T
    T --> U[SalesChannelContextSwitchEvent]
    U --> V[ContextTokenResponse]

    C --> W[createActionResponse]
    D --> X{リダイレクトURLあり?}
    X -->|No| Y[redirectToRoute]
    X -->|Yes| Z[ルーターコンテキスト更新]
    Z --> AA[router::generate]
    AA --> AB[RedirectResponse]
    Y --> AB
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-001 | 販売チャネル限定 | 言語、通貨、支払方法、配送方法は販売チャネルで有効なもののみ | 常時 |
| BR-002 | 住所は顧客所有 | 住所IDは現在の顧客が所有するものに限定 | ログイン時 |
| BR-003 | 未ログインで住所不可 | 未ログイン状態では住所IDを設定できない | 未ログイン時 |
| BR-004 | 言語ドメインリダイレクト | 言語を切り替えた場合、対応するドメインにリダイレクト | 言語切替時 |
| BR-005 | コンテキスト永続化 | コンテキスト変更はセッションに永続化される | 常時 |

### 計算ロジック

特になし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| コンテキスト保存 | sales_channel_api_context | UPDATE/INSERT | コンテキスト情報の永続化 |
| 言語存在チェック | language | SELECT | 販売チャネルの言語確認 |
| 通貨存在チェック | currency | SELECT | 販売チャネルの通貨確認 |
| 支払方法存在チェック | payment_method | SELECT | 販売チャネルの支払方法確認 |
| 配送方法存在チェック | shipping_method | SELECT | 販売チャネルの配送方法確認 |
| 住所存在チェック | customer_address | SELECT | 顧客の住所確認 |

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

#### sales_channel_api_context

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE/INSERT | payload | コンテキスト情報のJSON | token + sales_channel_id で識別 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| FRAMEWORK__ROUTING_MISSING_REQUEST_PARAMETER | RoutingException | languageIdパラメータ未指定（switchLanguage時） | パラメータを指定する |
| FRAMEWORK__ROUTING_INVALID_REQUEST_PARAMETER | RoutingException | languageIdが無効なUUID形式 | 正しいUUIDを指定する |
| FRAMEWORK__ROUTING_LANGUAGE_NOT_FOUND | RoutingException | 指定された言語が存在しない/無効 | 有効な言語IDを指定する |
| CHECKOUT__CUSTOMER_NOT_LOGGED_IN | SalesChannelException | 未ログインで住所IDを指定 | ログインするか住所指定を削除 |
| ConstraintViolationException | バリデーション | エンティティが存在しない | 有効なIDを指定する |

### リトライ仕様

リトライ処理なし

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

コンテキスト保存は単一のDB操作のためトランザクション制御なし

## パフォーマンス要件

- レスポンス時間：100ms以内推奨

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

- 住所IDは顧客所有チェックを行い、他顧客の住所を設定できないように制御
- 未ログインユーザーは住所IDを設定できない
- セッションベースのコンテキスト管理

## 備考

- SwitchContextEventを通じて拡張可能（CONSISTENT_CHECK、DATABASE_CHECK）
- 言語切替時は対応するドメインURLへのリダイレクトが発生
- 複数ドメイン構成（言語別ドメイン）に対応

---

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

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

### 推奨読解順序

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

コンテキスト切替に関わるデータ構造を理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | ContextTokenResponse.php | `src/Core/System/SalesChannel/ContextTokenResponse.php` | レスポンスの構造（token, redirectUrl） |
| 1-2 | SalesChannelContextService.php | `src/Core/System/SalesChannel/Context/SalesChannelContextService.php` | コンテキストサービスの定数定義 |

**読解のコツ**: SalesChannelContextServiceには切り替え可能なパラメータ名（LANGUAGE_ID, CURRENCY_ID等）が定数として定義されている。

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | ContextController.php | `src/Storefront/Controller/ContextController.php` | configure(), switchLanguage()メソッド |

**主要処理フロー**:
1. **43-49行目**: configure() - 汎用コンテキスト切替
2. **51-137行目**: switchLanguage() - 言語切替専用（リダイレクト処理含む）
3. **54-61行目**: languageIdのバリデーション
4. **63-70行目**: ContextSwitchRoute::switchContext()を呼び出し
5. **72-86行目**: リダイレクトパラメータの処理
6. **88-136行目**: ドメインリダイレクトの処理

#### Step 3: Store APIルートを理解する

コンテキスト切替の核心処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | ContextSwitchRoute.php | `src/Core/System/SalesChannel/SalesChannel/ContextSwitchRoute.php` | switchContext()メソッド（57-160行目） |

**主要処理フロー**:
- **60-71行目**: パラメータの抽出
- **73-83行目**: 型バリデーション定義
- **85-88行目**: SwitchContextEvent:CONSISTENT_CHECKをディスパッチ
- **91-103行目**: 住所IDのチェック（ログイン必須）
- **105-129行目**: エンティティ存在チェック（販売チャネル限定）
- **131-135行目**: SwitchContextEvent:DATABASE_CHECKをディスパッチ
- **138-143行目**: SalesChannelContextPersisterで保存
- **146行目**: 新しいドメインURLをチェック
- **149-154行目**: 最新コンテキストを取得
- **156-157行目**: SalesChannelContextSwitchEventをディスパッチ

#### Step 4: コンテキスト永続化を理解する

セッションへの永続化処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | SalesChannelContextPersister.php | `src/Core/System/SalesChannel/Context/SalesChannelContextPersister.php` | save()メソッド |

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

```
ContextController
    │
    ├─ configure()
    │      └─ AbstractContextSwitchRoute::switchContext()
    │             ├─ DataValidation (型チェック)
    │             ├─ SwitchContextEvent:CONSISTENT_CHECK
    │             ├─ EntityExists Validation (DB存在チェック)
    │             ├─ SwitchContextEvent:DATABASE_CHECK
    │             ├─ SalesChannelContextPersister::save()
    │             ├─ checkNewDomain()
    │             ├─ SalesChannelContextService::get()
    │             └─ SalesChannelContextSwitchEvent
    │
    └─ switchLanguage()
           ├─ languageId検証
           ├─ AbstractContextSwitchRoute::switchContext()
           ├─ リダイレクトパラメータ処理
           ├─ 新ドメインURL解決
           │      └─ SalesChannel::getDomains()
           └─ RedirectResponse
```

### データフロー図

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

RequestDataBag ─────────────────┐
                                ▼
SalesChannelContext ──────▶ ContextController ──────▶ Response
                                │                       │
                                ▼                       │
                         ContextSwitchRoute             │
                                │                       │
                                ├─ 型バリデーション      │
                                ├─ SwitchContextEvent   │
                                ├─ DB存在チェック        │
                                │                       │
                                ▼                       │
                         SalesChannelContextPersister   │
                                │                       │
                                ▼                       │
                         sales_channel_api_context      │
                                │                       │
                                ▼                       ▼
                         SalesChannelContextService → ContextTokenResponse
                                                          │
                                                          ├─ token
                                                          └─ redirectUrl (optional)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| ContextController.php | `src/Storefront/Controller/ContextController.php` | コントローラー | コンテキスト切替のエントリーポイント |
| ContextSwitchRoute.php | `src/Core/System/SalesChannel/SalesChannel/ContextSwitchRoute.php` | Store API | コンテキスト切替の核心処理 |
| AbstractContextSwitchRoute.php | `src/Core/System/SalesChannel/SalesChannel/AbstractContextSwitchRoute.php` | 抽象クラス | Store APIルートの抽象定義 |
| SalesChannelContextPersister.php | `src/Core/System/SalesChannel/Context/SalesChannelContextPersister.php` | サービス | コンテキストの永続化 |
| SalesChannelContextService.php | `src/Core/System/SalesChannel/Context/SalesChannelContextService.php` | サービス | コンテキストの取得・管理 |
| ContextTokenResponse.php | `src/Core/System/SalesChannel/ContextTokenResponse.php` | レスポンス | 切替結果のレスポンス |
| SalesChannelContextSwitchEvent.php | `src/Core/System/SalesChannel/Event/SalesChannelContextSwitchEvent.php` | イベント | コンテキスト切替完了イベント |
| SwitchContextEvent.php | `src/Core/System/SalesChannel/Event/SwitchContextEvent.php` | イベント | 切替時のバリデーション拡張用イベント |
