# 機能設計書 34-検索フィルター

## 概要

本ドキュメントは、Shopwareストアフロントにおける検索結果のフィルター取得機能の設計について記述する。

### 本機能の処理概要

本機能は、検索結果ページにおいてフィルタリング用の集計データ（メーカー、プロパティ、価格帯、評価等）を取得するための機能である。ユーザーがフィルターを選択した際に、選択可能なフィルター値を動的に更新するために使用される。

**業務上の目的・背景**：ECサイトにおいて、検索結果のフィルタリングは顧客が目的の商品を効率的に絞り込むための重要な機能である。フィルターの選択状態に応じて、「組み合わせ可能な」フィルター値のみを表示することで、「検索結果0件」となる無駄な絞り込みを防止する。また、各フィルター値に対応する商品件数を表示することで、顧客の選択判断を支援する。

**機能の利用シーン**：
- 検索結果ページでフィルターを選択した際
- フィルター条件を変更・解除した際
- フィルターパネルの初期表示時

**主要な処理内容**：
1. 検索キーワードの受け取りとバリデーション
2. 集計のみ取得フラグ（only-aggregations）の設定
3. フィルター縮小フラグ（reduce-aggregations）の設定
4. 商品検索ルート（ProductSearchRoute）の呼び出し
5. 集計結果（Aggregations）の抽出とJSON形式での返却

**関連システム・外部連携**：
- Elasticsearch（オプション）：集計処理の高速化

**権限による制御**：特になし（匿名ユーザーも利用可能）

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 5 | 検索結果ページ | 主画面 | 検索結果のフィルタリング（SearchController::filter） |

## 機能種別

データ集計・取得（Read操作 / Ajax通信）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| search | string | Yes | 検索キーワード | 空文字でないこと |
| manufacturer | string | No | 選択中のメーカーID | UUID形式 |
| properties | array | No | 選択中のプロパティID配列 | UUID形式の配列 |
| min-price | float | No | 選択中の最低価格 | 0以上の数値 |
| max-price | float | No | 選択中の最高価格 | 0以上の数値 |
| shipping-free | bool | No | 送料無料フィルター | true/false |
| rating | int | No | 評価フィルター | 1-5の整数 |

### 入力データソース

- クエリパラメータまたはPOSTパラメータ
- SalesChannelContext（言語、通貨、顧客グループ等のコンテキスト情報）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| manufacturer | AggregationResult | メーカー別商品件数 |
| properties | AggregationResult | プロパティ別商品件数 |
| price | AggregationResult | 価格帯の範囲（min/max） |
| rating | AggregationResult | 評価別商品件数 |
| shipping-free | AggregationResult | 送料無料商品件数 |

### 出力先

- JSON形式のHTTPレスポンス
- x-robots-tag: noindex ヘッダーを設定

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信（Ajax）
   └─ SearchController::filter()がリクエストを受信
2. 検索キーワード取得
   └─ RequestParamHelperでsearchパラメータを取得
3. 検索キーワードバリデーション
   └─ 空の場合はRoutingException::missingRequestParameterをスロー
4. 集計モードフラグ設定
   └─ only-aggregations = true（商品データ取得をスキップ）
   └─ reduce-aggregations = true（選択フィルターに基づき縮小）
5. Criteria構築
   └─ タイトル'search-page'を設定
6. ProductSearchRoute呼び出し
   └─ 集計データのみを取得
7. 集計結果の抽出
   └─ AggregationResultCollectionから各集計を取得
8. JSON形式でマッピング
   └─ 集計名をキーとした連想配列に変換
9. レスポンス生成
   └─ JsonResponseを生成、noindexヘッダーを設定
```

### フローチャート

```mermaid
flowchart TD
    A[開始: filter Ajax] --> B[searchパラメータ取得]
    B --> C{searchが空?}
    C -->|Yes| D[RoutingException: missingRequestParameter]
    C -->|No| E[only-aggregations = true設定]
    E --> F[reduce-aggregations = true設定]
    F --> G[Criteria構築]
    G --> H[ProductSearchRoute::load]
    H --> I[集計結果取得]
    I --> J[JSON形式にマッピング]
    J --> K[JsonResponse生成]
    K --> L[noindexヘッダー設定]
    L --> M[レスポンス返却]
    M --> N[終了]
    D --> N
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-34-01 | 検索パラメータ必須 | searchパラメータが空の場合はエラー | 常時 |
| BR-34-02 | 集計のみ取得 | only-aggregationsフラグにより商品データは取得しない | 常時 |
| BR-34-03 | フィルター縮小 | reduce-aggregationsにより選択中のフィルターと組み合わせ可能な値のみ返却 | 常時 |
| BR-34-04 | SEOインデックス除外 | 検索フィルターはnoindexを設定 | 常時 |
| BR-34-05 | GET/POST両対応 | Ajax通信はGET/POSTいずれも受け付け | 常時 |
| BR-34-06 | HTTPキャッシュ有効 | HTTP_CACHE属性が有効（キャッシュ可能） | 常時 |

### 計算ロジック

**reduce-aggregationsの動作**:
- 選択中のフィルターをpost-filterからfilterに変換
- これにより、選択中のフィルターと「AND条件」で組み合わせ可能な集計値のみを計算
- 例：メーカーAを選択中の場合、メーカーAの商品が持つプロパティのみを返却

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 集計取得 | product | SELECT | メーカー、プロパティ、価格帯等の集計 |
| 集計取得 | product_manufacturer | SELECT | メーカー情報 |
| 集計取得 | product_property | SELECT | プロパティ情報 |

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

#### product (集計クエリ)

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT(集計) | manufacturer_id, property_ids, price, rating_average, shipping_free | 検索条件 AND 選択中フィルター条件 | GROUP BY + COUNT |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| MISSING_REQUEST_PARAMETER | パラメータ不足 | searchパラメータが空の場合 | エラーレスポンス返却 |

### リトライ仕様

特になし（読み取り専用処理のためリトライ不要）

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

読み取り専用処理のため、トランザクション制御なし

## パフォーマンス要件

- レスポンス時間: 300ms以内（ユーザー操作への即時フィードバック）
- only-aggregationsにより商品データ取得をスキップ
- HTTPキャッシュを活用してサーバー負荷を軽減
- Elasticsearch使用時は集計処理も高速化

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

- XSS対策としてJSON形式で返却
- SQLインジェクション対策としてDAL（Data Abstraction Layer）を使用
- フィルター結果はnoindexを設定し、検索エンジンへの悪影響を防止

## 備考

- フィルターパネルの表示制御はフロントエンド（JavaScript）側で実装
- 集計結果のキャッシュはHTTPレベルで行われる

---

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

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

### 推奨読解順序

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

まず、フィルター機能で使用される集計データ構造を理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | AggregationResultCollection.php | `src/Core/Framework/DataAbstractionLayer/Search/AggregationResult/AggregationResultCollection.php` | 集計結果コレクションの構造 |
| 1-2 | AggregationResult.php | `src/Core/Framework/DataAbstractionLayer/Search/AggregationResult/AggregationResult.php` | 個別集計結果の構造 |
| 1-3 | ProductListingResult.php | `src/Core/Content/Product/SalesChannel/Listing/ProductListingResult.php` | リスティング結果（aggregationsプロパティ） |

**読解のコツ**: ProductListingResultはaggregationsプロパティを持ち、各種フィルター用の集計データを保持する。集計タイプには TermsResult（カテゴリ別件数）、StatsResult（min/max）などがある。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | SearchController.php | `src/Storefront/Controller/SearchController.php` | filterメソッドがエントリーポイント |

**主要処理フロー**:
1. **116-124行目**: ルート定義（/widgets/search/filter, GET/POST対応）
2. **125行目**: filterメソッドの開始
3. **127-130行目**: searchパラメータの取得とバリデーション
4. **133行目**: only-aggregations = true設定
5. **135行目**: reduce-aggregations = true設定
6. **136-137行目**: Criteria構築
7. **139-141行目**: ProductSearchRouteの呼び出しと結果取得
8. **142-146行目**: 集計結果をマッピング
9. **148-149行目**: JsonResponse生成とnoindexヘッダー設定

#### Step 3: 集計処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | ProductSearchRoute.php | `src/Core/Content/Product/SalesChannel/Search/ProductSearchRoute.php` | 集計処理の実行 |
| 3-2 | ProductListingLoader.php | `src/Core/Content/Product/SalesChannel/Listing/ProductListingLoader.php` | 集計条件の構築 |

**読解のコツ**: only-aggregationsとreduce-aggregationsの組み合わせにより、効率的にフィルター用集計データを取得する。

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

```
SearchController::filter()
    │
    ├─ RequestParamHelper::get('search') [検索キーワード取得]
    │
    ├─ request->request->set('only-aggregations', true)
    │
    ├─ request->request->set('reduce-aggregations', true)
    │
    ├─ Criteria [title: 'search-page']
    │
    ├─ ProductSearchRoute::load()
    │      │
    │      └─ ProductListingResult
    │             └─ aggregations (AggregationResultCollection)
    │
    ├─ foreach aggregations as aggregation
    │      └─ mapped[name] = aggregation
    │
    └─ JsonResponse [noindexヘッダー付き]
```

### データフロー図

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

search ───────────┐
manufacturer ─────┤
properties ───────┼──▶ ProductSearchRoute ────────▶ AggregationResultCollection
min-price ────────┤      (only-aggregations)             │
max-price ────────┤            │                         ├─ manufacturer
shipping-free ────┤            ▼                         ├─ properties
rating ───────────┘    product テーブル                  ├─ price
                       (集計クエリ)                      ├─ rating
                            │                            └─ shipping-free
                            ▼                                  │
                    AggregationResultCollection                ▼
                                                         JsonResponse
                                                    (noindex ヘッダー付き)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| SearchController.php | `src/Storefront/Controller/SearchController.php` | コントローラー | HTTPリクエスト処理 |
| AbstractProductSearchRoute.php | `src/Core/Content/Product/SalesChannel/Search/AbstractProductSearchRoute.php` | 抽象クラス | Store-API検索ルート契約 |
| ProductSearchRoute.php | `src/Core/Content/Product/SalesChannel/Search/ProductSearchRoute.php` | Store-APIルート | 商品検索・集計処理 |
| ProductListingResult.php | `src/Core/Content/Product/SalesChannel/Listing/ProductListingResult.php` | DTO | リスティング結果 |
| AggregationResultCollection.php | `src/Core/Framework/DataAbstractionLayer/Search/AggregationResult/AggregationResultCollection.php` | コレクション | 集計結果コレクション |
| RequestParamHelper.php | `src/Core/Framework/Adapter/Request/RequestParamHelper.php` | ヘルパー | リクエストパラメータ取得 |
| listing-filter.plugin.js | `src/Storefront/Resources/app/storefront/src/plugin/listing/filter-*.plugin.js` | JavaScript | フロントエンドフィルター処理 |
