esi/pagination

简单、轻量且通用的服务,用于实现事物集合的分页。

v2.0.1 2024-03-02 04:44 UTC

This package is auto-updated.

Last update: 2024-09-22 11:34:42 UTC


README

Build Status Code Coverage Scrutinizer Code Quality Tests PHPStan Psalm Static analysis Type Coverage Psalm Level Latest Stable Version Downloads per Month License

实现事物集合分页接口的分页库。

鸣谢

此库是AshleyDawson\SimplePagination(https://github.com/AshleyDawson/SimplePagination)库的分支,由Ashley Dawson(https://github.com/AshleyDawson)创建。

要查看与原始库相比此库中更改的列表,请参阅CHANGELOG.md文件。

安装

您可以通过Composer安装Pagination。为此,只需在您的composer.json文件中require该包即可,如下所示

{
    "require": {
        "esi/pagination": "^2.0"
    }
}

然后运行composer update来安装包。

Pagination的工作原理

我尽量使Pagination尽可能简单、灵活和易于使用。Pagination的操作由四个主要元素描述。这些是

  • Paginator服务
  • 项目总数回调
  • 切片回调
  • 分页模型

Paginator服务执行分页算法,生成页面范围和项目集合切片。完成时,它将返回一个包含项目集合切片和元数据的Pagination对象。

Paginator服务对您的集合(或数据集)执行的两个主要操作由传递给Paginator服务的两个回调方法表示。第一个是项目总数回调。此回调用于确定您的集合中的项目总数(以整数形式返回)。第二个是切片回调。此回调根据提供的偏移量长度参数实际切片您的集合。

使用这些回调的目的是使Pagination保持简单!真正的力量来自于灵活性。您可以使用Pagination与几乎任何您想要的集合一起使用。从简单的数组到数据库列表到Doctrine集合到Solr结果集 - 我们为您提供了所有这些!实际上,我们分页的内容并不重要 - 只要它是事物集合,并且您可以计数和切片它。

基本用法

好吧,让我们从最基本的例子开始 - 对数组进行分页。

use Esi\Pagination\Paginator;

// Build a mock list of items we want to paginate through.
$items = [
    'Banana',
    'Apple',
    'Cherry',
    'Lemon',
    'Pear',
    'Watermelon',
    'Orange',
    'Grapefruit',
    'Blackcurrant',
    'Dingleberry',
    'Snosberry',
    'Tomato',
];

// Instantiate a new paginator service.
$paginator = new Paginator();

// Set some parameters (optional).
$paginator
    ->setItemsPerPage(10) // Give us a maximum of 10 items per page.
    ->setPagesInRange(5)  // How many pages to display in navigation (e.g. if we have a lot of pages to get through).
;

// Pass our item total callback.
$paginator->setItemTotalCallback(function () use ($items): int {
    return count($items);
});

// Pass our slice callback.
$paginator->setSliceCallback(function (int $offset, int $length) use ($items): array {
    return array_slice($items, $offset, $length);
});

// Paginate the item collection, passing the current page number (e.g. from the current request).
$pagination = $paginator->paginate(filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT));

// Ok, from here on is where we'd be inside a template of view (e.g. pass $pagination to your view).

// Iterate over the items on this page.
foreach ($pagination->getItems() as $item) {
    echo $item . '<br />';
}

// Let's build a basic page navigation structure.
foreach ($pagination->getPages() as $page) {
    echo '<a href="?page=' . $page . '">' . $page . '</a> ';
}

分页对象中还有许多其他元数据。这些可以用于构建第一、最后、上一页和下一页按钮。

MySQL示例

让我们以上面的例子为基础,使用MySQL结果集而不是数组。

use Esi\Pagination\Paginator;

// Instantiate a new paginator service.
$paginator = new Paginator();

// Set some parameters (optional).
$paginator
    ->setItemsPerPage(10) // Give us a maximum of 10 items per page.
    ->setPagesInRange(5)  // How many pages to display in navigation (e.g. if we have a lot of pages to get through).
;

// Connect to a database.
$mysql = new mysqli('localhost', 'root', 'password', 'myDatabase');

// Pass our item total callback.
$paginator->setItemTotalCallback(function () use($mysql): int {

    // Run count query.
    $result = $mysql->query("SELECT COUNT(*) AS `totalCount` FROM `TestData`");
    $row = $result->fetch_array(MYSQLI_ASSOC);
    
    // Return the count, cast as an integer.
    return (int) $row['totalCount'];
});

// Pass our slice callback.
$paginator->setSliceCallback(function (int $offset, int $length) use ($mysql): array {
    // Run slice query.
    $result = $mysql->query("SELECT `Name` FROM `TestData` LIMIT $offset, $length");

    // Build a collection of items.
    $collection = [];

    while ($row = $result->fetch_assoc()) {
        $collection[] = $row;
    }
    
    // Return the collection.
    return $collection;
});

// Paginate the item collection, passing the current page number (e.g. from the current request).
$pagination = $paginator->paginate(filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT));

// Ok, from here on is where we'd be inside a template of view (e.g. pass $pagination to your view).

// Iterate over the items on this page.
foreach ($pagination->getItems() as $item) {
    echo $item['Name'] . '<br />';
}

// Let's build a basic page navigation structure.
foreach ($pagination->getPages() as $page) {
    echo '<a href="?page=' . $page . '">' . $page . '</a> ';
}

注意:上面的例子使用了mysqli等,因为我尽量使其尽可能简单。在现实世界中,请使用PDODoctrine DBAL等。

您不需要从Paginator::setSliceCallback()回调返回什么样的集合。它最终都会出现在Pagination::getItems()中。

构造函数配置

您还可以通过将配置数组传递给构造函数来配置分页器。例如

$paginator = new Paginator([
    'itemTotalCallback' => function () {
        // ...
    },
    'sliceCallback' => function (int $offset, int $length) {
        // ...
    },
    'itemsPerPage' => 10,
    'pagesInRange' => 5,
]);

分页作为迭代器

Paginator 服务返回的 Pagination 对象实现了 \IteratorAggregate\Countable,因此您可以在视图中进行如下操作

if (count($pagination) > 0) {
    foreach ($pagination as $item) {
        echo $item . '<br />';
    }
}

任意的分页元数据

在项目总数和切片回调期间,您可以选择将任意元数据传递给分页对象。这是一个可选功能,如果您有一个使用场景,其中这些操作返回额外的数据,并且您想在列出项目时访问它,则非常有用。一个很好的例子是在使用搜索引擎(如 ElasticSearch)时,您可以传递回二级信息,如聚合等。下面是一个通用示例

use Esi\Pagination\Pagination;

// ...

$paginator->setItemTotalCallback(function (Pagination $pagination) use ($items): int {
    // Pass arbitrary metadata to pagination object.
    $pagination->setMeta(['my', 'meta', 'data']);
    
    return count($items);
});

$paginator->setSliceCallback(function (int $offset, int $length, Pagination $pagination) use ($items): array {
    // Pass more arbitrary metadata to pagination object.
    $pagination->setMeta(array_merge($pagination->getMeta(), ['more', 'stuff']));

    return array_slice($items, $offset, $length);
});

// ...

// Perform the pagination
$pagination = $paginator->paginate(filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT));

// Get the metadata from the pagination object.
var_dump($pagination->getMeta());

查询前后回调

在计数和切片查询之前和之后,您可以设置触发回调。要设置它们,请执行以下操作

$paginator->setBeforeQueryCallback(function (Paginator $paginator, Pagination $pagination) {

});

$paginator->setAfterQueryCallback(function (Paginator $paginator, Pagination $pagination) {

});

如果您想在每次查询前后执行某些功能,这将非常有用。

分页对象

Paginator::paginate() 操作的结果是生成一个 Pagination 模型对象,它携带当前页的项目集合以及集合的元信息,例如页面数组、下一页编号、上一页编号等。

以下列出 Pagination 对象具有的属性列表。

  • items : 数组(当前页的项目集合)
  • pages : 数组(当前范围内的页面编号数组)
  • totalNumberOfPages : int(总页数)
  • currentPageNumber : int(当前页码)
  • firstPageNumber : int(第一页码)
  • lastPageNumber : int(最后一页码)
  • previousPageNumber : int | null(上一页码)
  • nextPageNumber : int | null(下一页码)
  • itemsPerPage : int(每页项目数)
  • totalNumberOfItems : int(项目总数)
  • firstPageNumberInRange : int(当前范围内的第一页码)
  • lastPageNumberInRange : int(当前范围内的最后一页码)

使用 Pagination 对象的一个好例子是构建一个简单的分页导航结构

// Render the first page link,
echo '<a href="?page=' . $pagination->getFirstPageNumber() . '">First Page</a> ';

// Render the previous page link (note: the previous page number could be null),
echo '<a href="?page=' . $pagination->getPreviousPageNumber() . '">Previous Page</a> ';

// Render page range links,
foreach ($pagination->getPages() as $page) {
    echo '<a href="?page=' . $page . '">' . $page . '</a> ';
}

// Render the next page link (note: the next page number could be null),
echo '<a href="?page=' . $pagination->getNextPageNumber() . '">Next Page</a> ';

// Render the last page link,
echo '<a href="?page=' . $pagination->getLastPageNumber() . '">Last Page</a>';

关于

要求

  • 分页与 PHP 8.2.0 或更高版本兼容。

提交错误和功能请求

错误和功能请求在 GitHub 上跟踪

问题是最快报告错误的途径。如果您发现错误或文档错误,请首先检查以下内容

  • 没有关于错误的已打开问题
  • 问题尚未被解决(例如,在已关闭的问题中)

贡献

请参阅 CONTRIBUTING

作者

Eric Sizemore - admin@secondversion.com - https://www.secondversion.com

许可

分页采用 MIT 许可证 - 详细信息请参阅 LICENSE.md 文件