Skip to content

Commit 1ef14ee

Browse files
author
Gregory Haddow
committed
fix: ensure resource id's are always encoded/decoded when appropriate within a cursor
test: test cursor id encoding/decoding
1 parent 06ee7ad commit 1ef14ee

File tree

6 files changed

+427
-60
lines changed

6 files changed

+427
-60
lines changed

src/Pagination/Cursor/CursorBuilder.php

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class CursorBuilder
1414
{
1515
private Builder|Relation $query;
1616

17-
private ?ID $id = null;
17+
private ID $id;
1818

1919
private string $keyName;
2020

@@ -26,6 +26,8 @@ class CursorBuilder
2626

2727
private bool $keySort = true;
2828

29+
private CursorParser $parser;
30+
2931
/**
3032
* CursorBuilder constructor.
3133
*
@@ -34,14 +36,16 @@ class CursorBuilder
3436
* @param string|null $key
3537
* the key column that the before/after cursors related to
3638
*/
37-
public function __construct($query, string $key = null)
39+
public function __construct($query, ID $id, string $key = null)
3840
{
3941
if (!$query instanceof Builder && !$query instanceof Relation) {
4042
throw new \InvalidArgumentException('Expecting an Eloquent query builder or relation.');
4143
}
4244

4345
$this->query = $query;
46+
$this->id = $id;
4447
$this->keyName = $key ?: $this->guessKey();
48+
$this->parser = new CursorParser(IdParser::make($this->id), $this->keyName);
4549
}
4650

4751
/**
@@ -58,15 +62,6 @@ public function withDefaultPerPage(?int $perPage): self
5862
return $this;
5963
}
6064

61-
/**
62-
* @return $this
63-
*/
64-
public function withIdField(?ID $id): self
65-
{
66-
$this->id = $id;
67-
68-
return $this;
69-
}
7065

7166
public function withKeySort(bool $keySort): self
7267
{
@@ -108,8 +103,8 @@ public function paginate(Cursor $cursor, array $columns = ['*']): CursorPaginato
108103
$this->applyKeySort();
109104

110105
$total = $this->getTotal();
111-
$laravelPaginator = $this->query->cursorPaginate($cursor->getLimit(), $columns, 'cursor', $this->convertCursor($cursor));
112-
$paginator = new CursorPaginator($laravelPaginator, $cursor, $total);
106+
$laravelPaginator = $this->query->cursorPaginate($cursor->getLimit(), $columns, 'cursor', $this->parser->decode($cursor));
107+
$paginator = new CursorPaginator($this->parser, $laravelPaginator, $cursor, $total);
113108

114109
return $paginator->withCurrentPath();
115110
}

src/Pagination/Cursor/CursorPaginator.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class CursorPaginator implements \IteratorAggregate, \Countable
1717
/**
1818
* CursorPaginator constructor.
1919
*/
20-
public function __construct(private readonly LaravelCursorPaginator $laravelPaginator, private readonly Cursor $cursor, private readonly int|null $total = null)
20+
public function __construct(private readonly CursorParser $parser, private readonly LaravelCursorPaginator $laravelPaginator, private readonly Cursor $cursor, private readonly int|null $total = null)
2121
{
2222
$this->items = Collection::make($this->laravelPaginator->items());
2323
}
@@ -34,7 +34,7 @@ public function firstItem(): ?string
3434
return null;
3535
}
3636

37-
return $this->laravelPaginator->getCursorForItem($this->items->first(), false)->encode();
37+
return $this->parser->encode($this->laravelPaginator->getCursorForItem($this->items->first(), false));
3838
}
3939

4040
public function lastItem(): ?string
@@ -43,7 +43,7 @@ public function lastItem(): ?string
4343
return null;
4444
}
4545

46-
return $this->laravelPaginator->getCursorForItem($this->items->last())->encode();
46+
return $this->parser->encode($this->laravelPaginator->getCursorForItem($this->items->last()));
4747
}
4848

4949
public function hasMorePages(): bool
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
namespace LaravelJsonApi\Eloquent\Pagination\Cursor;
4+
5+
use Illuminate\Pagination\Cursor as LaravelCursor;
6+
use LaravelJsonApi\Core\Schema\IdParser;
7+
8+
class CursorParser
9+
{
10+
public function __construct(private IdParser $idParser, private string $keyName)
11+
{
12+
13+
}
14+
15+
public function encode(LaravelCursor $cursor): string
16+
{
17+
$key = $cursor->parameter($this->keyName);
18+
if (!$key) {
19+
return $cursor->encode();
20+
}
21+
22+
$encodedId = $this->idParser->encode($key);
23+
$parameters = $cursor->toArray();
24+
unset($parameters['_pointsToNextItems']);
25+
$parameters[$this->keyName] = $encodedId;
26+
27+
$newCursor = new LaravelCursor($parameters, $cursor->pointsToNextItems());
28+
return $newCursor->encode();
29+
}
30+
31+
public function decode(Cursor $cursor): ?LaravelCursor
32+
{
33+
$encodedCursor = $cursor->isBefore() ? $cursor->getBefore() : $cursor->getAfter();
34+
if (!is_string($encodedCursor)) {
35+
return null;
36+
}
37+
38+
$parameters = json_decode(base64_decode(str_replace(['-', '_'], ['+', '/'], $encodedCursor)), true);
39+
40+
if (json_last_error() !== JSON_ERROR_NONE) {
41+
return null;
42+
}
43+
44+
$pointsToNextItems = $parameters['_pointsToNextItems'];
45+
unset($parameters['_pointsToNextItems']);
46+
if (isset($parameters[$this->keyName])) {
47+
$parameters[$this->keyName] = $this->idParser->decode(
48+
$parameters[$this->keyName],
49+
);
50+
}
51+
52+
return new LaravelCursor($parameters, $pointsToNextItems);
53+
}
54+
}

src/Pagination/CursorPagination.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,6 @@ public function paginate($query, array $page): Page
206206

207207
$paginator = $this
208208
->query($query)
209-
->withIdField($this->id)
210209
->withDirection($this->direction)
211210
->withKeySort($this->keySort)
212211
->withDefaultPerPage($this->defaultPerPage)
@@ -227,7 +226,7 @@ public function paginate($query, array $page): Page
227226
*/
228227
private function query(Builder|Relation $query): CursorBuilder
229228
{
230-
return new CursorBuilder($query, $this->primaryKey);
229+
return new CursorBuilder($query, $this->id, $this->primaryKey);
231230
}
232231

233232
/**

0 commit comments

Comments
 (0)