# 分页

CodeIgniter 提供了一个简洁但灵活的分页类，易于定制主题，支持与模型配合使用， 并能在单个页面上支持多个分页器。

## 加载类

与 CodeIgniter 中的所有服务一样，可通过 `Config\Services` 加载，但通常不需要手动加载：

    <?php

    $pager = service('pager');

## 使用模型进行分页

大多数情况下，分页库用于对从数据库检索的结果进行分页。 使用 模型 类时，可利用其内置的 `paginate()` 方法自动 获取当前批次的结果，并设置好 Pager 库以便在控制器中使用。 该方法还会从当前 URL 的 `page=X` 查询变量中自动读取应显示的页码。

要在应用中提供分页的用户列表，控制器方法大致如下：

    <?php

    namespace App\Controllers;

    use App\Models\UserModel;

    class UserController extends BaseController
    {
        public function index()
        {
            $model = model(UserModel::class);

            $data = [
                'users' => $model->paginate(10),
                'pager' => $model->pager,
            ];

            return view('users/index', $data);
        }
    }

在此示例中，首先创建 `UserModel` 的新实例，然后准备发送到视图的数据。 第一个元素是数据库查询结果 users，按正确页码返回，每页 10 条用户记录。第二个必须发送到视图的元素是 Pager 实例。为方便起见， 模型会保留所使用的实例并将其存储在公共属性 `$pager` 中。因此，直接 将其赋值给视图中的 `$pager` 变量即可。

### 自定义分页查询

要在模型中自定义分页查询，可在 `paginate()` 方法前添加 查询构建器 方法。

#### 添加 WHERE 条件

如需添加 WHERE 条件，可直接指定：

    use App\Models\UserModel;

    // In your Controller.
    $model = model(UserModel::class);

    $data = [
        'users' => $model->where('ban', 1)->paginate(10),
        'pager' => $model->pager,
    ];

也可将条件移至独立方法中：

    <?php

    namespace App\Models;

    use CodeIgniter\Model;

    class UserModel extends Model
    {
        // ...

        public function banned()
        {
            $this->builder()->where('ban', 1);

            return $this; // This will allow the call chain to be used.
        }
    }

    use App\Models\UserModel;

    // In your Controller.
    $model = model(UserModel::class);

    $data = [
        'users' => $model->banned()->paginate(10),
        'pager' => $model->pager,
    ];

#### 添加 JOIN

可连接其他表：

    <?php

    namespace App\Models;

    use CodeIgniter\Model;

    class NewsModel extends Model
    {
        protected $table = 'news';

        // ...

        public function getPagination(?int $perPage = null): array
        {
            $this->builder()
                ->select('news.*, category.name')
                ->join('category', 'news.category_id = category.id');

            return [
                'news'  => $this->paginate($perPage),
                'pager' => $this->pager,
            ];
        }
    }

重要

需要理解的是，`Model::paginate()` 方法 使用的是模型内部的 模型 和 查询构建器 实例。 因此，尝试将 `Model::paginate()` 与 $db->query() 一起使用 无法正常工作，因为 `$db->query()` 会立即执行查询， 与查询构建器无关。

如需执行无法用查询构建器编写的复杂 SQL 查询， 请尝试使用 $db->query() 和 手动分页。

### 显示分页链接

在视图中，需要指定分页链接的显示位置:

    <?= $pager->links() ?>

仅此而已。Pager 类会渲染首页和末页链接，以及当前页两侧超过两页时的 "上一页"和"下一页"链接。

需要注意的是，库中"上一页"和"下一页"的链接模式与传统分页方式不同。

此处的"下一页"和"上一页"链接到分页结构中要显示的链接组，而不是记录的下一页或上一页。

如需更简化的输出，可使用 `simpleLinks()` 方法，该方法仅使用"Older"和"Newer"链接， 而不显示详细的分页链接:

    <?= $pager->simpleLinks() ?>

在幕后，库会加载一个视图文件来确定链接的格式，便于根据需要修改。 详见下文关于完全自定义输出的说明。

### 分页多组结果

如需为两组不同的结果集提供链接，可向大多数分页 方法传入分组名称，以保持数据独立：

    <?php

    namespace App\Controllers;

    use App\Models\PageModel;
    use App\Models\UserModel;

    class UserController extends BaseController
    {
        public function index()
        {
            $userModel = model(UserModel::class);
            $pageModel = model(PageModel::class);

            $data = [
                'users' => $userModel->paginate(10, 'group1'),
                'pages' => $pageModel->paginate(15, 'group2'),
                'pager' => $userModel->pager,
            ];

            echo view('users/index', $data);
        }
    }
    ?>

    <!-- In your view file: -->
    <?= $pager->links('group1') ?>
    <?= $pager->simpleLinks('group2') ?>

### 手动设置页码

如需指定返回哪一页结果，可将页码作为第 3 个参数传入。 当使用不同于默认 `$_GET` 变量来控制页码时，此功能非常实用。

    <?php

    use App\Models\UserModel;

    $userModel = model(UserModel::class);
    $page      = 3;

    $users = $userModel->paginate(10, 'group1', $page);

### 指定页码的 URI 段

也可使用 URI 段作为页码，而非 page 查询参数。只需将 要使用的段号作为第 4 个参数传入即可。此时分页器生成的 URI 形如 https://domain.tld/foo/bar/[pageNumber]，而非 https://domain.tld/foo/bar?page=[pageNumber]。

    <?php

    $users = $userModel->paginate(10, 'group1', null, $segment);

请注意：`$segment` 的值不能大于 URI 段数加 1。

## 手动分页

有时可能只需基于已知数据创建分页。可使用 `makeLinks()` 方法手动创建链接，该方法依次接受当前页码、每页结果数和 总条目数作为第 1、第 2 和第 3 个参数：

    <?php

    namespace App\Controllers;

    class UserController extends BaseController
    {
        public function index()
        {
            // ...

            $pager = service('pager');

            $page    = (int) ($this->request->getGet('page') ?? 1);
            $perPage = 20;
            $total   = 200;

            // Call makeLinks() to make pagination links.
            $pager_links = $pager->makeLinks($page, $perPage, $total);

            $data = [
                // ...
                'pager_links' => $pager_links,
            ];

            return view('users/index', $data);
        }
    }
    ?>

    <!-- In your view file: -->
    <?= $pager_links ?>

默认情况下，这会以常规方式将链接显示为一系列链接，但也可通过在第 4 个参数中传入 模板名称来更改所使用的显示模板。详见下文各节:

    $pager->makeLinks($page, $perPage, $total, 'template_name');

如前一节所述，也可使用 URI 段作为页码，而非 page 查询参数。将 段号作为第 5 个参数传入 `makeLinks()` 即可:

    $pager->makeLinks($page, $perPage, $total, 'template_name', $segment);

请注意：`$segment` 的值不能大于 URI 段数加 1。

如需在同一页面显示多个分页器，则额外的分组参数会很有帮助：

    <?php

    $pager = service('pager');
    $pager->setPath('path/for/my-group', 'my-group'); // Additionally you could define path for every group.
    $pager->makeLinks($page, $perPage, $total, 'template_name', $segment, 'my-group');

默认情况下，分页库使用 page 查询参数（未指定分组或使用 `default` 分组名时）， 或使用 `page_[groupName]` 作为自定义组名。

## 仅使用预期查询进行分页

默认情况下，分页链接会显示所有 GET 查询参数。

例如，访问 URL https://domain.tld?search=foo&order=asc&hello=i+am+here&page=2 时，第 3 页的链接及其他链接将按如下方式生成：

    <?php

    echo $pager->links();
    // Page 3 link: https://domain.tld?search=foo&order=asc&hello=i+am+here&page=3

`only()` 方法可将链接中的查询参数限制为仅预期的参数：

    <?php

    echo $pager->only(['search', 'order'])->links();
    // Page 3 link: https://domain.tld?search=foo&order=asc&page=3

page 查询参数默认启用。`only()` 对所有分页链接生效。

## 自定义链接

### 视图配置

渲染分页链接时，使用视图文件来描述 HTML 结构。可通过编辑 app/Config/Pager.php 轻松 更改所使用的视图：

    <?php

    namespace Config;

    use CodeIgniter\Config\BaseConfig;

    class Pager extends BaseConfig
    {
        public $templates = [
            'default_full'   => 'CodeIgniter\Pager\Views\default_full',
            'default_simple' => 'CodeIgniter\Pager\Views\default_simple',
        ];

        // ...
    }

此设置存储视图的别名和 命名空间视图路径。 `default_full` 和 `default_simple` 视图分别用于 `links()` 和 `simpleLinks()` 方法。要更改应用范围内的显示方式，可在此处分配新视图。

例如，假设创建了一个适配 Foundation CSS 框架的新视图文件， 并将其放置在 app/Views/Pagers/foundation_full.php。由于 application 目录 的命名空间为 `App`，其下的所有目录直接映射到命名空间的对应段，因此可通过命名空间定位 视图文件:

    'default_full' => 'App\Views\Pagers\foundation_full'

由于该文件位于标准的 app/Views 目录下，无需使用命名空间， 因为 `view()` 方法可通过文件名定位。此时，只需指定子目录和文件名即可:

    'default_full' => 'Pagers/foundation_full'

创建视图并在配置中设置后，系统会自动使用它。无需替换现有模板。 可根据需要在配置文件中创建任意数量的额外模板。 常见场景是应用的前台和后台需要不同样式的分页器。

    <?php

    namespace Config;

    use CodeIgniter\Config\BaseConfig;

    class Pager extends BaseConfig
    {
        public $templates = [
            'default_full'   => 'CodeIgniter\Pager\Views\default_full',
            'default_simple' => 'CodeIgniter\Pager\Views\default_simple',
            'front_full'     => 'App\Views\Pagers\foundation_full',
        ];

        // ...
    }

配置完成后，可在 `links()`、`simpleLinks()` 和 `makeLinks()` 方法的最后一个参数中指定:

    <?= $pager->links('group1', 'front_full') ?>
    <?= $pager->simpleLinks('group2', 'front_full') ?>
    <?= $pager->makeLinks($page, $perPage, $total, 'front_full') ?>

### 创建视图

创建新视图时，只需编写生成分页链接所需的代码。 不应创建多余的外层 div，因为视图可能在多处使用，多余结构会降低其适用性。 以下通过展示现有的 `default_full` 模板来说明如何创建新视图：

    <?php $pager->setSurroundCount(2) ?>

    <nav aria-label="Page navigation">
        <ul class="pagination">
        <?php if ($pager->hasPrevious()) : ?>
            <li>
                <a href="<?= $pager->getFirst() ?>" aria-label="<?= lang('Pager.first') ?>">
                    <span aria-hidden="true"><?= lang('Pager.first') ?></span>
                </a>
            </li>
            <li>
                <a href="<?= $pager->getPrevious() ?>" aria-label="<?= lang('Pager.previous') ?>">
                    <span aria-hidden="true"><?= lang('Pager.previous') ?></span>
                </a>
            </li>
        <?php endif ?>

        <?php foreach ($pager->links() as $link): ?>
            <li <?= $link['active'] ? 'class="active"' : '' ?>>
                <a href="<?= $link['uri'] ?>">
                    <?= $link['title'] ?>
                </a>
            </li>
        <?php endforeach ?>

        <?php if ($pager->hasNext()) : ?>
            <li>
                <a href="<?= $pager->getNext() ?>" aria-label="<?= lang('Pager.next') ?>">
                    <span aria-hidden="true"><?= lang('Pager.next') ?></span>
                </a>
            </li>
            <li>
                <a href="<?= $pager->getLast() ?>" aria-label="<?= lang('Pager.last') ?>">
                    <span aria-hidden="true"><?= lang('Pager.last') ?></span>
                </a>
            </li>
        <?php endif ?>
        </ul>
    </nav>

#### setSurroundCount()

在第一行中，`setSurroundCount()` 方法指定在当前页链接两侧各显示两个链接。 它唯一接受的参数是要显示的链接数量。

备注

必须首先调用此方法才能生成正确的分页链接。

#### hasPrevious() 和 hasNext()

根据传递给 setSurroundCount() 的值，如果当前页两侧还有可显示的链接， 这些方法分别返回布尔值 `true`。

例如，假设有 20 页数据。当前 页是第 3 页。如果 surroundCount 为 2，则显示的链接如下:

    1  |  2  |  3  |  4  |  5

由于第一个显示的链接是第 1 页，`hasPrevious()` 会返回 `false`，因为不存在第 0 页。 而 `hasNext()` 会返回 `true`，因为第 5 页之后还有 15 页。

#### getPrevious() 和 getNext()

这些方法返回编号链接两侧上一页或下一页的 URL。

例如，当前页设为 5，前后两侧（surroundCount）各设为 2，结果如下:

    3  |  4  |  5  |  6  |  7

`getPrevious()` 返回第 2 页的 URL。`getNext()` 返回第 8 页的 URL。

如需获取第 4 页和第 6 页，请改用 getPreviousPage() 和 getNextPage()。

#### getFirst() 和 getLast()

与 getPrevious() 和 getNext() 类似，这些方法返回结果集中首页和末页的 URL。

#### links()

返回所有编号链接的数据数组。每个链接的数组包含链接的 URI、 标题（即数字），以及指示是否为当前活动链接的布尔值：

    <?php

    $link = [
        'active' => false,
        'uri'    => 'https://example.com/foo?page=2',
        'title'  => 1,
    ];

在上述标准分页结构的代码中，使用 getPrevious() 和 getNext() 方法分别获取上一页和下一页的链接。

如果希望使用基于当前页的上一页和下一页链接的分页结构， 只需将 getPrevious() 和 getNext() 方法替换为 getPreviousPage() 和 getNextPage()， 并将 hasPrevious() 和 hasNext() 方法替换为 hasPreviousPage() 和 hasNextPage() 即可。

请参阅以下使用这些改动的示例：

    <nav aria-label="<?= lang('Pager.pageNavigation') ?>">
        <ul class="pagination">
            <?php if ($pager->hasPreviousPage()) : ?>
                <li>
                    <a href="<?= $pager->getFirst() ?>" aria-label="<?= lang('Pager.first') ?>">
                        <span aria-hidden="true"><?= lang('Pager.first') ?></span>
                    </a>
                </li>
                <li>
                    <a href="<?= $pager->getPreviousPage() ?>" aria-label="<?= lang('Pager.previous') ?>">
                        <span aria-hidden="true"><?= lang('Pager.previous') ?></span>
                    </a>
                </li>
            <?php endif ?>

            <?php foreach ($pager->links() as $link): ?>
                <li <?= $link['active'] ? 'class="active"' : '' ?>>
                    <a href="<?= $link['uri'] ?>">
                        <?= $link['title'] ?>
                    </a>
                </li>
            <?php endforeach ?>

            <?php if ($pager->hasNextPage()) : ?>
                <li>
                    <a href="<?= $pager->getNextPage() ?>" aria-label="<?= lang('Pager.next') ?>">
                        <span aria-hidden="true"><?= lang('Pager.next') ?></span>
                    </a>
                </li>
                <li>
                    <a href="<?= $pager->getLast() ?>" aria-label="<?= lang('Pager.last') ?>">
                        <span aria-hidden="true"><?= lang('Pager.last') ?></span>
                    </a>
                </li>
            <?php endif ?>
        </ul>
    </nav>

#### hasPreviousPage() 和 hasNextPage()

如果当前显示页的前后分别有可跳转的页面，此方法返回布尔值 `true`。

例如，假设有 20 页数据。当前 页是第 3 页。如果 surroundCount 为 2，则显示的链接如下:

    1  |  2  |  3  |  4  |  5

`hasPreviousPage()` 会返回 `true`，因为存在第 2 页。 `hasNextPage()` 会返回 `true`，因为存在第 4 页。

备注

与 hasPrevious() 和 hasNext() 的区别在于，此方法基于 当前页判断，而 hasPrevious() 和 hasNext() 基于传递给 setSurroundCount() 的值所确定的链接集合。

#### getPreviousPage() 和 getNextPage()

这些方法返回相对于当前显示页的上一页和下一页的 URL。

例如，当前页设为 5，前后两侧（surroundCount）各设为 2，结果如下:

    3  |  4  |  5  |  6  |  7

`getPreviousPage()` 返回第 4 页的 URL。`getNextPage()` 返回第 6 页的 URL。

备注

getPrevious() 和 getNext() 返回编号链接两侧上一页或下一页的 URL。

如需使用页码而非 URL，可使用以下方法：

#### getPreviousPageNumber() 和 getNextPageNumber()

这些方法返回相对于当前显示页的上一页和下一页的页码。

#### getFirstPageNumber() 和 getLastPageNumber()

这些方法返回要显示的链接集合中首页和末页的页码。 例如，如果要显示的链接集合如下:

    3  |  4  |  5  |  6  |  7

`getFirstPageNumber()` 返回 3，`getLastPageNumber()` 返回 7。

备注

如需获取整个结果集中首页和末页的页码， 可使用以下方法：首页页码始终为 1，末页页码可通过 getPageCount() 获取。

#### getCurrentPageNumber()

此方法返回当前页的页码。

#### getPageCount()

此方法返回总页数。

### 显示当前页的条目数量

Added in version 4.6.0.

对条目进行分页时，显示总条目数和当前页展示的条目范围通常很有帮助。为简化此任务，新增了以下方法。 这些方法让管理和展示分页信息更加便捷。示例如下：

    <?php $pager->setSurroundCount(1) ?>

    <p>
        Showing <span class="font-medium"><?= $pager->getPerPageStart() ?></span>
        to <span class="font-medium"><?= $pager->getPerPageEnd() ?></span>
        of <span class="font-medium"><?= $pager->getTotal() ?></span> results
    </p>

#### getTotal()

返回页面的总条目数。

#### getPerPage()

返回每页应显示的条目数。

#### getPerPageStart()

返回当前页起始条目的序号。

#### getPerPageEnd()

返回当前页结束条目的序号。