1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/* |
4
|
|
|
* This file is part of the OverblogGraphQLBundle package. |
5
|
|
|
* |
6
|
|
|
* (c) Overblog <http://github.com/overblog/> |
7
|
|
|
* |
8
|
|
|
* For the full copyright and license information, please view the LICENSE |
9
|
|
|
* file that was distributed with this source code. |
10
|
|
|
*/ |
11
|
|
|
|
12
|
|
|
namespace Overblog\GraphQLBundle\Relay\Connection; |
13
|
|
|
|
14
|
|
|
use GraphQL\Executor\Promise\Promise; |
15
|
|
|
use Overblog\GraphQLBundle\Definition\Argument; |
16
|
|
|
use Overblog\GraphQLBundle\Relay\Connection\Output\Connection; |
17
|
|
|
use Overblog\GraphQLBundle\Relay\Connection\Output\ConnectionBuilder; |
18
|
|
|
|
19
|
|
|
class Paginator |
20
|
|
|
{ |
21
|
|
|
const MODE_REGULAR = false; |
22
|
|
|
const MODE_PROMISE = true; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* @var callable |
26
|
|
|
*/ |
27
|
|
|
private $fetcher; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* @var bool |
31
|
|
|
*/ |
32
|
|
|
private $promise; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* @param callable $fetcher |
36
|
|
|
* @param bool $promise |
37
|
|
|
*/ |
38
|
14 |
|
public function __construct(callable $fetcher, $promise = self::MODE_REGULAR) |
39
|
|
|
{ |
40
|
14 |
|
$this->fetcher = $fetcher; |
41
|
14 |
|
$this->promise = $promise; |
42
|
14 |
|
} |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* @param Argument|array $args |
46
|
|
|
* @param int|callable $total |
47
|
|
|
* @param array $callableArgs |
48
|
|
|
* |
49
|
|
|
* @return Connection |
50
|
|
|
*/ |
51
|
7 |
|
public function backward($args, $total, array $callableArgs = []) |
52
|
|
|
{ |
53
|
7 |
|
$total = is_callable($total) ? call_user_func_array($total, $callableArgs) : $total; |
54
|
|
|
|
55
|
7 |
|
$args = $this->protectArgs($args); |
56
|
7 |
|
$limit = $args['last']; |
57
|
7 |
|
$offset = max(0, ConnectionBuilder::getOffsetWithDefault($args['before'], $total) - $limit); |
58
|
|
|
|
59
|
7 |
|
$entities = call_user_func($this->fetcher, $offset, $limit); |
60
|
|
|
|
61
|
|
View Code Duplication |
return $this->handleEntities($entities, function ($entities) use ($args, $offset, $total) { |
|
|
|
|
62
|
7 |
|
return ConnectionBuilder::connectionFromArraySlice($entities, $args, [ |
63
|
7 |
|
'sliceStart' => $offset, |
64
|
7 |
|
'arrayLength' => $total, |
65
|
7 |
|
]); |
66
|
7 |
|
}); |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* @param Argument|array $args |
71
|
|
|
* |
72
|
|
|
* @return Connection |
73
|
|
|
*/ |
74
|
7 |
|
public function forward($args) |
75
|
|
|
{ |
76
|
7 |
|
$args = $this->protectArgs($args); |
77
|
7 |
|
$limit = $args['first']; |
78
|
7 |
|
$offset = ConnectionBuilder::getOffsetWithDefault($args['after'], 0); |
79
|
|
|
|
80
|
|
|
// If we don't have a cursor or if it's not valid, then we must not use the slice method |
81
|
7 |
|
if (!is_numeric(ConnectionBuilder::cursorToOffset($args['after'])) || !$args['after']) { |
82
|
4 |
|
$entities = call_user_func($this->fetcher, $offset, $limit + 1); |
83
|
|
|
|
84
|
|
|
return $this->handleEntities($entities, function ($entities) use ($args) { |
85
|
3 |
|
return ConnectionBuilder::connectionFromArray($entities, $args); |
86
|
4 |
|
}); |
87
|
|
|
} else { |
88
|
3 |
|
$entities = call_user_func($this->fetcher, $offset, $limit + 2); |
89
|
|
|
|
90
|
3 |
View Code Duplication |
return $this->handleEntities($entities, function ($entities) use ($args, $offset) { |
|
|
|
|
91
|
3 |
|
return ConnectionBuilder::connectionFromArraySlice($entities, $args, [ |
92
|
3 |
|
'sliceStart' => $offset, |
93
|
3 |
|
'arrayLength' => $offset + count($entities), |
94
|
3 |
|
]); |
95
|
3 |
|
}); |
96
|
|
|
} |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
/** |
100
|
|
|
* @param Argument|array $args |
101
|
|
|
* @param int|callable $total |
102
|
|
|
* @param array $callableArgs |
103
|
|
|
* |
104
|
|
|
* @return Connection |
105
|
|
|
*/ |
106
|
5 |
|
public function auto($args, $total, $callableArgs = []) |
107
|
|
|
{ |
108
|
5 |
|
$args = $this->protectArgs($args); |
109
|
|
|
|
110
|
5 |
|
if ($args['last']) { |
111
|
3 |
|
return $this->backward($args, $total, $callableArgs); |
112
|
|
|
} else { |
113
|
2 |
|
return $this->forward($args); |
114
|
|
|
} |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
/** |
118
|
|
|
* @param array|object $entities An array of entities to paginate or a promise |
119
|
|
|
* @param callable $callback |
120
|
|
|
* |
121
|
|
|
* @return Connection|object A connection or a promise |
122
|
|
|
*/ |
123
|
14 |
|
private function handleEntities($entities, callable $callback) |
124
|
|
|
{ |
125
|
14 |
|
if ($this->promise) { |
126
|
1 |
|
return $entities->then($callback); |
127
|
|
|
} |
128
|
|
|
|
129
|
13 |
|
return call_user_func($callback, $entities); |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* @param Argument|array $args |
134
|
|
|
* |
135
|
|
|
* @return Argument |
136
|
|
|
*/ |
137
|
14 |
|
private function protectArgs($args) |
138
|
|
|
{ |
139
|
14 |
|
return $args instanceof Argument ? $args : new Argument($args); |
140
|
|
|
} |
141
|
|
|
} |
142
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.