# 機能設計書 59-オフキャンバスメニュー

## 概要

本ドキュメントは、Shopware Storefrontにおける「オフキャンバスメニュー」機能の設計仕様を定義する。モバイルデバイスや小画面デバイス向けに、画面端からスライドインするナビゲーションメニューを提供する。

### 本機能の処理概要

**業務上の目的・背景**：モバイルデバイスでのEC利用が増加する中、限られた画面スペースで効率的なナビゲーションを提供することは重要である。オフキャンバスメニューは、画面の端から表示されるスライドパネルとして実装され、カテゴリ階層をドリルダウン形式で閲覧できる。これにより、モバイルユーザーの操作性と顧客体験を向上させる。

**機能の利用シーン**：モバイル画面でハンバーガーメニューアイコンをタップした際に、本機能が呼び出される。ユーザーはカテゴリ階層を一つずつ掘り下げながらナビゲーションでき、目的のカテゴリを選択して遷移できる。

**主要な処理内容**：
1. リクエストからナビゲーションIDを取得（指定なければルートカテゴリ）
2. NavigationLoaderでカテゴリツリーを取得
3. 1階層分のカテゴリ一覧をページレットとして構築
4. イベント発火による拡張ポイント提供
5. ナビゲーションページレットをレンダリング

**関連システム・外部連携**：本機能は Store API の `NavigationRoute` を内部的に利用し、カテゴリ階層情報を取得する。`MenuOffcanvasPageletLoadedEvent`イベントを発火し、プラグインによる拡張を可能にする。

**権限による制御**：本機能はログイン不要で利用可能。HTTPキャッシュが有効で、全ユーザーに同じコンテンツを提供する。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 2 | カテゴリページ | 参照画面 | モバイル用メニューからカテゴリ選択 |
| 1 | ホームページ | 参照画面 | ハンバーガーメニューから表示 |

## 機能種別

ページレット表示 / ナビゲーション / Ajax API

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| navigationId | string (UUID) | No | 表示するカテゴリのID | UUID形式（指定なければルートカテゴリ） |

### 入力データソース

- URLクエリパラメータ: `?navigationId=xxx`
- SalesChannelContext: 販売チャネル情報を含む

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| page | MenuOffcanvasPagelet | オフキャンバスメニューページレット |

### 出力先

- HTTPレスポンス: Twigテンプレートによるページレット表示
- x-robots-tag: noindex（検索エンジン対策）

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ NavigationController::offcanvas が呼び出される

2. ナビゲーションID取得
   └─ クエリパラメータから取得
   └─ なければ販売チャネルのナビゲーションカテゴリID

3. NavigationLoader::load 実行
   └─ 指定カテゴリを起点として1階層分のナビゲーションを取得

4. ページレット作成
   └─ MenuOffcanvasPageletを作成
   └─ ナビゲーションツリーを設定

5. イベント発火
   └─ MenuOffcanvasPageletLoadedEvent をディスパッチ

6. レスポンス返却
   └─ navigation-pagelet.html.twig でレンダリング
   └─ x-robots-tagをnoindexに設定
```

### フローチャート

```mermaid
flowchart TD
    A[開始: offcanvas呼び出し] --> B{navigationIdあり?}
    B -->|Yes| C[指定IDを使用]
    B -->|No| D[販売チャネルのルートカテゴリIDを使用]
    C --> E[NavigationLoader::load]
    D --> E
    E --> F{navigationIDが空?}
    F -->|Yes| G[RoutingException]
    F -->|No| H[1階層分のナビゲーション取得]
    H --> I[MenuOffcanvasPagelet作成]
    I --> J[MenuOffcanvasPageletLoadedEvent発火]
    J --> K[navigation-pagelet.html.twigレンダリング]
    K --> L[x-robots-tag: noindex設定]
    L --> M[終了]
    G --> N[エラーレスポンス]
    N --> M
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-59-01 | ログイン不要 | ログインなしでアクセス可能 | 全リクエスト |
| BR-59-02 | HTTPキャッシュ有効 | HTTPキャッシュ属性が設定される | 全リクエスト |
| BR-59-03 | XmlHttpRequest | Ajax呼び出し用エンドポイント | 全リクエスト |
| BR-59-04 | noindex | 検索エンジンにインデックスさせない | 全リクエスト |
| BR-59-05 | 1階層表示 | ドリルダウンで1階層ずつ表示 | ナビゲーション取得時 |

### 計算ロジック

特になし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| カテゴリ取得 | category | SELECT | 指定カテゴリとその子カテゴリを取得 |
| 翻訳取得 | category_translation | SELECT | カテゴリ名の翻訳を取得 |

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

#### category

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | id, name, active, visible, parent_id | parent_id = ? AND active = true AND visible = true | 子カテゴリ取得 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| MISSING_REQUEST_PARAMETER | パラメータ不正 | navigationIdが空文字列 | 正しいIDを指定 |

### リトライ仕様

特になし

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

- 読み取り専用のため、トランザクション管理は不要
- SELECTクエリのみ実行

## パフォーマンス要件

- レスポンス時間: 200ms以内（キャッシュヒット時は50ms以内）
- HTTPキャッシュ有効

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

- HTTPキャッシュが有効のため、ユーザー固有情報は含めない
- noindexでSEOスパム対策

## 備考

- オフキャンバスメニューはJavaScriptで制御されるスライドパネル
- depth=1で1階層のみ取得し、ドリルダウン時に再度APIを呼び出す
- 親カテゴリへの「戻る」ナビゲーションもサポート

---

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

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

### 推奨読解順序

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

まず、オフキャンバスページレットのデータ構造を理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | MenuOffcanvasPagelet.php | `src/Storefront/Pagelet/Menu/Offcanvas/MenuOffcanvasPagelet.php` | ページレット構造。navigationプロパティ（Tree型）を確認 |

**読解のコツ**: Pageletは部分的に更新可能なUIコンポーネント。Ajax通信で取得され、DOMに挿入される。

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | NavigationController.php | `src/Storefront/Controller/NavigationController.php` | Storefront側のエントリーポイント。offcanvasメソッドがオフキャンバスメニューの入口 |

**主要処理フロー**:
1. **95-102行目**: offcanvasメソッドのルート定義。GET `/widgets/menu/offcanvas`、XmlHttpRequest、HTTPキャッシュ有効
2. **104-118行目**: offcanvasメソッドの実装
3. **106行目**: MenuOffcanvasPageletLoaderでページレットをロード
4. **108行目**: MenuOffcanvasPageletLoadedHookを発火
5. **110-113行目**: Twigテンプレートでレンダリング
6. **115行目**: x-robots-tagをnoindexに設定

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

実際のビジネスロジックはページレットローダーに実装されている。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | MenuOffcanvasPageletLoader.php | `src/Storefront/Pagelet/Menu/Offcanvas/MenuOffcanvasPageletLoader.php` | ページレット生成のロジック |

**主要処理フロー**:
- **34-50行目**: loadメソッドの実装
- **36-38行目**: ナビゲーションIDを取得（クエリパラメータから、なければ販売チャネル設定から）
- **37-39行目**: navigationIdが空の場合は例外
- **41行目**: NavigationLoaderでナビゲーションツリーを取得（depth=1）
- **43行目**: MenuOffcanvasPageletを作成
- **45-47行目**: イベント発火

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

```
NavigationController::offcanvas (Storefront)
    │
    └─ MenuOffcanvasPageletLoader::load
           │
           ├─ NavigationLoader::load
           │      └─ NavigationRoute (Store API経由でカテゴリ取得)
           │
           └─ EventDispatcher::dispatch
                  └─ MenuOffcanvasPageletLoadedEvent
```

### データフロー図

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

navigationId (任意) ───▶ NavigationController::offcanvas
                              │
Request ───────────────────▶ │
                              │
SalesChannelContext ───────▶ │
                              ▼
                         MenuOffcanvasPageletLoader::load
                              │
                              └─▶ category テーブル (SELECT - 1階層分)
                                          │
                                          ▼
                                   Twigテンプレート ──────▶ ナビゲーションHTML
                                          │
                                          ▼
                                   x-robots-tag: noindex
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| NavigationController.php | `src/Storefront/Controller/NavigationController.php` | ソース | Storefrontコントローラー、エントリーポイント |
| MenuOffcanvasPageletLoader.php | `src/Storefront/Pagelet/Menu/Offcanvas/MenuOffcanvasPageletLoader.php` | ソース | ページレットローダー、ビジネスロジック |
| MenuOffcanvasPagelet.php | `src/Storefront/Pagelet/Menu/Offcanvas/MenuOffcanvasPagelet.php` | ソース | ページレットクラス |
| NavigationLoader.php | `src/Core/Content/Category/Service/NavigationLoader.php` | ソース | ナビゲーションツリー生成 |
| MenuOffcanvasPageletLoadedEvent.php | `src/Storefront/Pagelet/Menu/Offcanvas/MenuOffcanvasPageletLoadedEvent.php` | ソース | イベントクラス |
| navigation-pagelet.html.twig | `src/Storefront/Resources/views/storefront/layout/navigation/offcanvas/navigation-pagelet.html.twig` | テンプレート | ナビゲーションページレットテンプレート |
