Skip to content

Commit c8b46c3

Browse files
committed
Merge tag 'v2.1.0' into develop
Support encoded ids.
2 parents d6e0fc9 + dbb98d4 commit c8b46c3

File tree

7 files changed

+328
-15
lines changed

7 files changed

+328
-15
lines changed

Diff for: CHANGELOG.md

+14
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,20 @@ All notable changes to this project will be documented in this file. This projec
99

1010
- Upgrade to Laravel 10 and set minimum PHP version to 8.1.
1111

12+
## [2.1.0] - 2023-01-24
13+
14+
### Added
15+
16+
- The cursor pagination implementation now supports id fields that are encoded. To use, pass your schema's id
17+
field to the cursor pagination class when returning it from the schema's `pagination()` method. For example:
18+
19+
```php
20+
public function pagination(): CursorPagination
21+
{
22+
return CursorPagination::make($this->id());
23+
}
24+
```
25+
1226
## [2.0.0] - 2022-02-09
1327

1428
### Added

Diff for: src/Cursor/Cursor.php

-1
Original file line numberDiff line numberDiff line change
@@ -113,5 +113,4 @@ public function getLimit(): ?int
113113
{
114114
return $this->limit;
115115
}
116-
117116
}

Diff for: src/Cursor/CursorBuilder.php

+49-4
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,11 @@
2424
use Illuminate\Database\Eloquent\Relations\Relation;
2525
use Illuminate\Database\Query\Builder as QueryBuilder;
2626
use InvalidArgumentException;
27+
use LaravelJsonApi\Contracts\Schema\ID;
28+
use LaravelJsonApi\Core\Schema\IdParser;
2729
use LogicException;
2830
use OutOfRangeException;
31+
use RuntimeException;
2932
use function in_array;
3033

3134
class CursorBuilder
@@ -36,6 +39,11 @@ class CursorBuilder
3639
*/
3740
private $query;
3841

42+
/**
43+
* @var ID|null
44+
*/
45+
private ?ID $id = null;
46+
3947
/**
4048
* @var string
4149
*/
@@ -81,6 +89,17 @@ public function __construct($query, string $column = null, string $key = null)
8189
$this->direction = 'desc';
8290
}
8391

92+
/**
93+
* @param ID|null $id
94+
* @return $this
95+
*/
96+
public function withIdField(?ID $id): self
97+
{
98+
$this->id = $id;
99+
100+
return $this;
101+
}
102+
84103
/**
85104
* Set the query direction.
86105
*
@@ -178,7 +197,8 @@ private function getQualifiedKeyName(): string
178197
private function next(Cursor $cursor, $columns): CursorPaginator
179198
{
180199
if ($cursor->isAfter()) {
181-
$this->whereId($cursor->getAfter(), $this->isDescending() ? '<' : '>');
200+
$afterId = $this->decodeId($cursor->getAfter());
201+
$this->whereId($afterId, $this->isDescending() ? '<' : '>');
182202
}
183203

184204
$items = $this
@@ -187,12 +207,15 @@ private function next(Cursor $cursor, $columns): CursorPaginator
187207

188208
$more = $items->count() > $cursor->getLimit();
189209

190-
return new CursorPaginator(
210+
$paginator = new CursorPaginator(
191211
$items->slice(0, $cursor->getLimit()),
192212
$more,
193213
$cursor,
194214
$this->getUnqualifiedKey()
195215
);
216+
$paginator->withIdField($this->id);
217+
218+
return $paginator;
196219
}
197220

198221
/**
@@ -212,14 +235,36 @@ private function next(Cursor $cursor, $columns): CursorPaginator
212235
*/
213236
private function previous(Cursor $cursor, $columns): CursorPaginator
214237
{
238+
$beforeId = $this->decodeId($cursor->getBefore());
239+
215240
$items = $this
216-
->whereId($cursor->getBefore(), $this->isDescending() ? '>' : '<')
241+
->whereId($beforeId, $this->isDescending() ? '>' : '<')
217242
->orderForPrevious()
218243
->get($cursor->getLimit(), $columns)
219244
->reverse()
220245
->values();
221246

222-
return new CursorPaginator($items, true, $cursor, $this->getUnqualifiedKey());
247+
$paginator = new CursorPaginator($items, true, $cursor, $this->getUnqualifiedKey());
248+
$paginator->withIdField($this->id);
249+
250+
return $paginator;
251+
}
252+
253+
/**
254+
* @param int|string $id
255+
* @return int|string
256+
*/
257+
private function decodeId($id)
258+
{
259+
$decoded = IdParser::make($this->id)->decodeIfMatch($id);
260+
261+
if (null === $decoded) {
262+
throw new RuntimeException(
263+
"Cursor key {$id} did not decode. Use validation to ensure the key matches the expected pattern."
264+
);
265+
}
266+
267+
return $decoded;
223268
}
224269

225270
/**

Diff for: src/Cursor/CursorPaginator.php

+24-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
2424
use Illuminate\Pagination\Paginator;
2525
use IteratorAggregate;
26+
use LaravelJsonApi\Contracts\Schema\ID;
27+
use LaravelJsonApi\Core\Schema\IdParser;
2628
use LogicException;
2729
use Traversable;
2830

@@ -54,6 +56,11 @@ class CursorPaginator implements IteratorAggregate, Countable
5456
*/
5557
private ?string $path;
5658

59+
/**
60+
* @var ID|null
61+
*/
62+
private ?ID $id = null;
63+
5764
/**
5865
* CursorPaginator constructor.
5966
*
@@ -72,6 +79,17 @@ public function __construct(EloquentCollection $items, bool $more, Cursor $curso
7279
$this->key = $key;
7380
}
7481

82+
/**
83+
* @param ID|null $id
84+
* @return $this
85+
*/
86+
public function withIdField(?ID $id): self
87+
{
88+
$this->id = $id;
89+
90+
return $this;
91+
}
92+
7593
/**
7694
* @return EloquentCollection
7795
*/
@@ -86,7 +104,9 @@ public function getItems(): EloquentCollection
86104
public function firstItem()
87105
{
88106
if ($first = $this->items->first()) {
89-
return $first->{$this->key};
107+
return IdParser::make($this->id)->encode(
108+
$first->{$this->key},
109+
);
90110
}
91111

92112
return null;
@@ -98,7 +118,9 @@ public function firstItem()
98118
public function lastItem()
99119
{
100120
if ($last = $this->items->last()) {
101-
return $last->{$this->key};
121+
return IdParser::make($this->id)->encode(
122+
$last->{$this->key},
123+
);
102124
}
103125

104126
return null;

Diff for: src/CursorPagination.php

+15-5
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,21 @@
2121

2222
use InvalidArgumentException;
2323
use LaravelJsonApi\Contracts\Pagination\Page;
24+
use LaravelJsonApi\Contracts\Schema\ID;
2425
use LaravelJsonApi\Core\Pagination\Concerns\HasPageMeta;
2526
use LaravelJsonApi\CursorPagination\Cursor\Cursor;
2627
use LaravelJsonApi\CursorPagination\Cursor\CursorBuilder;
2728
use LaravelJsonApi\Eloquent\Contracts\Paginator;
2829

2930
class CursorPagination implements Paginator
3031
{
31-
3232
use HasPageMeta;
3333

34+
/**
35+
* @var ID|null
36+
*/
37+
private ?ID $id;
38+
3439
/**
3540
* @var string
3641
*/
@@ -74,18 +79,22 @@ class CursorPagination implements Paginator
7479
/**
7580
* Fluent constructor.
7681
*
82+
* @param ID|null $id
7783
* @return CursorPagination
7884
*/
79-
public static function make(): self
85+
public static function make(ID $id = null): self
8086
{
81-
return new static();
87+
return new static($id);
8288
}
8389

8490
/**
85-
* CursorStrategy constructor.
91+
* CursorPagination constructor.
92+
*
93+
* @param ID|null $id
8694
*/
87-
public function __construct()
95+
public function __construct(ID $id = null)
8896
{
97+
$this->id = $id;
8998
$this->before = 'before';
9099
$this->after = 'after';
91100
$this->limit = 'limit';
@@ -223,6 +232,7 @@ public function paginate($query, array $page): Page
223232
{
224233
$paginator = $this
225234
->query($query)
235+
->withIdField($this->id)
226236
->withDirection($this->direction)
227237
->withDefaultPerPage($this->defaultPerPage)
228238
->paginate($this->cursor($page), $this->columns ?: ['*']);

0 commit comments

Comments
 (0)