lampager/lampager
Composer 安装命令:
composer require lampager/lampager
包简介
Rapid pagination without using OFFSET
README 文档
README
Lampager Core
The core package of Lampager
Requirements
- PHP:
^8.2
Note
Older versions have outdated dependency requirements. If you cannot prepare the latest environment, please refer to past releases.
Installing
composer require lampager/lampager
Usage
Basically you don't need to directly use this package. For example, if you use Laravel, install lampager/lampager-laravel.
However, you can manually use like this:
use Lampager\Paginator; use Lampager\ArrayProcessor; $cursor = [ 'id' => 3, 'created_at' => '2017-01-10 00:00:00', 'updated_at' => '2017-01-20 00:00:00', ]; $query = (new Paginator()) ->forward() ->limit(5) ->orderByDesc('updated_at') // ORDER BY `updated_at` DESC, `created_at` DESC, `id` DESC ->orderByDesc('created_at') ->orderByDesc('id') ->seekable() ->configure($cursor); $rows = run_your_query_using_PDO($query); // Note: SQLite3 driver example is bundled in the tests/StubPaginator.php. Please refer to that. $result = (new ArrayProcessor())->process($query, $rows);
It will run the optimized query.
(
SELECT * FROM `posts`
WHERE `user_id` = 1
AND (
`updated_at` = '2017-01-20 00:00:00' AND `created_at` = '2017-01-10 00:00:00' AND `id` > 3
OR
`updated_at` = '2017-01-20 00:00:00' AND `created_at` > '2017-01-10 00:00:00'
OR
`updated_at` > '2017-01-20 00:00:00'
)
ORDER BY `updated_at` ASC, `created_at` ASC, `id` ASC
LIMIT 1
) UNION ALL (
SELECT * FROM `posts`
WHERE `user_id` = 1
AND (
`updated_at` = '2017-01-20 00:00:00' AND `created_at` = '2017-01-10 00:00:00' AND `id` <= 3
OR
`updated_at` = '2017-01-20 00:00:00' AND `created_at` < '2017-01-10 00:00:00'
OR
`updated_at` < '2017-01-20 00:00:00'
)
ORDER BY `updated_at` DESC, `created_at` DESC, `id` DESC
LIMIT 6
)
And you'll get
object(Lampager\PaginationResult)#1 (5) { ["records"]=> array(5) { [0]=> array(5) { ["id"]=> int(3) ["user_id"]=> int(1) ["text"]=> string(3) "foo" ["created_at"]=> string(19) "2017-01-10 00:00:00" ["updated_at"]=> string(19) "2017-01-20 00:00:00" } [1]=> array(5) { ["id"]=> int(5) ["user_id"]=> int(1) ["text"]=> string(3) "bar" ["created_at"]=> string(19) "2017-01-05 00:00:00" ["updated_at"]=> string(19) "2017-01-20 00:00:00" } [2]=> array(5) { ["id"]=> int(4) ["user_id"]=> int(1) ["text"]=> string(3) "baz" ["created_at"]=> string(19) "2017-01-05 00:00:00" ["updated_at"]=> string(19) "2017-01-20 00:00:00" } [3]=> array(5) { ["id"]=> int(2) ["user_id"]=> int(1) ["text"]=> string(3) "qux" ["created_at"]=> string(19) "2017-01-17 00:00:00" ["updated_at"]=> string(19) "2017-01-18 00:00:00" } [4]=> array(5) { ["id"]=> int(1) ["user_id"]=> int(1) ["text"]=> string(3) "quux" ["created_at"]=> string(19) "2017-01-16 00:00:00" ["updated_at"]=> string(19) "2017-01-18 00:00:00" } } ["hasPrevious"]=> bool(false) ["previousCursor"]=> NULL ["hasNext"]=> bool(true) ["nextCursor"]=> array(2) { ["updated_at"]=> string(19) "2017-01-18 00:00:00" ["created_at"]=> string(19) "2017-01-14 00:00:00" ["id"]=> int(6) } }
Question: How about Tuple Comparison?
With this feature, SQL statements should be simpler. However, according to SQL Feature Comparison, some RDBMS, such as SQLServer, do not support this syntax. Therefore, Lampager continuously uses redundant statements.
It is also useful for Doctrine 2 since its DQL lexer does not support the syntax and triggers parse errors.
Classes
| Name | Type | Parent Class Implemented Interface |
Description |
|---|---|---|---|
Lampager\Paginator |
Class | Fluent factory for building Query | |
Lampager\AbstractProcessor |
Abstract Class | Receive fetched records and format them | |
Lampager\PaginationResult |
Class | Processor wraps result with this by default | |
Lampager\ArrayProcessor |
Class | Lampager\AbstractProcessor |
Simple Processor implementation for pure PDO |
Lampager\ArrayCursor |
Class | Lampager\Contracts\Cursor |
Simple Cursor implementation for pure PDO Arrays are automatically wrapped with this |
Lampager\Query |
Class | SQL configuration container generated by Paginator | |
| Lampager\Query\... | Class | Child components of Query | |
Lampager\Contracts\Cursor |
Interface | Indicates parameters for retrieving previous/next records | |
Lampager\Contracts\Formatter |
Interface | Formatter interface pluggable to Processor | |
Lampager\Concerns\HasProcessor |
Trait | Helper for extended Paginator providing convenient accessibility to Processor |
API
Paginator::orderBy()
Paginator::orderByDesc()
Paginator::clearOrderBy()
Add or clear cursor parameter name for ORDER BY statement.
At least one parameter required.
Paginator::orderBy(string $column, string $direction = 'asc'): $this Paginator::orderByDesc(string $column): $this Paginator::clearOrderBy(): $this
IMPORTANT: The last key MUST be the primary key.
e.g. $paginator->orderBy('updated_at')->orderBy('id')
Arguments
(string)$column
Table column name.(string)$direction
"asc"or"desc".
Paginator::limit()
Define the pagination limit.
Paginator::limit(int $limit): $this
Arguments
(int)$limit
Positive integer.
Paginator::forward()
Paginator::backward()
Define the pagination direction.
Paginator::forward(bool $forward = true): $this Paginator::backward(bool $backward = true): $this
Forward (Default)
===============>
[2] [ 3, 4, 5, 6, 7] [8]
| | └ next cursor
| └ current cursor
└ previous cursor
===============>
[8] [ 7, 6, 5, 4, 3] [2]
| | └ next cursor
| └ current cursor
└ previous cursor
Backward
<===============
[2] [ 3, 4, 5, 6, 7] [8]
| | └ next cursor
| └ current cursor
└ previous cursor
<===============
[8] [ 7, 6, 5, 4, 3] [2]
| | └ next cursor
| └ current cursor
└ previous cursor
IMPORTANT: You need previous cursor to retrieve more results.
Paginator::inclusive()
Paginator::exclusive()
Paginator::inclusive(bool $inclusive = true): $this Paginator::exclusive(bool $exclusive = true): $this
Change the behavior of handling cursor.
Inclusive (Default)
Current cursor will be included in the current page.
===============>
[2] [ 3, 4, 5, 6, 7] [8]
| | └ next cursor
| └ current cursor
└ previous cursor
<===============
[2] [ 3, 4, 5, 6, 7] [8]
| | └ next cursor
| └ current cursor
└ previous cursor
Exclusive
Current cursor will not be included in the current page.
===============>
[2] [ 3, 4, 5, 6, 7] [8]
| └ next cursor
└ current cursor
<===============
[2] [ 3, 4, 5, 6, 7] [8]
| |
| └ current cursor
└ previous cursor
Paginator::unseekable()
Paginator::seekable()
Paginator::unseekable(bool $unseekable = true): $this Paginator::seekable(bool $seekable = true): $this
Define that the pagination result should contain both of the next cursor and the previous cursor.
unseekable()always requires one simpleSELECTquery. (Default)seekable()may requireSELECT ... UNION ALL SELECT ...query when the cursor parameters are not empty.
Unseekable (Default)
===============>
[?] [ 3, 4, 5, 6, 7] [8]
| └ next cursor
└ current cursor
Seekable
===============>
[2] [ 3, 4, 5, 6, 7] [8]
| | └ next cursor
| └ current cursor
└ previous cursor
Always when the current cursor parameters are empty
===============>
[ 1, 2, 3, 4, 5] [6]
└ next cursor
Paginator::fromArray()
Define options from an associative array.
Paginator::fromArray(array $options): $this
Arguments
(array)$options
Associative array that contains the following keys.(int)limit(bool)backward / forward(bool)exclusive / inclusive(bool)seekable / unseekable(string[][])$orders
e.g.
[
'limit' => 30,
'backward' => true,
'unseekable' => false,
'orders' => [
['created_at', 'asc'],
['id', 'asc'],
],
]
Paginator::configure()
Generate Query corresponding to the current cursor.
Paginator::configure(Cursor|array $cursor = []): Query
Arguments
(mixed)$cursor
An associative array that contains$column => $valueor an object that implements\Lampager\Contracts\Cursor. It must be all-or-nothing.- For the initial page, omit this parameter or pass an empty array.
- For subsequent pages, pass all parameters. Partial parameters are not allowed.
AbstractProcessor::process()
Receive a pair of Query and fetched rows to analyze and format them.
AbstractProcessor::process(Query $query, mixed $rows): mixed
Arguments
(Query)$query(mixed)$rows
Fetched records from database. Typically it should be an array or a Traversable.
Return Value
(mixed)
By default, an instance of \Lampager\PaginationResult is returned. All fields are public.
e.g.
object(Lampager\PaginationResult)#1 (5) { ["records"]=> array(5) { /* ... */ } ["hasPrevious"]=> bool(false) ["previousCursor"]=> NULL ["hasNext"]=> bool(true) ["nextCursor"]=> array(2) { ["updated_at"]=> string(19) "2017-01-18 00:00:00" ["created_at"]=> string(19) "2017-01-14 00:00:00" ["id"]=> int(6) } }
Note that
hasPrevious/hasNextwill befalsewhen there are no more results for the corresponding direction.- Either
hasPrevious/hasNextwill benullwhen$cursoris empty orseekable()is not be enabled.
PaginationResult::getIterator()
It can be directly traversed using foreach thanks to the interface \IteratorAggregate.
AbstractProcessor::getIterator(): \ArrayIterator
Return Value
(mixed)
ArrayIterator instance that wraps records.
AbstractProcessor::useFormatter()
AbstractProcessor::restoreFormatter()
Override or restore the formatter for the pagination result.
AbstractProcessor::useFormatter(Formatter|callable $formatter): $this AbstractProcessor::restoreFormatter(): $this
Callable Formatter Example
<?php use Lampager\Query; use Lampager\ArrayProcessor; use Lampager\PaginationResult; $formatter = function ($rows, array $meta, Query $query) { // Drop table prefix in meta properties (e.g. "posts.updated_at" -> "updated_at") foreach (array_filter($meta, 'is_array') as $property => $cursor) { foreach ($cursor as $column => $field) { unset($meta[$property][$column]); $segments = explode('.', $column); $meta[$property][end($segments)] = $field; } } return new PaginationResult($rows, $meta); }; $result = (new ArrayProcessor())->useFormatter($formatter)->process($query, $rows);
Class Formatter Example
<?php use Lampager\Query; use Lampager\ArrayProcessor; use Lampager\PaginationResult; use Lampager\Contracts\Formatter; class DropTablePrefix implements Formatter { public function format($rows, array $meta, Query $query) { // Drop table prefix in meta properties (e.g. "posts.updated_at" -> "updated_at") foreach (array_filter($meta, 'is_array') as $property => $cursor) { foreach ($cursor as $column => $field) { unset($meta[$property][$column]); $segments = explode('.', $column); $meta[$property][end($segments)] = $field; } } return new PaginationResult($rows, $meta); } } $result = (new ArrayProcessor())->useFormatter(DropTablePrefix::class)->process($query, $rows);
AbstractProcessor::setDefaultFormatter()
AbstractProcessor::restoreDefaultFormatter()
Globally override or restore the formatter.
static AbstractProcessor::setDefaultFormatter(Formatter|callable $formatter): void static AbstractProcessor::restoreDefaultFormatter(): void
Example (Laravel)
<?php use Illuminate\Database\Eloquent\Builder; use Lampager\Query; use Lampager\Laravel\Processor as IlluminateProcessor; IlluminateProcessor::setDefaultFormatter(function ($rows, array $meta, Query $query) { // Note: // $builder is provided from extended Paginator. // For example, lampager/lampager-laravel provides QueryBuilder, EloquentBuilder or Relation. $builder = $query->builder(); switch ($builder instanceof Builder ? $builder->getModel() : null) { case Post::class: return (new PostFormatter())->format($rows, $meta, $query); case Comment::class: return (new CommentFormatter())->format($rows, $meta, $query); default: return new PaginationResult($rows, $meta); } }); $posts = Post::lampager()->orderBy('created_at')->orderBy('id')->paginate(); $comments = Comment::lampager()->orderBy('created_at')->orderBy('id')->paginate();
lampager/lampager 适用场景与选型建议
lampager/lampager 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 80.22k 次下载、GitHub Stars 达 37, 最近一次更新时间为 2017 年 10 月 20 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「paginator」 「pagination」 「limit」 「offset」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 lampager/lampager 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 lampager/lampager 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 lampager/lampager 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
Limit number of records that can be added to a SilverStripe GridField
PHP PDO Mysql select statement iterator implemented as multiple queries using LIMIT clauses
Magento 2 Discount Amount Limiter
Symfony DataGridBundle
A Laravel Pulse card to track users & URL's that are being throttled by your Rate Limiters
MongoDB Adapter to be used with Laminas Paginator
统计信息
- 总下载量: 80.22k
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 37
- 点击次数: 8
- 依赖项目数: 5
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2017-10-20
