# 機能設計書 27-商品詳細表示

## 概要

本ドキュメントは、Shopwareストアフロントにおける商品詳細表示機能の設計を記述したものである。本機能は、商品の詳細情報、画像、オプション、関連情報を包括的に表示するページを提供する。

### 本機能の処理概要

商品詳細表示機能は、ECサイトの中核となる機能であり、顧客が購入を決定するために必要な商品情報を網羅的に提供する。

**業務上の目的・背景**：ECサイトにおいて、商品詳細ページは顧客の購買意思決定に最も大きな影響を与えるページである。商品の特徴、仕様、画像、価格、在庫状況、レビューなどの情報を適切に表示することで、購入転換率の向上と返品率の低減に寄与する。また、SEO対策としても重要なページであり、検索エンジンからの流入を増やすためにメタ情報の最適化が必要である。

**機能の利用シーン**：
- 商品一覧から特定の商品を選択して詳細を確認する場合
- 検索結果から商品詳細を閲覧する場合
- 外部リンク（広告、SNS等）から直接商品ページにアクセスする場合
- 商品のバリエーション（サイズ、色等）を確認する場合

**主要な処理内容**：
1. 商品IDによる商品データの取得
2. 関連データ（メーカー、オプション、プロパティ、メディア）の読み込み
3. CMSページ（レイアウト）の適用
4. メタ情報（SEO）の設定
5. コンフィギュレーター設定の取得
6. 選択オプションの特定

**関連システム・外部連携**：
- AbstractProductDetailRoute: 商品詳細データ取得API
- GenericPageLoader: 共通ページ要素読み込み
- CMSシステム: 商品ページレイアウト

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

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 3 | 商品詳細ページ | 主画面 | 商品の詳細情報、画像、オプションの表示（ProductController::index） |
| 33 | 商品一覧（管理画面） | 参照画面 | 商品の一覧表示・検索・フィルタリング |
| 35 | メーカー管理 | 参照画面 | 商品メーカー情報の管理 |
| 68 | 一括編集 | 補助機能 | 複数商品の同時編集 |

## 機能種別

CRUD操作（Read） / データ表示 / SEO最適化

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| productId | string | Yes | 商品ID（URLパラメータ） | UUID形式 |

### 入力データソース

- URLルートパラメータ（productId）
- SalesChannelContext（販売チャネル、言語、通貨情報）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| page | ProductPage | 商品ページオブジェクト |
| page.product | SalesChannelProductEntity | 商品データ |
| page.configuratorSettings | PropertyGroupCollection | バリアントコンフィギュレーター設定 |
| page.selectedOptions | PropertyGroupOptionCollection | 選択済みオプション |
| page.cmsPage | CmsPageEntity | CMSページレイアウト |
| page.metaInformation | MetaInformation | SEOメタ情報 |

#### 商品データの詳細項目

| 項目名 | 型 | 説明 |
|--------|-----|------|
| product.name | string | 商品名 |
| product.description | string | 商品説明 |
| product.productNumber | string | 商品番号 |
| product.price | PriceCollection | 価格情報 |
| product.manufacturer | ProductManufacturerEntity | メーカー情報 |
| product.media | ProductMediaCollection | 商品メディア（画像） |
| product.cover | ProductMediaEntity | カバー画像 |
| product.options | PropertyGroupOptionCollection | 商品オプション |
| product.properties | PropertyGroupOptionCollection | 商品プロパティ |
| product.seoCategory | CategoryEntity | SEOカテゴリ |

### 出力先

- HTMLレスポンス（Twigテンプレート: `@Storefront/storefront/page/content/product-detail.html.twig`）

## 処理フロー

### 処理シーケンス

```
1. index（GET /detail/{productId}）
   └─ productIdをURLから取得
   └─ ProductPageLoader.loadを呼び出し
      └─ Criteria構築（Association設定）
      └─ ProductPageCriteriaEventを発火
      └─ AbstractProductDetailRoute.loadで商品取得
      └─ メディアの並び替え（カバー画像を先頭に）
      └─ SEOカテゴリの設定
      └─ GenericPageLoaderでベースページ読み込み
      └─ コンフィギュレーター設定の適用
      └─ CMSページの適用
      └─ 選択オプションの特定
      └─ メタ情報の設定
      └─ ProductPageLoadedEventを発火
   └─ ProductPageLoadedHookを発火
   └─ product-detail.html.twigをレンダリング
```

### フローチャート

```mermaid
flowchart TD
    A[商品詳細ページアクセス] --> B[productIdをURLから取得]
    B --> C{productId存在?}
    C -->|No| D[MissingRequestParameterException]
    C -->|Yes| E[Criteria構築]
    E --> F[ProductPageCriteriaEvent発火]
    F --> G[AbstractProductDetailRoute.load]
    G --> H{商品存在?}
    H -->|No| I[ProductNotFoundException]
    H -->|Yes| J[メディア並び替え]
    J --> K[SEOカテゴリ設定]
    K --> L[GenericPageLoader.load]
    L --> M[ProductPage生成]
    M --> N[コンフィギュレーター設定適用]
    N --> O[CMSページ適用]
    O --> P[選択オプション特定]
    P --> Q[メタ情報設定]
    Q --> R[ProductPageLoadedEvent発火]
    R --> S[ProductPageLoadedHook発火]
    S --> T[Twigレンダリング]
    T --> U[HTMLレスポンス]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-27-01 | 商品ID必須 | URLにproductIdが含まれていること | 常時 |
| BR-27-02 | 公開状態確認 | 販売チャネルで有効かつ公開状態の商品のみ表示 | 常時 |
| BR-27-03 | カバー画像優先 | メディア一覧ではカバー画像を先頭に配置 | 常時 |
| BR-27-04 | メタタイトル生成 | metaTitleが未設定の場合、商品名+オプション名+商品番号で自動生成 | metaTitle未設定時 |
| BR-27-05 | HTTPキャッシュ | HTTP_CACHE属性によりキャッシュ可能 | 常時 |

### 計算ロジック

#### メタタイトル生成ロジック

```
if (product.metaTitle is not empty) {
    metaTitle = product.metaTitle
} else {
    metaTitleParts = [product.name]
    foreach (selectedOptions as option) {
        metaTitleParts[] = option.name
    }
    metaTitleParts[] = product.productNumber
    metaTitle = implode(' | ', metaTitleParts)
}
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 商品取得 | product | SELECT | 商品基本情報 |
| メーカー取得 | product_manufacturer | SELECT | メーカー情報 |
| メディア取得 | product_media | SELECT | 商品画像 |
| オプション取得 | property_group_option | SELECT | 商品オプション |
| プロパティ取得 | property_group_option | SELECT | 商品プロパティ |
| カテゴリ取得 | category | SELECT | SEOカテゴリ |
| CMSページ取得 | cms_page | SELECT | 商品ページレイアウト |

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

#### Criteria Association設定

以下のAssociationがCriteriaに設定される：
- manufacturer.media
- options.group
- properties.group
- mainCategories.category
- media.media（position昇順でソート）

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| MISSING_REQUEST_PARAMETER | パラメータ不足 | productIdが指定されていない | 400エラー |
| PRODUCT_NOT_FOUND | 商品不在 | 指定された商品が存在しないまたは非公開 | 404エラーページ表示 |

### リトライ仕様

リトライ処理は行わない。商品が存在しない場合は404エラーページを表示。

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

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

## パフォーマンス要件

- ページ表示: 1秒以内
- HTTPキャッシュ対応により、キャッシュヒット時は高速レスポンス
- Associationの最適化により必要なデータのみ取得

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

- 公開商品のみ表示（販売チャネルの設定に従う）
- HTTPキャッシュ対応（パーソナライズされていない情報のみ）
- XSS対策（Twigの自動エスケープ）

## 備考

- CMS（Shopping Experiences）によりページレイアウトをカスタマイズ可能
- SEO URLにより、/detail/{productId}以外のURLでもアクセス可能
- redirectTo='frontend.detail.page'を設定し、カートへの追加後に詳細ページに戻れるようにする

---

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

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

### 推奨読解順序

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

商品エンティティとページ構造を理解。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | SalesChannelProductEntity.php | `src/Core/Content/Product/SalesChannel/SalesChannelProductEntity.php` | 販売チャネル向け商品エンティティ |
| 1-2 | ProductPage.php | `src/Storefront/Page/Product/ProductPage.php` | 商品ページビューモデル |
| 1-3 | PropertyGroupCollection.php | `src/Core/Content/Property/PropertyGroupCollection.php` | オプショングループコレクション |

**読解のコツ**: SalesChannelProductEntityはProductEntityを継承し、calculatedPriceなど販売チャネル固有のプロパティを持つ。

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

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

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

**主要処理フロー**:
1. **53-72行目**: `index`メソッド - 商品詳細表示処理
2. **59行目**: HTTP_CACHE属性（キャッシュ可能）
3. **61行目**: ProductPageLoader.load呼び出し
4. **63行目**: ProductPageLoadedHook発火
5. **65-71行目**: Twigレンダリング、redirectTo設定

#### Step 3: ページローダーを理解する

商品ページの読み込み処理を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | ProductPageLoader.php | `src/Storefront/Page/Product/ProductPageLoader.php` | ページデータの組み立て |

**主要処理フロー**:
- **43-96行目**: `load`メソッド - ページデータの読み込み
- **45-48行目**: productIdの取得と検証
- **50-55行目**: Criteria構築（Association設定）
- **57-59行目**: mediaのソート設定
- **61行目**: ProductPageCriteriaEvent発火
- **63-64行目**: AbstractProductDetailRoute.load
- **66-71行目**: メディア並び替え（カバー画像先頭）
- **73-75行目**: SEOカテゴリ設定
- **77-78行目**: GenericPageLoader.load、ProductPage生成
- **80-86行目**: 商品データ設定
- **88行目**: loadOptions呼び出し
- **89行目**: loadMetaData呼び出し
- **91-93行目**: ProductPageLoadedEvent発火

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

商品取得に使用されるAPIルートを確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | AbstractProductDetailRoute.php | `src/Core/Content/Product/SalesChannel/Detail/AbstractProductDetailRoute.php` | 商品詳細取得API抽象クラス |

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

```
ProductController
    │
    └─ index (GET /detail/{productId})
           │
           ├─ ProductPageLoader.load
           │      ├─ Criteria構築
           │      │      ├─ manufacturer.media Association
           │      │      ├─ options.group Association
           │      │      ├─ properties.group Association
           │      │      ├─ mainCategories.category Association
           │      │      └─ media.media Association (position昇順)
           │      │
           │      ├─ ProductPageCriteriaEvent dispatch
           │      │
           │      ├─ AbstractProductDetailRoute.load
           │      │      └─ 商品データ取得
           │      │
           │      ├─ メディア並び替え（カバー画像先頭）
           │      │
           │      ├─ SEOカテゴリ設定
           │      │
           │      ├─ GenericPageLoader.load
           │      │
           │      ├─ ProductPage生成
           │      │      ├─ setProduct
           │      │      ├─ setConfiguratorSettings
           │      │      ├─ setNavigationId
           │      │      └─ setCmsPage
           │      │
           │      ├─ loadOptions
           │      │      └─ 選択オプション特定
           │      │
           │      ├─ loadMetaData
           │      │      └─ メタ情報設定
           │      │
           │      └─ ProductPageLoadedEvent dispatch
           │
           ├─ ProductPageLoadedHook dispatch
           │
           └─ renderStorefront (product-detail.html.twig)
```

### データフロー図

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

URL (/detail/{productId}) ─────┬──────────────────────────────────► 商品詳細HTML
                               │
                               ▼
                    ┌─────────────────────┐
                    │ ProductController   │
                    │ ::index             │
                    └─────────┬───────────┘
                              │
                              ▼
                    ┌─────────────────────┐
                    │ ProductPageLoader   │
                    │ .load               │
                    └─────────┬───────────┘
                              │
                              ▼
                    ┌─────────────────────┐
                    │ ProductDetailRoute  │◄──── product
                    │ .load               │      product_manufacturer
                    └─────────┬───────────┘      product_media
                              │                  property_group_option
                              │                  category
                              ▼                  cms_page
                    ┌─────────────────────┐
                    │ ProductPage         │
                    │ (ViewModel)         │
                    └─────────┬───────────┘
                              │
                              ▼
                    ┌─────────────────────┐
                    │ Twig Template       │
                    │ (product-detail     │─────────────────────────► HTMLレスポンス
                    │  .html.twig)        │
                    └─────────────────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| ProductController.php | `src/Storefront/Controller/ProductController.php` | ソース | コントローラー（エントリーポイント） |
| ProductPageLoader.php | `src/Storefront/Page/Product/ProductPageLoader.php` | ソース | ページデータ読み込み |
| ProductPage.php | `src/Storefront/Page/Product/ProductPage.php` | ソース | ページビューモデル |
| AbstractProductDetailRoute.php | `src/Core/Content/Product/SalesChannel/Detail/AbstractProductDetailRoute.php` | ソース | 商品詳細取得API抽象クラス |
| SalesChannelProductEntity.php | `src/Core/Content/Product/SalesChannel/SalesChannelProductEntity.php` | ソース | 商品エンティティ |
| ProductPageCriteriaEvent.php | `src/Storefront/Page/Product/ProductPageCriteriaEvent.php` | ソース | Criteria構築イベント |
| ProductPageLoadedEvent.php | `src/Storefront/Page/Product/ProductPageLoadedEvent.php` | ソース | ページ読み込み完了イベント |
| ProductPageLoadedHook.php | `src/Storefront/Page/Product/ProductPageLoadedHook.php` | ソース | ページ読み込み完了フック |
| product-detail.html.twig | `src/Storefront/Resources/views/storefront/page/content/product-detail.html.twig` | テンプレート | 商品詳細表示テンプレート |
