Skip to content

Commit 8f54b66

Browse files
authored
Merge c12cb42 into 4e441b3
2 parents 4e441b3 + c12cb42 commit 8f54b66

20 files changed

+789
-164
lines changed

UPGRADE-0.12.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,48 @@ UPGRADE FROM 0.11 to 0.12
3535
App\GraphQL\:
3636
resource: ../GraphQL
3737
```
38+
39+
40+
### Relay Paginator, Connections & Edges
41+
42+
- Following the [paginator update](docs/helpers/relay-paginator.md) and the use of interfaces for Relay Connection & Edge, getters & setters must be use to manipulate Connection, Edge and PageInfo Properties
43+
44+
Before :
45+
46+
```php
47+
$connection->edges = $edges;
48+
$connection->totalCount = 10;
49+
...
50+
$edge->cursor = $cursor;
51+
$edge->node = $node;
52+
53+
```
54+
55+
After :
56+
57+
```php
58+
$connection->setEdges($edges);
59+
$connection->setTotalCount(10);
60+
...
61+
$edge->setCursor($cursor);
62+
$edge->setNode($node);
63+
```
64+
65+
Connection builder method are no more accessible statically:
66+
67+
Before:
68+
69+
```php
70+
use Overblog\GraphQLBundle\Relay\Connection\ConnectionBuilder;
71+
72+
ConnectionBuilder::connectionFromArray([]);
73+
```
74+
75+
After:
76+
77+
```php
78+
use Overblog\GraphQLBundle\Relay\Connection\ConnectionBuilder;
79+
80+
$connectionBuilder = new ConnectionBuilder();
81+
$connectionBuilder->connectionFromArray([]);
82+
```

docs/helpers/relay-paginator.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,3 +261,53 @@ public function resolveList($args)
261261
return $pagination->forward($args);
262262
}
263263
```
264+
265+
266+
#### Customize the way the connection & edges are built
267+
268+
Sometimes, you want to add fields to your Connection or Edges. In order to do so, you'll have to pass a custom instance of `ConnectionBuilder` to your Paginator as follow:
269+
270+
```php
271+
use Overblog\GraphQLBundle\Relay\Connection\ConnectionBuilder;
272+
273+
public function resolveSomething(Argument $args)
274+
{
275+
$connectionBuilder = new ConnectionBuilder(
276+
function(iterable $edges, PageInfo $pageInfo) : FriendsConnection {
277+
$connection = new FriendsConnection($edges, $pageInfo)
278+
$connection->setAverageAge(calculateAverage($edges));
279+
return $connection;
280+
},
281+
function(string $cursor, UserFriend $entity, int $index):FriendEdge {
282+
$edge = new FriendEdge($cursor, $entity->getUser());
283+
$edge->setFriendshipTime($entity->getCreatedAt());
284+
return $edge;
285+
}
286+
);
287+
288+
$paginator = new Paginator(function ($offset, $limit) use ($backend) {
289+
return $backend->getData($offset);
290+
}, true, $connectionBuilder);
291+
}
292+
```
293+
294+
The `ConnectionBuilder` constructor accepts two parameters. The first one is a callback to build the Connection object, and the second one is a callback to build an Edge object.
295+
296+
The connection callback will be call with the following parameters :
297+
298+
- `edges` An array of edges object implementing `Overblog\GraphQLBundle\Relay\Connection\EdgeInterface`
299+
- `pageInfo` a PageInfo object `Overblog\GraphQLBundle\Relay\Connection\Output\PageInfo`
300+
301+
This callback MUST return an instance of `Overblog\GraphQLBundle\Relay\Connection\ConnectionInterface`
302+
303+
304+
The edge callback will be call with the following parameters :
305+
306+
- `cursor` The cursor
307+
- `value` A value returned by the paginator data fetcher
308+
- `index` The index of the value
309+
310+
This callback MUST return an instance of `Overblog\GraphQLBundle\Relay\Connection\EdgeInterface`
311+
312+
If no callback are specified for the `ConnectionBuilder`, it'll generate instance of `Overblog\GraphQLBundle\Relay\Connection\Output\Connection` and `Overblog\GraphQLBundle\Relay\Connection\Output\Edge`
313+

src/Relay/Connection/Output/ConnectionBuilder.php renamed to src/Relay/Connection/ConnectionBuilder.php

Lines changed: 73 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22

33
declare(strict_types=1);
44

5-
namespace Overblog\GraphQLBundle\Relay\Connection\Output;
5+
namespace Overblog\GraphQLBundle\Relay\Connection;
66

77
use Overblog\GraphQLBundle\Definition\Argument;
8+
use Overblog\GraphQLBundle\Relay\Connection\Output\Connection;
9+
use Overblog\GraphQLBundle\Relay\Connection\Output\Edge;
10+
use Overblog\GraphQLBundle\Relay\Connection\Output\PageInfo;
811

912
/**
1013
* Class ConnectionBuilder.
@@ -15,6 +18,26 @@ class ConnectionBuilder
1518
{
1619
public const PREFIX = 'arrayconnection:';
1720

21+
/**
22+
* If set, used to generate the connection object.
23+
*
24+
* @var callable
25+
*/
26+
protected $connectionCallback;
27+
28+
/**
29+
* If set, used to generate the edge object.
30+
*
31+
* @var callable
32+
*/
33+
protected $edgeCallback;
34+
35+
public function __construct(callable $connectionCallback = null, callable $edgeCallback = null)
36+
{
37+
$this->connectionCallback = $connectionCallback;
38+
$this->edgeCallback = $edgeCallback;
39+
}
40+
1841
/**
1942
* A simple function that accepts an array and connection arguments, and returns
2043
* a connection object for use in GraphQL. It uses array offsets as pagination,
@@ -23,11 +46,11 @@ class ConnectionBuilder
2346
* @param array $data
2447
* @param array|Argument $args
2548
*
26-
* @return Connection
49+
* @return ConnectionInterface
2750
*/
28-
public static function connectionFromArray(array $data, $args = []): Connection
51+
public function connectionFromArray(array $data, $args = []): ConnectionInterface
2952
{
30-
return static::connectionFromArraySlice(
53+
return $this->connectionFromArraySlice(
3154
$data,
3255
$args,
3356
[
@@ -46,12 +69,12 @@ public static function connectionFromArray(array $data, $args = []): Connection
4669
*
4770
* @return mixed a promise
4871
*/
49-
public static function connectionFromPromisedArray($dataPromise, $args = [])
72+
public function connectionFromPromisedArray($dataPromise, $args = [])
5073
{
51-
self::checkPromise($dataPromise);
74+
$this->checkPromise($dataPromise);
5275

5376
return $dataPromise->then(function ($data) use ($args) {
54-
return static::connectionFromArray($data, $args);
77+
return $this->connectionFromArray($data, $args);
5578
});
5679
}
5780

@@ -68,11 +91,11 @@ public static function connectionFromPromisedArray($dataPromise, $args = [])
6891
* @param array|Argument $args
6992
* @param array $meta
7093
*
71-
* @return Connection
94+
* @return ConnectionInterface
7295
*/
73-
public static function connectionFromArraySlice(array $arraySlice, $args, array $meta): Connection
96+
public function connectionFromArraySlice(array $arraySlice, $args, array $meta): ConnectionInterface
7497
{
75-
$connectionArguments = self::getOptionsWithDefaults(
98+
$connectionArguments = $this->getOptionsWithDefaults(
7699
$args instanceof Argument ? $args->getRawArguments() : $args,
77100
[
78101
'after' => '',
@@ -81,7 +104,7 @@ public static function connectionFromArraySlice(array $arraySlice, $args, array
81104
'last' => null,
82105
]
83106
);
84-
$arraySliceMetaInfo = self::getOptionsWithDefaults(
107+
$arraySliceMetaInfo = $this->getOptionsWithDefaults(
85108
$meta,
86109
[
87110
'sliceStart' => 0,
@@ -97,8 +120,8 @@ public static function connectionFromArraySlice(array $arraySlice, $args, array
97120
$sliceStart = $arraySliceMetaInfo['sliceStart'];
98121
$arrayLength = $arraySliceMetaInfo['arrayLength'];
99122
$sliceEnd = $sliceStart + $arraySliceLength;
100-
$beforeOffset = static::getOffsetWithDefault($before, $arrayLength);
101-
$afterOffset = static::getOffsetWithDefault($after, -1);
123+
$beforeOffset = $this->getOffsetWithDefault($before, $arrayLength);
124+
$afterOffset = $this->getOffsetWithDefault($after, -1);
102125

103126
$startOffset = \max($sliceStart - 1, $afterOffset, -1) + 1;
104127
$endOffset = \min($sliceEnd, $beforeOffset, $arrayLength);
@@ -131,23 +154,40 @@ public static function connectionFromArraySlice(array $arraySlice, $args, array
131154
$edges = [];
132155

133156
foreach ($slice as $index => $value) {
134-
$edges[] = new Edge(static::offsetToCursor($startOffset + $index), $value);
157+
$cursor = $this->offsetToCursor($startOffset + $index);
158+
if ($this->edgeCallback) {
159+
$edge = ($this->edgeCallback)($cursor, $value, $index);
160+
if (!($edge instanceof EdgeInterface)) {
161+
throw new \InvalidArgumentException(\sprintf('The $edgeCallback of the ConnectionBuilder must return an instance of EdgeInterface'));
162+
}
163+
} else {
164+
$edge = new Edge($cursor, $value);
165+
}
166+
$edges[] = $edge;
135167
}
136168

137169
$firstEdge = $edges[0] ?? null;
138170
$lastEdge = \end($edges);
139171
$lowerBound = $after ? ($afterOffset + 1) : 0;
140172
$upperBound = $before ? $beforeOffset : $arrayLength;
141173

142-
return new Connection(
143-
$edges,
144-
new PageInfo(
145-
$firstEdge instanceof Edge ? $firstEdge->cursor : null,
146-
$lastEdge instanceof Edge ? $lastEdge->cursor : null,
147-
null !== $last ? $startOffset > $lowerBound : false,
148-
null !== $first ? $endOffset < $upperBound : false
149-
)
174+
$pageInfo = new PageInfo(
175+
$firstEdge instanceof EdgeInterface ? $firstEdge->getCursor() : null,
176+
$lastEdge instanceof EdgeInterface ? $lastEdge->getCursor() : null,
177+
null !== $last ? $startOffset > $lowerBound : false,
178+
null !== $first ? $endOffset < $upperBound : false
150179
);
180+
181+
if ($this->connectionCallback) {
182+
$connection = ($this->connectionCallback)($edges, $pageInfo);
183+
if (!($connection instanceof ConnectionInterface)) {
184+
throw new \InvalidArgumentException(\sprintf('The $connectionCallback of the ConnectionBuilder must return an instance of ConnectionInterface'));
185+
}
186+
187+
return $connection;
188+
}
189+
190+
return new Connection($edges, $pageInfo);
151191
}
152192

153193
/**
@@ -160,12 +200,12 @@ public static function connectionFromArraySlice(array $arraySlice, $args, array
160200
*
161201
* @return mixed a promise
162202
*/
163-
public static function connectionFromPromisedArraySlice($dataPromise, $args, array $meta)
203+
public function connectionFromPromisedArraySlice($dataPromise, $args, array $meta)
164204
{
165-
self::checkPromise($dataPromise);
205+
$this->checkPromise($dataPromise);
166206

167207
return $dataPromise->then(function ($arraySlice) use ($args, $meta) {
168-
return static::connectionFromArraySlice($arraySlice, $args, $meta);
208+
return $this->connectionFromArraySlice($arraySlice, $args, $meta);
169209
});
170210
}
171211

@@ -177,7 +217,7 @@ public static function connectionFromPromisedArraySlice($dataPromise, $args, arr
177217
*
178218
* @return null|string
179219
*/
180-
public static function cursorForObjectInConnection(array $data, $object): ?string
220+
public function cursorForObjectInConnection(array $data, $object): ? string
181221
{
182222
$offset = null;
183223

@@ -195,7 +235,7 @@ public static function cursorForObjectInConnection(array $data, $object): ?strin
195235
return null;
196236
}
197237

198-
return static::offsetToCursor($offset);
238+
return $this->offsetToCursor($offset);
199239
}
200240

201241
/**
@@ -208,12 +248,12 @@ public static function cursorForObjectInConnection(array $data, $object): ?strin
208248
*
209249
* @return int
210250
*/
211-
public static function getOffsetWithDefault(?string $cursor, int $defaultOffset): int
251+
public function getOffsetWithDefault(?string $cursor, int $defaultOffset): int
212252
{
213253
if (empty($cursor)) {
214254
return $defaultOffset;
215255
}
216-
$offset = static::cursorToOffset($cursor);
256+
$offset = $this->cursorToOffset($cursor);
217257

218258
return !\is_numeric($offset) ? $defaultOffset : (int) $offset;
219259
}
@@ -225,7 +265,7 @@ public static function getOffsetWithDefault(?string $cursor, int $defaultOffset)
225265
*
226266
* @return string
227267
*/
228-
public static function offsetToCursor($offset): string
268+
public function offsetToCursor($offset): string
229269
{
230270
return \base64_encode(static::PREFIX.$offset);
231271
}
@@ -237,7 +277,7 @@ public static function offsetToCursor($offset): string
237277
*
238278
* @return string
239279
*/
240-
public static function cursorToOffset($cursor): string
280+
public function cursorToOffset($cursor): string
241281
{
242282
if (null === $cursor) {
243283
return '';
@@ -246,12 +286,12 @@ public static function cursorToOffset($cursor): string
246286
return \str_replace(static::PREFIX, '', \base64_decode($cursor, true));
247287
}
248288

249-
private static function getOptionsWithDefaults(array $options, array $defaults)
289+
private function getOptionsWithDefaults(array $options, array $defaults)
250290
{
251291
return $options + $defaults;
252292
}
253293

254-
private static function checkPromise($value): void
294+
private function checkPromise($value): void
255295
{
256296
if (!\is_callable([$value, 'then'])) {
257297
throw new \InvalidArgumentException('This is not a valid promise.');
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Overblog\GraphQLBundle\Relay\Connection;
6+
7+
use Overblog\GraphQLBundle\Relay\Connection\Output\PageInfo;
8+
9+
interface ConnectionInterface
10+
{
11+
/**
12+
* Get the connection edges.
13+
*
14+
* @return iterable|EdgeInterface[]
15+
*/
16+
public function getEdges();
17+
18+
/**
19+
* Set the connection edges.
20+
*
21+
* @param iterable|EdgeInterface[] $edges
22+
*/
23+
public function setEdges(iterable $edges);
24+
25+
/**
26+
* Get the page info.
27+
*
28+
* @return PageInfoInterface
29+
*/
30+
public function getPageInfo(): ?PageInfoInterface;
31+
32+
/**
33+
* Set the page info.
34+
*
35+
* @param PageInfoInterface $pageInfo
36+
*/
37+
public function setPageInfo(PageInfoInterface $pageInfo);
38+
39+
/**
40+
* Get the total count.
41+
*
42+
* @return int
43+
*/
44+
public function getTotalCount(): ?int;
45+
46+
/**
47+
* Set the total count.
48+
*
49+
* @param int $totalCount
50+
*/
51+
public function setTotalCount(int $totalCount);
52+
}

0 commit comments

Comments
 (0)