定制 mention/fast-doctrine-paginator 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

mention/fast-doctrine-paginator

Composer 安装命令:

composer require mention/fast-doctrine-paginator

包简介

Fast Doctrine paginator for batching, infinite scrolling, Relay

README 文档

README

This package provides a fast, no-offset, Doctrine paginator that's suitable for infinite scrolling, batch jobs, and GraphQL/Relay pagination.

Build Status Latest Version MIT License PHPStan Enabled

Install

composer require mention/fast-doctrine-paginator

Why use this paginator ?

It is fast

What makes this paginator fast is that it relies on seek/keyset pagination rather than limit/offset. Learn more at https://use-the-index-luke.com/no-offset.

In addition to that, it lets the user write its own optimised queries.

It is suitable for batch jobs

Batch jobs over Doctrine queries typically have two problems:

  • The query returns too many results at once and exhausts the memory (even when using iterate(), because the query result is still buffered locally)
  • The entities accumulate in the UnitOfWork and exhaust the memory

Using this paginator solves these two problems:

  • By using a paginated query, we avoid fetching too many items at once
  • By giving the opportunity to act before and after each page, the paginator gives a safe point where the entity manager can be cleared, in order to keep the number of managed entities under control

It is suitable for GraphQL/Relay

GraphQL/Relay style pagination requires fine-grained cursors that point to the beginning and end of a page, as well as to each item in a page. This paginator provides that.

It is suitable for infinite scrolling

Every page will be as fast as the previous one.

It is type safe

The paginator is fully typed and supports phpstan-doctrine's static type inference of query results.

When not to use this paginator ?

This paginator is cursor-based, so it may not be suitable for you if you need to access a page by number (although this could be emulated).

Usage

Here is a typical example:

<?php

$query = $entityManager->createQuery('
    SELECT   u.id, u.name
    FROM     Acme\\Entity\\Users u
    WHERE    u.id > :idCursor
    ORDER    BY u.id ASC
');

// Number of items per page
$query->setMaxResults(3);

$paginator = DoctrinePaginatorBuilder::fromQuery($query)
    ->setDiscriminators([
        new PageDiscriminator('idCursor', 'getId'),
    ])
    ->build();

foreach ($paginator as $page) {
    foreach ($page() as $user) {
        // ...
    }
    $em->clear();
}

A user-defined query is used to fetch the results for one page of results, and will be executed multiple times when fetching multiple pages. Pagination stops when the query returns no results.

The number of elements per page is defined by calling setMaxResults() on the query itself.

Pagination is keyset-based, rather than limit/offset-based: Instead of asking for the Nth rows in a query, we ask for the rows that are upper than the highest one of the previous page. When comparing rows, we only take into account one or a few columns, that we call the discriminators. We use the value of the discriminator columns of the last row of one page to create an internal cursor that can be used to fetch the next page.

For this to work effectively and flawlessly, the following conditions must be true:

  • The column used for discrimination must be unique (if it's not the case, a combination of multiple columns must be used)
  • The query must be ordered by the discrimination columns
  • The query must have a WHERE clause that selects only rows whose discriminators are higher than the higher discriminators of the previous page.

About the query

The query must be a Doctrine Query object. It must have a defined number of max results (setMaxResults()), because this defines the number of items per page.

It must be ordered by the discriminator columns.

It must have a WHERE clause that selects only the rows whose discriminators are higher than the ones of the previous page. The paginator calls setParameter() on the query to set these values.

Examples

Examples with a Users table:

id name
1 Jackson
2 Sophia
3 Aiden
4 Olivia
5 Lucas
6 Ava

If we sort by id, we can use id as discriminator, because it's unique.

Note the ORDER clause, that orders by our discriminator.

Note the WHERE clause, that discriminates by our discriminator. We use the query parameter :idCursor. The value of this parameter is automatically set by the paginator. By default, it's set to 0 when requesting the first page, and then it's automatically updated to the value found in the last row of the latest fetched page.

<?php

$query = $entityManager->createQuery('
    SELECT   u.id, u.name
    FROM     Users u
    WHERE    u.id > :idCursor
    ORDER    BY u.id ASC
');

// Max results per page
$query->setMaxResults(3);

$paginator = DoctrinePaginatorBuilder::fromQuery($query)
    ->setDiscriminators([
        new PageDiscriminator('idCursor', 'getId'),
    ])
    ->build();

foreach ($paginator as $page) {
    foreach ($page() as $result) {
        // ...
    }
    $em->clear();
}

The first page will return this:

id name
1 Jackson
2 Sophia
3 Aiden

The paginator retains id=3 as cursor internally. Before requesting the next page, the paginator calls setParameter('idCursor', 3) on the query. As expected, the second page returns this:

id name
4 Olivia
5 Lucas
6 Ava

Sorting by name:

If we sort by name, we can not use it directly as discriminator, because it's not unique. If we sort by name and id, we can use name and id as discriminators, because they are unique together.

Notice how we use u.id as a fallback when the name equals to the current name cursor.

<?php

$query = $entityManager->createQuery('
    SELECT   u.id, u.name
    FROM     Users u
    WHERE    u.name > :nameCursor
    OR       (u.name = :nameCursor AND u.id > :idCursor)
    ORDER    BY u.name ASC, u.id ASC
');

$paginator = DoctrinePaginatorBuilder::fromQuery($query)
    ->setDiscriminators([
        new PageDiscriminator('nameCursor', 'getName'),
        new PageDiscriminator('idCursor', 'getId'),
    ])
    ->build();

Resuming pagination

We can resume pagination on a new DoctrinePaginator instance by setting the cursor explicitly. This is useful when paginating through multiple requests, for example.

The end cursor of a page can be retrieved by calling getCursor() on a PaginatorItem object. Using this cursor will fetch the next page.

A paginator that will resume at this position can be built by calling setCursor() on a DoctrinePaginatorBuilder.

Batch jobs

The paginator is particularly suitable for batching, because it allows to keep the memory usage under control:

  • The query returns a controlled amount of rows
  • It gives an opportunity to act before and after every page

For example, the entity manager can be safely cleared between two pages:

<?php

foreach ($paginator as $page) {
    foreach ($page() as $result) {
        // ...
    }
    $em->clear();
}

GraphQL/Relay

The paginator is particularly suitable for GraphQL/Relay pagination, since it provides cursors for the pages and items.

  • For pages: Use firstCursor() / lastCursor() on the PaginatorPageInterface.
  • For items: Use getCursor() on the PaginatorPageItemInterface.
<?php

foreach ($paginator as $page) {
    $startCursor = $page->firstCursor();
    $endCursor = $page->lastCursor();
    foreach ($page->items() as $item) {
        $itemCursor = $item->getCursor();
    }
}

Authors

The Mention team and contributors

mention/fast-doctrine-paginator 适用场景与选型建议

mention/fast-doctrine-paginator 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 127.14k 次下载、GitHub Stars 达 18, 最近一次更新时间为 2019 年 12 月 07 日, 在 PHP 生态内属于活跃度较高的组件。

我们在过去多个企业项目中使用过 mention/fast-doctrine-paginator 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 mention/fast-doctrine-paginator 我们能提供哪些服务?
定制开发 / 二次开发

基于 mention/fast-doctrine-paginator 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 127.14k
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 18
  • 点击次数: 17
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 18
  • Watchers: 5
  • Forks: 1
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2019-12-07