Skip to content

Commit c5f2200

Browse files
Document cursor pagination and its advantages/limitations
1 parent 01a0298 commit c5f2200

File tree

1 file changed

+102
-7
lines changed

1 file changed

+102
-7
lines changed

pagination.md

Lines changed: 102 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,18 @@
33
- [Introduction](#introduction)
44
- [Basic Usage](#basic-usage)
55
- [Paginating Query Builder Results](#paginating-query-builder-results)
6+
- [Cursor vs Offset Pagination](#cursor-vs-offset-pagination)
67
- [Paginating Eloquent Results](#paginating-eloquent-results)
8+
- [The Cursor Instance](#the-cursor-instance)
79
- [Manually Creating A Paginator](#manually-creating-a-paginator)
810
- [Customizing Pagination URLs](#customizing-pagination-urls)
911
- [Displaying Pagination Results](#displaying-pagination-results)
1012
- [Adjusting The Pagination Link Window](#adjusting-the-pagination-link-window)
1113
- [Converting Results To JSON](#converting-results-to-json)
1214
- [Customizing The Pagination View](#customizing-the-pagination-view)
1315
- [Using Bootstrap](#using-bootstrap)
14-
- [Paginator Instance Methods](#paginator-instance-methods)
16+
- [Paginator and LengthAwarePaginator Instance Methods](#paginator-instance-methods)
17+
- [Cursor Paginator Instance Methods](#cursor-paginator-instance-methods)
1518

1619
<a name="introduction"></a>
1720
## Introduction
@@ -53,14 +56,48 @@ In this example, the only argument passed to the `paginate` method is the number
5356
}
5457

5558
<a name="simple-pagination"></a>
56-
#### "Simple Pagination"
59+
#### Simple Pagination
5760

5861
The `paginate` method counts the total number of records matched by the query before retrieving the records from the database. This is done so that the paginator knows how many pages of records there are in total. However, if you do not plan to show the total number of pages in your application's UI then the record count query is unnecessary.
5962

6063
Therefore, if you only need to display simple "Next" and "Previous" links in your application's UI, you may use the `simplePaginate` method to perform a single, efficient query:
6164

6265
$users = DB::table('users')->simplePaginate(15);
6366

67+
<a name="cursor-pagination"></a>
68+
#### Cursor Pagination
69+
70+
While `paginate` and `simplePaginate` create queries using `offset`, cursor pagination works by constructing `where` clauses that compare the values of the ordered columns. This can be really helpful for large data-sets, infinite scrolling and APIs.
71+
72+
Similar to `simplePaginate`, `cursorPaginate` displays "Next" and "Previous" links in your application's UI. You may use the `cursorPaginate` method like so:
73+
74+
$users = DB::table('users')->orderBy('id')->cursorPaginate(15);
75+
76+
An order by clause is required for cursor pagination to work for the Database Query Builder.
77+
78+
<a name="cursor-vs-offset-pagination"></a>
79+
### Cursor vs Offset Pagination
80+
81+
To illustrate the difference between offset pagination and cursor pagination, mentioned below are the SQL queries constructed by both to view the "second page" of a `users` table ordered by `id`:
82+
83+
```sql
84+
# Offset Pagination
85+
select * from users order by id asc limit 15 offset 15;
86+
87+
# Cursor Pagination
88+
select * from users where id > 15 order by id asc limit 15;
89+
```
90+
91+
Cursor pagination offers the following advantages over offset pagination:
92+
1. For large data-sets, cursor pagination will perform better if the "order by" columns are indexed. This is because offset scans through all the previous data unlike comparison queries.
93+
2. For data-sets with frequent writes, offset pagination may skip records or show duplicates if results have been added to or deleted from the previous page.
94+
95+
Cursor pagination, however, has the following limitations:
96+
1. Like `simplePaginate`, it can only be used to display "Next" and "Previous" links and does not support page numbers.
97+
2. It requires that the ordering is based on at least one unique column (or a combination of columns that are unique).
98+
3. It requires that the "order by" directions (desc/asc) are the same if there are multiple "order by" clauses.
99+
4. Query expressions in "order by" clauses are supported only if they are aliased and added to the select clause as well.
100+
64101
<a name="paginating-eloquent-results"></a>
65102
### Paginating Eloquent Results
66103

@@ -78,14 +115,47 @@ You may also use the `simplePaginate` method when paginating Eloquent models:
78115

79116
$users = User::where('votes', '>', 100)->simplePaginate(15);
80117

118+
Similarly, you may use the `cursorPaginate` method to cursor paginate Eloquent models:
119+
120+
$users = User::where('votes', '>', 100)->cursorPaginate(15);
121+
122+
<a name="the-cursor-instance"></a>
123+
### The Cursor Instance
124+
125+
Offset pagination determines which items to show using the page number. Cursor pagination instead uses what is called a "cursor" to determine which items to display.
126+
127+
The cursor is an instance of `Illuminate\Pagination\Cursor`, which includes values that identify a specific record, from where to start paginating, along with the direction to paginate.
128+
129+
So, for instance, if we were paginating records from the `users` table in ascending order of the `id`, a cursor can be instantiated like so:
130+
131+
```php
132+
use Illuminate\Pagination\Cursor;
133+
134+
$cursor = new Cursor(['id' => 10], true);
135+
```
136+
137+
The first argument of the constructor identifies the record after which we need to start paginating and the second argument refers to the direction (`true` being forwards). The cursor above indicates that the pagination should start from an `id` greater than 10.
138+
139+
The `cursorPaginate` methods on the Eloquent and Database query builders will automatically instantiate the cursors for the next and previous pages, json encode them, and then base64 safe URL encode the values as a query parameter.
140+
141+
However, in case you need to manually supply a cursor, you may do so using the `cursorPaginate` method on the Eloquent or Database query builder:
142+
143+
```php
144+
use App\Models\User;
145+
use Illuminate\Pagination\Cursor;
146+
147+
$cursor = new Cursor(['id' => 10, true]);
148+
$users = User::cursorPaginate(15, ['*'], 'cursor', $cursor);
149+
```
150+
81151
<a name="manually-creating-a-paginator"></a>
82152
### Manually Creating A Paginator
83153

84-
Sometimes you may wish to create a pagination instance manually, passing it an array of items that you already have in memory. You may do so by creating either an `Illuminate\Pagination\Paginator` or `Illuminate\Pagination\LengthAwarePaginator` instance, depending on your needs.
154+
Sometimes you may wish to create a pagination instance manually, passing it an array of items that you already have in memory. You may do so by creating either an `Illuminate\Pagination\Paginator`, `Illuminate\Pagination\LengthAwarePaginator` or `Illuminate\Pagination\CursorPaginator` instance, depending on your needs.
85155

86-
The `Paginator` class does not need to know the total number of items in the result set; however, because of this, the class does not have methods for retrieving the index of the last page. The `LengthAwarePaginator` accepts almost the same arguments as the `Paginator`; however, it requires a count of the total number of items in the result set.
156+
The `Paginator` and `CursorPaginator` classes do not need to know the total number of items in the result set; however, because of this, these classes do not have methods for retrieving the index of the last page. The `LengthAwarePaginator` accepts almost the same arguments as the `Paginator`; however, it requires a count of the total number of items in the result set.
87157

88-
In other words, the `Paginator` corresponds to the `simplePaginate` method on the query builder, while the `LengthAwarePaginator` corresponds to the `paginate` method.
158+
In other words, the `Paginator` corresponds to the `simplePaginate` method on the query builder, the `CursorPaginator` corresponds to the `cursorPaginate` method and the `LengthAwarePaginator` corresponds to the `paginate` method.
89159

90160
> {note} When manually creating a paginator instance, you should manually "slice" the array of results you pass to the paginator. If you're unsure how to do this, check out the [array_slice](https://secure.php.net/manual/en/function.array-slice.php) PHP function.
91161
@@ -133,7 +203,9 @@ If you need to append a "hash fragment" to URLs generated by the paginator, you
133203
<a name="displaying-pagination-results"></a>
134204
## Displaying Pagination Results
135205

136-
When calling the `paginate` method, you will receive an instance of `Illuminate\Pagination\LengthAwarePaginator`. When calling the `simplePaginate` method, you will receive an instance of `Illuminate\Pagination\Paginator`. These objects provide several methods that describe the result set. In addition to these helpers methods, the paginator instances are iterators and may be looped as an array. So, once you have retrieved the results, you may display the results and render the page links using [Blade](/docs/{{version}}/blade):
206+
When calling the `paginate` method, you will receive an instance of `Illuminate\Pagination\LengthAwarePaginator`. When calling the `simplePaginate` method, you will receive an instance of `Illuminate\Pagination\Paginator`. When calling the `cursorPaginate` method, you will receive an instance of `Illuminate\Pagination\CursorPaginator`.
207+
208+
These objects provide several methods that describe the result set. In addition to these helpers methods, the paginator instances are iterators and may be looped as an array. So, once you have retrieved the results, you may display the results and render the page links using [Blade](/docs/{{version}}/blade):
137209

138210
```html
139211
<div class="container">
@@ -248,7 +320,7 @@ Laravel includes pagination views built using [Bootstrap CSS](https://getbootstr
248320
}
249321

250322
<a name="paginator-instance-methods"></a>
251-
## Paginator Instance Methods
323+
## Paginator and LengthAwarePaginator Instance Methods
252324

253325
Each paginator instance provides additional pagination information via the following methods:
254326

@@ -272,3 +344,26 @@ Method | Description
272344
`$paginator->url($page)` | Get the URL for a given page number.
273345
`$paginator->getPageName()` | Get the query string variable used to store the page.
274346
`$paginator->setPageName($name)` | Set the query string variable used to store the page.
347+
348+
<a name="cursor-paginator-instance-methods"></a>
349+
## Cursor Paginator Instance Methods
350+
351+
Each cursor paginator instance provides additional pagination information via the following methods:
352+
353+
Method | Description
354+
------- | -----------
355+
`$paginator->count()` | Get the number of items for the current page.
356+
`$paginator->cursor()` | Get the current cursor instance.
357+
`$paginator->getOptions()` | Get the paginator options.
358+
`$paginator->hasPages()` | Determine if there are enough items to split into multiple pages.
359+
`$paginator->hasMorePages()` | Determine if there are more items in the data store.
360+
`$paginator->getCursorName()` | Get the query string variable used to store the cursor.
361+
`$paginator->items()` | Get the items for the current page.
362+
`$paginator->nextCursor()` | Get the cursor instance for the next set of items.
363+
`$paginator->nextPageUrl()` | Get the URL for the next page.
364+
`$paginator->onFirstPage()` | Determine if the paginator is on the first page.
365+
`$paginator->perPage()` | The number of items to be shown per page.
366+
`$paginator->previousCursor()` | Get the cursor instance for the previous set of items.
367+
`$paginator->previousPageUrl()` | Get the URL for the previous page.
368+
`$paginator->setCursorName()` | Set the query string variable used to store the cursor.
369+
`$paginator->url($cursor)` | Get the URL for a given cursor instance.

0 commit comments

Comments
 (0)