juampi92 / cursor-pagination
为 Laravel 简化游标分页
Requires
- php: >=7.1.0
- illuminate/database: ^5.5|^6|^7|^8
- illuminate/http: ^5.5|^6|^7|^8
- illuminate/support: ^5.5|^6|^7|^8
Requires (Dev)
- orchestra/testbench: ~3.5|~4.2
- phpunit/phpunit: ~6.0|~7.0|~8.0
README
⚠️ 游标分页已归档。 这是因为 游标分页 已集成到 Laravel 中,因此不再需要。我们建议您使用原生包,或者如果您必须使用,可以分支此包。
此包提供基于游标的分页,已与 Laravel 的 查询构建器 和 Eloquent ORM 集成。它通过检查请求的 GET 参数自动计算 SQL 查询限制,并自动为您构建下一页和上一页的 URL。
安装
您可以通过 composer 使用以下命令安装此包:
composer require juampi92/cursor-pagination
此包将自动注册自己。
配置
要发布配置文件到 config/cursor_pagination.php
,请运行:
php artisan vendor:publish --provider="Juampi92\CursorPagination\CursorPaginationServiceProvider" --tag="config"
这将发布以下 文件。您可以根据需要自定义 GET 参数的名称,以及每页默认的项目数。
如何工作
游标分页背后的主要思想是它需要一个上下文来知道要显示哪些结果。因此,您可以说 next_cursor=10
而不是 page=2
。结果与传统的分页相同,但现在您对输出的控制更多,因为 next_cursor=10
应该始终返回相同的结果(除非某些记录被删除)。
这种分页方式在按最新条目排序时很有用,您可以始终使用 next_cursor
在到达底部时进行无限滚动,或者当您需要刷新顶部列表时,进行 previous_cursor
。如果您发送第一个游标,则结果将仅获取新条目。
优点
- 新行不会影响结果,因此在分页时不会出现重复结果。
- 使用索引游标进行过滤比使用数据库偏移量快得多。
- 使用上一个游标可以避免重复第一个元素。
缺点
- 没有上一页,尽管浏览器中仍然有。
- 无法跳转到任意页面(您必须知道上一个结果才能知道下一个结果)。
基本上,它非常适合无限滚动!
基本用法
分页查询构建器结果
有几种方法可以分页显示项目。最简单的方法是使用查询构建器或Eloquent查询的cursorPaginate
方法。该方法会自动处理设置合适的限制条件,并根据用户查看的游标获取下一页或上一页的元素。默认情况下,游标是通过HTTP请求中页面查询字符串参数的值来检测的。这个值会被包自动检测,并考虑到自定义配置,同时也会自动插入分页器生成的链接和元数据。
public function index() { $users = DB::table('users')->cursorPaginate(); return $users; }
分页Eloquent结果
您还可以分页Eloquent查询。在这个例子中,我们将每页显示15个项目的User
模型进行分页。如您所见,语法与分页查询构建器结果相同
$users = User::cursorPaginate(15);
当然,您可以在设置其他查询约束之后调用分页,例如where子句
$users = User::where('votes', '>', 100)->cursorPaginate(15);
或者对结果进行排序
$users = User::orderBy('id', 'desc')->cursorPaginate(15);
不用担心,包会检测模型的主键是否用于排序,并且会根据需要自动调整,以确保下一页和上一页能按预期工作。
标识符
分页器标识符基本上是游标属性。用于分页的模型属性。这个标识符在结果中必须是唯一的。重复的标识符可能会导致某些记录未显示,所以请务必小心。
如果查询没有按标识符排序,游标分页可能不会按预期工作。
自动检测标识符
如果没有定义标识符,游标会尝试自行找出。首先,它会检查是否有orderBy子句。如果有,它会选择第一个排序的列并使用它。如果没有orderBy子句,它会检查是否是Eloquent模型,并使用其primaryKey
(默认为'id')。如果不是Eloquent模型,它将使用'id
'。
示例
// Will use Booking's primaryKey Bookings::cursorPaginate(10);
// Will use hardcoded 'id' DB::table('bookings')->cursorPaginate(10);
// Will use 'created_by' Bookings::orderBy('created_by', 'asc') ->cursorPaginate(10);
自定义标识符
只需定义identifier
选项
// Will use _id, ignoring everything else. Bookings::cursorPaginate(10, ['*'], [ 'identifier' => '_id' ]);
日期游标
默认情况下,标识符是模型的primaryKey(如果不是Eloquent模型则使用'id
'),因此没有重复项。但您可以通过传递自定义的'identifier
'选项来调整此设置。如果指定的标识符在Eloquent的'$casts
'属性中被转换为日期或datetime,分页器会将其转换为unix时间戳
。
您也可以通过添加'[ 'date_identifier' => true ]
'选项来手动指定。
示例
使用Eloquent(确保Booking模型有'protected $casts = ['datetime' => 'datetime'];
')
// It will autodetect 'datetime' as identifier, // and will detect it's casted to datetime. Bookings::orderBy('datetime', 'asc') ->cursorPaginate(10);
使用查询
// It will autodetect 'datetime' as identifier, // but since there is no model, you'll have to // specify the 'date_identifier' option to `true` DB::table('bookings') ->orderBy('datetime', 'asc') ->cursorPaginate(10, ['*'], [ 'date_identifier' => true ]);
继承Laravel分页
您应该知道CursorPaginator继承自Laravel的AbstractPaginator,所以方法如withPath
、appends
、fragment
都是可用的,因此您可以使用这些方法构建URL,但您应该知道此包的默认URL创建已经包含了您可能已经有的查询参数。
显示分页结果
转换为JSON
基本返回将分页器转换为JSON,结果如下
Route::get('api/v1', function () { return App\User::cursorPaginate(); });
调用 api/v1
将输出
{ "path": "api/v1?", "previous_cursor": "10", "next_cursor": "3", "per_page": 3, "next_page_url": "api/v1?next_cursor=3", "prev_page_url": "api/v1?previous_cursor=1", "data": [ {} ] }
使用资源集合
默认情况下,当Laravel的API资源用作集合时,它们会将分页器的元数据输出到 links
和 meta
。
{ "data":[ {} ], "links": { "first": null, "last": null, "prev": "api/v1?previous_cursor=1", "next": "api/v1?next_cursor=3" }, "meta": { "path": "api/v1?", "previous_cursor": "1", "next_cursor": "3", "per_page": 3 } }
理解前一个光标
需要明确的是,前一个光标的工作方式与下一个光标不同。下一个光标使分页器返回光标之后的所有元素。
所以,如果下一个光标是 10
,那么该分页应该返回 next + 1
、next + 2
、next + 3
。所以这符合预期。
然而,前一个光标不是 prev - 1
、prev - 2
、prev - 3
。它不会返回相邻元素,或该光标的'上下文'。它所做的另一件事非常有趣
假设我们进行一个简单的获取,并返回元素 [10, 9, 8]
。如果我们执行 prev=10
,它将返回空,因为这些都是第一个元素。所以,如果我们现在再添加5个,从15到11,然后再执行 prev=10
,结果将是 [15, 14, 13]
。 而不是 [13, 12, 11]
。
这是因为前一个是像过滤器一样工作的。它显示光标之前的前'每页'项。顺序与下一个光标相同,因此它获取最新的项。
同样的逻辑可以在组合光标时使用: next=13&prev=10
将输出缺少的两个元素: [12, 11]
,这样您就可以在不获取任何额外项目的情况下完成列表。
自定义每页结果
使用此插件,您可以通过多种方式指定每页数量。
您可以将 cursor_pagination.per_page
配置设置为,如果您不发送任何参数,它将使用该值作为默认值。
您还可以使用一个数组。声明数组的目的是在它是前一个光标还是下一个/默认光标时进行分隔。这样,当您执行 '刷新' 时,您可以获取比简单地滚动更多的结果。
要配置它,请设置 $perPage = [15, 5]
。这样,当您执行 previous_cursor
时,它将获取15个,当您执行常规获取或 next_cursor
时,它将获取5个。
自定义参数名称
在配置中,将 identifier_name
改变为更改单词 cursor
。例如: cursor
、id
、pointer
等。
将 navigation_names
改变为更改单词 ['previous', 'next']
。例如: ['before', 'after']
、['min', 'max']
转换结果
编辑 transform_name
以不同的方式格式化字符串
- 对于
previousCursor
使用 'camel_case'。 - 对于
previous-cursor
使用 'kebab_case'。 - 对于
previous_cursor
使用 'snake_case'。
使用 null
默认为 camelCase。
API 文档
分页器自定义选项
new CursorPaginator(array|collection $items, array|int $perPage, array options = [ // Attribute used for choosing the cursor. Used primaryKey on Eloquent Models as default. 'identifier' => 'id', 'identifier_alias' => 'id', 'date_identifier' => false, 'path' => request()->path(), ]);
(项目必须有一个 $item->{$identifier} 属性。)
Eloquent 构建器和查询构建器的宏
cursorPaginate(array|int $perPage, array $cols = ['*'], array options = []): CursorPaginator;
分页器方法
$resutls->hasMorePages(): bool; $results->nextCursor(): string|null; $results->prevCursor(): string|null; $results->previousPageUrl(): string|null; $results->nextPageUrl(): string|null; $results->url(['next' => 1]): string; $results->count(): int;
注意:所有游标都被转换为字符串。
标识符别名
有时我们需要在JOIN查询中使用分页器。这可能会导致列重复,因此我们必须指定一个用于排序的列。由于MySQL不允许我们在WHERE条件中使用选择器别名,我们必须使用table
.column
名称。
$following = $user->following() ->orderBy('follows.created_at', 'desc') ->cursorPaginate(10, ['*'], [ 'date_identifier' => true, 'identifier' => 'follows.created_at', 'identifier_alias' => 'created_at', ]);
默认情况下,该包会猜测identifier_alias
是您指定的列名,因此如果您按follows.created_at
排序,它将尝试使用模型的created_at
作为标识符(通过使用别名)。
测试
使用以下命令运行测试
vendor/bin/phpunit
致谢
许可证
MIT许可证(MIT)。请参阅许可证文件获取更多信息。