# 機能設計書 50-ページネーション

## 概要

本ドキュメントは、Laravel Frameworkのページネーション機能の設計仕様を定義する。Paginator、LengthAwarePaginator、CursorPaginatorクラスを中心とした、データのページ分割機能について詳述する。

### 本機能の処理概要

ページネーションは、大量のデータを複数ページに分割して表示する機能を提供する。オフセットベースのページネーション（通常のページ番号方式）とカーソルベースのページネーション（効率的な大量データ処理向け）の両方をサポートする。

**業務上の目的・背景**：大量のデータを一度に表示すると、パフォーマンスの低下やUXの悪化を招く。ページネーションにより、データを適切なサイズに分割して表示し、ユーザーが必要な情報に効率的にアクセスできるようになる。また、サーバーリソースの節約にも寄与する。

**機能の利用シーン**：商品一覧、検索結果、管理画面の一覧表示、APIレスポンスのページング、無限スクロールなどで利用される。

**主要な処理内容**：
1. オフセットベースのページネーション（Paginator、LengthAwarePaginator）
2. カーソルベースのページネーション（CursorPaginator）
3. ページリンクの生成
4. JSON/配列へのシリアライズ
5. Bladeビューでのレンダリング

**関連システム・外部連携**：Eloquent ORM、クエリビルダ、Bladeテンプレート、APIリソースと連携する。

**権限による制御**：ページネーション自体には権限制御は存在しないが、ページネーション対象のデータに対する認可チェックが別途行われる。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | 画面機能マッピングにページネーション関連の画面なし |

## 機能種別

データ表示 / UI補助

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| items | Collection\|array | Yes | ページネーション対象のアイテム | - |
| perPage | int | Yes | 1ページあたりの件数 | 正の整数 |
| currentPage | int\|null | No | 現在のページ番号（null時はリクエストから取得） | 正の整数 |
| total | int | Yes（LengthAware） | 全件数 | 正の整数 |
| cursor | Cursor\|null | No | カーソル（CursorPaginatorで使用） | - |
| options | array | No | オプション設定（path、query、fragment、pageName） | - |

### 入力データソース

- Eloquent/クエリビルダのpaginate()、simplePaginate()、cursorPaginate()
- 開発者コードからの直接インスタンス化

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| paginator | Paginator\|LengthAwarePaginator\|CursorPaginator | ページネーターインスタンス |

### 出力先

- Bladeテンプレートでのリンクレンダリング
- APIレスポンス（JSON）

## 処理フロー

### 処理シーケンス

```
1. ページネーター作成
   └─ コンストラクタでitems、perPage、currentPage/cursor等を設定

2. アイテム設定
   └─ setItems()でアイテムをCollection化し、perPageを超える分をスライス

3. hasMorePages判定
   └─ hasMore（シンプル）またはcurrentPage < lastPage（LengthAware）で判定

4. URL生成
   └─ url()でページ番号/カーソル付きURLを生成

5. レンダリング
   └─ render()またはlinks()でBladeビューを使用してHTML生成
```

### フローチャート

```mermaid
flowchart TD
    A[ページネーター作成] --> B{種別}
    B -->|Paginator| C[SimplePaginator処理]
    B -->|LengthAwarePaginator| D[LengthAware処理]
    B -->|CursorPaginator| E[Cursor処理]
    C --> F[setItems でアイテム設定]
    D --> G[total, lastPage 計算]
    E --> H[cursor方向でアイテム処理]
    F --> I{hasMore判定}
    G --> J{hasMorePages判定}
    H --> K{hasMorePages判定}
    I --> L[url 生成]
    J --> L
    K --> L
    L --> M[render/links でHTML生成]
    M --> N[出力]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-50-1 | hasMore判定 | items数がperPageを超える場合、次ページありと判定 | Paginator, CursorPaginator |
| BR-50-2 | lastPage計算 | ceil(total / perPage)で最終ページを計算、最小1 | LengthAwarePaginator |
| BR-50-3 | 無効ページ番号 | 1未満または非数値の場合はページ1として扱う | isValidPageNumber() |
| BR-50-4 | カーソル方向 | カーソルがpreviousItemsを指す場合、アイテムを逆順に並べ替え | CursorPaginator |

### 計算ロジック

| 計算名 | 計算式 | 説明 |
|--------|-------|------|
| lastPage | max((int) ceil($total / $perPage), 1) | 最終ページ番号 |
| firstItem | ($currentPage - 1) * $perPage + 1 | 現在ページの最初のアイテム番号 |
| lastItem | $firstItem + $items->count() - 1 | 現在ページの最後のアイテム番号 |

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| - | - | - | ページネーション自体はデータベース操作を行わない（Eloquent/クエリビルダが実行） |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| RuntimeException | ビューファクトリ未設定 | resolveViewFactoryコールバック未設定 | viewFactory()メソッドを呼び出し前にコールバックを設定 |

### リトライ仕様

リトライ処理は実装されていない。

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

ページネーションはトランザクション管理を行わない。

## パフォーマンス要件

- LengthAwarePaginatorはCOUNTクエリが追加で発行される
- CursorPaginatorはCOUNTクエリ不要で大量データに効率的
- SimplePaginatorはCOUNTクエリ不要だが、ページ総数は不明

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

- ページ番号やカーソルは外部入力として扱い、適切なバリデーションを実施
- perPageの上限を設定してDoS攻撃を防止

## 備考

- paginate()、simplePaginate()、cursorPaginate()ヘルパーメソッドで簡単に利用可能
- TailwindCSS、Bootstrap 4/5用のビューテンプレートを標準提供

---

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

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

### 推奨読解順序

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

各ペジネータークラスの構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | AbstractPaginator.php | `src/Illuminate/Pagination/AbstractPaginator.php` | 共通プロパティと基底メソッド |
| 1-2 | LengthAwarePaginator.php | `src/Illuminate/Pagination/LengthAwarePaginator.php` | total、lastPageの管理 |
| 1-3 | Paginator.php | `src/Illuminate/Pagination/Paginator.php` | シンプルなhasMore管理 |
| 1-4 | CursorPaginator.php | `src/Illuminate/Pagination/CursorPaginator.php` | カーソルベースのページング |

**読解のコツ**: AbstractPaginatorが共通機能（URL生成、ビューレンダリング等）を提供し、各サブクラスがhasMorePages()の実装を持つ。

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

ページネーター生成の入口を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | LengthAwarePaginator.php | `src/Illuminate/Pagination/LengthAwarePaginator.php` | __construct()（51-65行目） |
| 2-2 | Paginator.php | `src/Illuminate/Pagination/Paginator.php` | __construct()（43-56行目） |

**主要処理フロー**:
1. **LengthAwarePaginator 59-61行目**: total、perPage、lastPageを設定
2. **Paginator 55行目**: setItems()でアイテムを設定

#### Step 3: アイテム設定処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | Paginator.php | `src/Illuminate/Pagination/Paginator.php` | setItems()（77-84行目） |
| 3-2 | CursorPaginator.php | `src/Illuminate/Pagination/CursorPaginator.php` | setItems()（64-75行目） |

**主要処理フロー**:
- **Paginator 79行目**: Collectionに変換
- **Paginator 81行目**: items数 > perPageならhasMore=true
- **Paginator 83行目**: perPage分にスライス
- **CursorPaginator 72-74行目**: previousItems方向ならreverse

#### Step 4: ページ判定処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | LengthAwarePaginator.php | `src/Illuminate/Pagination/LengthAwarePaginator.php` | hasMorePages()（174-177行目） |
| 4-2 | Paginator.php | `src/Illuminate/Pagination/Paginator.php` | hasMorePages()（142-145行目） |
| 4-3 | CursorPaginator.php | `src/Illuminate/Pagination/CursorPaginator.php` | hasMorePages()（108-113行目） |

**主要処理フロー**:
- **LengthAwarePaginator**: currentPage() < lastPage()
- **Paginator**: $hasMore変数を返却
- **CursorPaginator**: カーソル方向に応じた複合判定

#### Step 5: URL生成とレンダリングを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | AbstractPaginator.php | `src/Illuminate/Pagination/AbstractPaginator.php` | url() - ページ番号付きURL生成 |
| 5-2 | LengthAwarePaginator.php | `src/Illuminate/Pagination/LengthAwarePaginator.php` | render()（100-106行目） |
| 5-3 | LengthAwarePaginator.php | `src/Illuminate/Pagination/LengthAwarePaginator.php` | elements()（146-157行目） |

**主要処理フロー**:
- **url()**: クエリパラメータにページ番号を追加
- **render()**: viewFactory()->make()でBladeビュー生成
- **elements()**: UrlWindowで表示するページ番号の配列を生成

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

```
Model::paginate($perPage) [Eloquent]
    │
    └─ Builder::paginate($perPage, $columns, $pageName, $page)
           │
           ├─ getCountForPagination() → $total
           │
           ├─ forPage($page, $perPage)->get($columns) → $results
           │
           └─ new LengthAwarePaginator($results, $total, $perPage, $page, $options)
                  │
                  ├─ setCurrentPage($page)
                  │
                  ├─ $lastPage = max(ceil($total / $perPage), 1)
                  │
                  └─ $items = $results instanceof Collection ? $results : new Collection($results)

LengthAwarePaginator::render($view)
    │
    └─ viewFactory()->make($view, ['paginator' => $this, 'elements' => $this->elements()])
           │
           └─ elements()
                  │
                  └─ UrlWindow::make($this)
                         │
                         └─ ['first' => [...], 'slider' => [...], 'last' => [...]]

LengthAwarePaginator::toArray()
    │
    └─ ['current_page', 'data', 'first_page_url', 'from', 'last_page', ...]
```

### データフロー図

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

$items, $perPage  ───▶  __construct()            ───▶  Paginator
(アイテムと件数)          (初期化)

$items            ───▶  setItems()               ───▶  スライス済みCollection
(元データ)               (スライス処理)

$page             ───▶  url($page)               ───▶  ページURL文字列
(ページ番号)             (URL生成)

Paginator         ───▶  elements()               ───▶  ページ番号配列
(ペジネーター)           (表示要素生成)

Paginator         ───▶  render()                 ───▶  HTML文字列
(ペジネーター)           (ビューレンダリング)

Paginator         ───▶  toArray()                ───▶  配列/JSON
(ペジネーター)           (シリアライズ)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| AbstractPaginator.php | `src/Illuminate/Pagination/AbstractPaginator.php` | ソース | オフセットベースページネーター基底クラス |
| AbstractCursorPaginator.php | `src/Illuminate/Pagination/AbstractCursorPaginator.php` | ソース | カーソルベースページネーター基底クラス |
| LengthAwarePaginator.php | `src/Illuminate/Pagination/LengthAwarePaginator.php` | ソース | 総件数認識ページネーター |
| Paginator.php | `src/Illuminate/Pagination/Paginator.php` | ソース | シンプルページネーター |
| CursorPaginator.php | `src/Illuminate/Pagination/CursorPaginator.php` | ソース | カーソルベースページネーター |
| Cursor.php | `src/Illuminate/Pagination/Cursor.php` | ソース | カーソル値オブジェクト |
| UrlWindow.php | `src/Illuminate/Pagination/UrlWindow.php` | ソース | ページリンク表示範囲計算 |
| PaginationServiceProvider.php | `src/Illuminate/Pagination/PaginationServiceProvider.php` | ソース | サービスプロバイダ |
| LengthAwarePaginator.php | `src/Illuminate/Contracts/Pagination/LengthAwarePaginator.php` | コントラクト | 総件数認識インターフェース |
| Paginator.php | `src/Illuminate/Contracts/Pagination/Paginator.php` | コントラクト | ページネーターインターフェース |
| CursorPaginator.php | `src/Illuminate/Contracts/Pagination/CursorPaginator.php` | コントラクト | カーソルページネーターインターフェース |
