Skip to content

Commit fcd5add

Browse files
committed
refactor: tidy up cursor pagination implementation
1 parent d46b030 commit fcd5add

File tree

8 files changed

+529
-311
lines changed

8 files changed

+529
-311
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. This projec
55

66
## Unreleased
77

8+
### Added
9+
10+
- [#37](https://github.com/laravel-json-api/eloquent/pull/37) Add Eloquent cursor pagination implementation.
11+
812
## [4.1.0] - 2024-06-26
913

1014
### Added

src/Pagination/Cursor/Cursor.php

Lines changed: 18 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,10 @@
11
<?php
22
/*
3-
* Copyright 2023 Cloud Creativity Limited
3+
* Copyright 2024 Cloud Creativity Limited
44
*
5-
* Licensed under the Apache License, Version 2.0 (the "License");
6-
* you may not use this file except in compliance with the License.
7-
* You may obtain a copy of the License at
8-
*
9-
* http://www.apache.org/licenses/LICENSE-2.0
10-
*
11-
* Unless required by applicable law or agreed to in writing, software
12-
* distributed under the License is distributed on an "AS IS" BASIS,
13-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14-
* See the License for the specific language governing permissions and
15-
* limitations under the License.
5+
* Use of this source code is governed by an MIT-style
6+
* license that can be found in the LICENSE file or at
7+
* https://opensource.org/licenses/MIT.
168
*/
179

1810
declare(strict_types=1);
@@ -21,40 +13,23 @@
2113

2214
use InvalidArgumentException;
2315

24-
class Cursor
16+
final readonly class Cursor
2517
{
26-
27-
/**
28-
* @var string|null
29-
*/
30-
private ?string $before;
31-
32-
/**
33-
* @var string|null
34-
*/
35-
private ?string $after;
36-
37-
/**
38-
* @var int|null
39-
*/
40-
private ?int $limit;
41-
42-
/**
18+
/**
4319
* Cursor constructor.
4420
*
4521
* @param string|null $before
4622
* @param string|null $after
4723
* @param int|null $limit
4824
*/
49-
public function __construct(string $before = null, string $after = null, int $limit = null)
50-
{
51-
if (is_int($limit) && 1 > $limit) {
25+
public function __construct(
26+
private ?string $before = null,
27+
private ?string $after = null,
28+
private ?int $limit = null
29+
) {
30+
if (is_int($this->limit) && 1 > $this->limit) {
5231
throw new InvalidArgumentException('Expecting a limit that is 1 or greater.');
5332
}
54-
55-
$this->before = $before ?: null;
56-
$this->after = $after ?: null;
57-
$this->limit = $limit;
5833
}
5934

6035
/**
@@ -97,10 +72,12 @@ public function getAfter(): ?string
9772
*/
9873
public function withDefaultLimit(int $limit): self
9974
{
100-
if (is_null($this->limit)) {
101-
$copy = clone $this;
102-
$copy->limit = $limit;
103-
return $copy;
75+
if ($this->limit === null) {
76+
return new self(
77+
before: $this->before,
78+
after: $this->after,
79+
limit: $limit,
80+
);
10481
}
10582

10683
return $this;
Lines changed: 61 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,66 @@
11
<?php
2+
/*
3+
* Copyright 2024 Cloud Creativity Limited
4+
*
5+
* Use of this source code is governed by an MIT-style
6+
* license that can be found in the LICENSE file or at
7+
* https://opensource.org/licenses/MIT.
8+
*/
29

310
declare(strict_types=1);
411

512
namespace LaravelJsonApi\Eloquent\Pagination\Cursor;
613

714
use Illuminate\Database\Eloquent\Builder;
815
use Illuminate\Database\Eloquent\Relations\Relation;
9-
use Illuminate\Pagination\Cursor as LaravelCursor;
1016
use LaravelJsonApi\Contracts\Schema\ID;
1117
use LaravelJsonApi\Core\Schema\IdParser;
1218

13-
class CursorBuilder
19+
final class CursorBuilder
1420
{
15-
private Builder|Relation $query;
16-
17-
private ID $id;
18-
19-
private string $keyName;
21+
/**
22+
* @var string
23+
*/
24+
private readonly string $keyName;
2025

26+
/**
27+
* @var string
28+
*/
2129
private string $direction;
2230

31+
/**
32+
* @var int|null
33+
*/
2334
private ?int $defaultPerPage = null;
2435

36+
/**
37+
* @var bool
38+
*/
2539
private bool $withTotal;
2640

41+
/**
42+
* @var bool
43+
*/
2744
private bool $keySort = true;
2845

29-
private CursorParser $parser;
46+
/**
47+
* @var CursorParser
48+
*/
49+
private readonly CursorParser $parser;
3050

3151
/**
3252
* CursorBuilder constructor.
3353
*
34-
* @param Builder|Relation $query
35-
* the column to use for the cursor
36-
* @param string|null $key
37-
* the key column that the before/after cursors related to
54+
* @param Builder|Relation $query the column to use for the cursor
55+
* @param ID $id
56+
* @param string|null $key the key column that the before/after cursors related to
3857
*/
39-
public function __construct($query, ID $id, string $key = null)
40-
{
41-
if (!$query instanceof Builder && !$query instanceof Relation) {
42-
throw new \InvalidArgumentException('Expecting an Eloquent query builder or relation.');
43-
}
44-
45-
$this->query = $query;
46-
$this->id = $id;
47-
$this->keyName = $key ?: $this->guessKey();
58+
public function __construct(
59+
private readonly Builder|Relation $query,
60+
private readonly ID $id,
61+
?string $key = null
62+
) {
63+
$this->keyName = $key ?: $this->id->key();
4864
$this->parser = new CursorParser(IdParser::make($this->id), $this->keyName);
4965
}
5066

@@ -62,8 +78,11 @@ public function withDefaultPerPage(?int $perPage): self
6278
return $this;
6379
}
6480

65-
66-
public function withKeySort(bool $keySort): self
81+
/**
82+
* @param bool $keySort
83+
* @return $this
84+
*/
85+
public function withKeySort(bool $keySort = true): self
6786
{
6887
$this->keySort = $keySort;
6988

@@ -86,6 +105,10 @@ public function withDirection(string $direction): self
86105
throw new \InvalidArgumentException('Unexpected query direction.');
87106
}
88107

108+
/**
109+
* @param bool $withTotal
110+
* @return $this
111+
*/
89112
public function withTotal(bool $withTotal): self
90113
{
91114
$this->withTotal = $withTotal;
@@ -103,12 +126,20 @@ public function paginate(Cursor $cursor, array $columns = ['*']): CursorPaginato
103126
$this->applyKeySort();
104127

105128
$total = $this->getTotal();
106-
$laravelPaginator = $this->query->cursorPaginate($cursor->getLimit(), $columns, 'cursor', $this->parser->decode($cursor));
129+
$laravelPaginator = $this->query->cursorPaginate(
130+
$cursor->getLimit(),
131+
$columns,
132+
'cursor',
133+
$this->parser->decode($cursor),
134+
);
107135
$paginator = new CursorPaginator($this->parser, $laravelPaginator, $cursor, $total);
108136

109137
return $paginator->withCurrentPath();
110138
}
111139

140+
/**
141+
* @return void
142+
*/
112143
private function applyKeySort(): void
113144
{
114145
if (!$this->keySort) {
@@ -125,35 +156,17 @@ private function applyKeySort(): void
125156
}
126157
}
127158

159+
/**
160+
* @return int|null
161+
*/
128162
private function getTotal(): ?int
129163
{
130164
return $this->withTotal ? $this->query->count() : null;
131165
}
132166

133-
private function convertCursor(Cursor $cursor): ?LaravelCursor
134-
{
135-
$encodedCursor = $cursor->isBefore() ? $cursor->getBefore() : $cursor->getAfter();
136-
if (!is_string($encodedCursor)) {
137-
return null;
138-
}
139-
140-
$parameters = json_decode(base64_decode(str_replace(['-', '_'], ['+', '/'], $encodedCursor)), true);
141-
142-
if (json_last_error() !== JSON_ERROR_NONE) {
143-
return null;
144-
}
145-
146-
$pointsToNextItems = $parameters['_pointsToNextItems'];
147-
unset($parameters['_pointsToNextItems']);
148-
if (isset($parameters[$this->keyName])) {
149-
$parameters[$this->keyName] = IdParser::make($this->id)->decode(
150-
(string) $parameters[$this->keyName],
151-
);
152-
}
153-
154-
return new LaravelCursor($parameters, $pointsToNextItems);
155-
}
156-
167+
/**
168+
* @return int
169+
*/
157170
private function getDefaultPerPage(): int
158171
{
159172
if (is_int($this->defaultPerPage)) {
@@ -162,12 +175,4 @@ private function getDefaultPerPage(): int
162175

163176
return $this->query->getModel()->getPerPage();
164177
}
165-
166-
/**
167-
* Guess the key to use for the cursor.
168-
*/
169-
private function guessKey(): string
170-
{
171-
return $this->id?->key() ?? $this->query->getModel()->getKeyName();
172-
}
173178
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy