Scrutinizer GitHub App not installed

We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.

Install GitHub App

Passed
Push — master ( 4e441b...b33a6d )
by Jérémiah
39s
created

ConnectionBuilder   A

Complexity

Total Complexity 36

Size/Duplication

Total Lines 296
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 99.06%

Importance

Changes 0
Metric Value
wmc 36
lcom 1
cbo 5
dl 0
loc 296
ccs 105
cts 106
cp 0.9906
rs 9.52
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A connectionFromArray() 0 11 1
A connectionFromPromisedArray() 0 8 1
C connectionFromArraySlice() 0 74 12
A connectionFromPromisedArraySlice() 0 8 1
A cursorForObjectInConnection() 0 20 4
A getOffsetWithDefault() 0 9 3
A offsetToCursor() 0 4 1
A cursorToOffset() 0 8 2
A createEdges() 0 19 4
A createConnection() 0 13 3
A getOptionsWithDefaults() 0 4 1
A checkPromise() 0 6 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Overblog\GraphQLBundle\Relay\Connection;
6
7
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;
11
12
/**
13
 * Class ConnectionBuilder.
14
 *
15
 * https://github.com/graphql/graphql-relay-js/blob/master/src/connection/arrayconnection.js
16
 */
17
class ConnectionBuilder
18
{
19
    public const PREFIX = 'arrayconnection:';
20
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 77
    public function __construct(callable $connectionCallback = null, callable $edgeCallback = null)
36
    {
37 77
        $this->connectionCallback = $connectionCallback;
38 77
        $this->edgeCallback = $edgeCallback;
39 77
    }
40
41
    /**
42
     * A simple function that accepts an array and connection arguments, and returns
43
     * a connection object for use in GraphQL. It uses array offsets as pagination,
44
     * so pagination will only work if the array is static.
45
     *
46
     * @param array          $data
47
     * @param array|Argument $args
48
     *
49
     * @return ConnectionInterface
50
     */
51 66
    public function connectionFromArray(array $data, $args = []): ConnectionInterface
52
    {
53 66
        return $this->connectionFromArraySlice(
54 66
            $data,
55 66
            $args,
56
            [
57 66
                'sliceStart' => 0,
58 66
                'arrayLength' => \count($data),
59
            ]
60
        );
61
    }
62
63
    /**
64
     * A version of `connectionFromArray` that takes a promised array, and returns a
65
     * promised connection.
66
     *
67
     * @param mixed          $dataPromise a promise
68
     * @param array|Argument $args
69
     *
70
     * @return mixed a promise
71
     */
72 14
    public function connectionFromPromisedArray($dataPromise, $args = [])
73
    {
74 14
        $this->checkPromise($dataPromise);
75
76
        return $dataPromise->then(function ($data) use ($args) {
77 4
            return $this->connectionFromArray($data, $args);
78 4
        });
79
    }
80
81
    /**
82
     * Given a slice (subset) of an array, returns a connection object for use in
83
     * GraphQL.
84
     *
85
     * This function is similar to `connectionFromArray`, but is intended for use
86
     * cases where you know the cardinality of the connection, consider it too large
87
     * to materialize the entire array, and instead wish pass in a slice of the
88
     * total result large enough to cover the range specified in `args`.
89
     *
90
     * @param array          $arraySlice
91
     * @param array|Argument $args
92
     * @param array          $meta
93
     *
94
     * @return ConnectionInterface
95
     */
96 90
    public function connectionFromArraySlice(array $arraySlice, $args, array $meta): ConnectionInterface
97
    {
98 90
        $connectionArguments = $this->getOptionsWithDefaults(
99 90
            $args instanceof Argument ? $args->getRawArguments() : $args,
100
            [
101 90
                'after' => '',
102
                'before' => '',
103
                'first' => null,
104
                'last' => null,
105
            ]
106
        );
107 90
        $arraySliceMetaInfo = $this->getOptionsWithDefaults(
108 90
            $meta,
109
            [
110 90
                'sliceStart' => 0,
111
                'arrayLength' => 0,
112
            ]
113
        );
114
115 90
        $arraySliceLength = \count($arraySlice);
116 90
        $after = $connectionArguments['after'];
117 90
        $before = $connectionArguments['before'];
118 90
        $first = $connectionArguments['first'];
119 90
        $last = $connectionArguments['last'];
120 90
        $sliceStart = $arraySliceMetaInfo['sliceStart'];
121 90
        $arrayLength = $arraySliceMetaInfo['arrayLength'];
122 90
        $sliceEnd = $sliceStart + $arraySliceLength;
123 90
        $beforeOffset = $this->getOffsetWithDefault($before, $arrayLength);
124 90
        $afterOffset = $this->getOffsetWithDefault($after, -1);
125
126 90
        $startOffset = \max($sliceStart - 1, $afterOffset, -1) + 1;
127 90
        $endOffset = \min($sliceEnd, $beforeOffset, $arrayLength);
128
129 90
        if (\is_numeric($first)) {
130 54
            if ($first < 0) {
131 2
                throw new \InvalidArgumentException('Argument "first" must be a non-negative integer');
132
            }
133 52
            $endOffset = \min($endOffset, $startOffset + $first);
134
        }
135
136 88
        if (\is_numeric($last)) {
137 24
            if ($last < 0) {
138 2
                throw new \InvalidArgumentException('Argument "last" must be a non-negative integer');
139
            }
140
141 22
            $startOffset = \max($startOffset, $endOffset - $last);
142
        }
143
144
        // If supplied slice is too large, trim it down before mapping over it.
145 86
        $offset = \max($startOffset - $sliceStart, 0);
146 86
        $length = ($arraySliceLength - ($sliceEnd - $endOffset)) - $offset;
147
148 86
        $slice = \array_slice(
149 86
            $arraySlice,
150 86
            $offset,
151 86
            $length
152
        );
153
154 86
        $edges = $this->createEdges($slice, $startOffset);
155
156 86
        $firstEdge = $edges[0] ?? null;
157 86
        $lastEdge = \end($edges);
158 86
        $lowerBound = $after ? ($afterOffset + 1) : 0;
159 86
        $upperBound = $before ? $beforeOffset : $arrayLength;
160
161 86
        $pageInfo = new PageInfo(
162 86
            $firstEdge instanceof EdgeInterface ? $firstEdge->getCursor() : null,
163 86
            $lastEdge instanceof EdgeInterface ? $lastEdge->getCursor() : null,
164 86
            null !== $last ? $startOffset > $lowerBound : false,
165 86
            null !== $first ? $endOffset < $upperBound : false
166
        );
167
168 86
        return $this->createConnection($edges, $pageInfo);
169
    }
170
171
    /**
172
     * A version of `connectionFromArraySlice` that takes a promised array slice,
173
     * and returns a promised connection.
174
     *
175
     * @param mixed          $dataPromise a promise
176
     * @param array|Argument $args
177
     * @param array          $meta
178
     *
179
     * @return mixed a promise
180
     */
181 12
    public function connectionFromPromisedArraySlice($dataPromise, $args, array $meta)
182
    {
183 12
        $this->checkPromise($dataPromise);
184
185
        return $dataPromise->then(function ($arraySlice) use ($args, $meta) {
186 2
            return $this->connectionFromArraySlice($arraySlice, $args, $meta);
187 2
        });
188
    }
189
190
    /**
191
     * Return the cursor associated with an object in an array.
192
     *
193
     * @param array $data
194
     * @param mixed $object
195
     *
196
     * @return null|string
197
     */
198 4
    public function cursorForObjectInConnection(array $data, $object): ? string
199
    {
200 4
        $offset = null;
201
202 4
        foreach ($data as $i => $entry) {
203
            // When using the comparison operator (==), object variables are compared in a simple manner,
204
            // namely: Two object instances are equal if they have the same attributes and values,
205
            // and are instances of the same class.
206 4
            if ($entry == $object) {
207 2
                $offset = $i;
208 4
                break;
209
            }
210
        }
211
212 4
        if (null === $offset) {
213 2
            return null;
214
        }
215
216 2
        return $this->offsetToCursor($offset);
217
    }
218
219
    /**
220
     * Given an optional cursor and a default offset, returns the offset
221
     * to use; if the cursor contains a valid offset, that will be used,
222
     * otherwise it will be the default.
223
     *
224
     * @param string|null $cursor
225
     * @param int         $defaultOffset
226
     *
227
     * @return int
228
     */
229 92
    public function getOffsetWithDefault(?string $cursor, int $defaultOffset): int
230
    {
231 92
        if (empty($cursor)) {
232 74
            return $defaultOffset;
233
        }
234 44
        $offset = $this->cursorToOffset($cursor);
235
236 44
        return !\is_numeric($offset) ? $defaultOffset : (int) $offset;
237
    }
238
239
    /**
240
     * Creates the cursor string from an offset.
241
     *
242
     * @param $offset
243
     *
244
     * @return string
245
     */
246 84
    public function offsetToCursor($offset): string
247
    {
248 84
        return \base64_encode(static::PREFIX.$offset);
249
    }
250
251
    /**
252
     * Redefines the offset from the cursor string.
253
     *
254
     * @param $cursor
255
     *
256
     * @return string
257
     */
258 48
    public function cursorToOffset($cursor): string
259
    {
260 48
        if (null === $cursor) {
261 3
            return '';
262
        }
263
264 45
        return \str_replace(static::PREFIX, '', \base64_decode($cursor, true));
265
    }
266
267 86
    private function createEdges(iterable $slice, int $startOffset): array
268
    {
269 86
        $edges = [];
270
271 86
        foreach ($slice as $index => $value) {
272 81
            $cursor = $this->offsetToCursor($startOffset + $index);
273 81
            if ($this->edgeCallback) {
274 2
                $edge = ($this->edgeCallback)($cursor, $value, $index);
275 2
                if (!($edge instanceof EdgeInterface)) {
276 2
                    throw new \InvalidArgumentException(\sprintf('The $edgeCallback of the ConnectionBuilder must return an instance of EdgeInterface'));
277
                }
278
            } else {
279 79
                $edge = new Edge($cursor, $value);
280
            }
281 81
            $edges[] = $edge;
282
        }
283
284 86
        return $edges;
285
    }
286
287 86
    private function createConnection($edges, PageInfoInterface $pageInfo): ConnectionInterface
288
    {
289 86
        if ($this->connectionCallback) {
290 2
            $connection = ($this->connectionCallback)($edges, $pageInfo);
291 2
            if (!($connection instanceof ConnectionInterface)) {
292
                throw new \InvalidArgumentException(\sprintf('The $connectionCallback of the ConnectionBuilder must return an instance of ConnectionInterface'));
293
            }
294
295 2
            return $connection;
296
        }
297
298 84
        return new Connection($edges, $pageInfo);
299
    }
300
301 90
    private function getOptionsWithDefaults(array $options, array $defaults)
302
    {
303 90
        return $options + $defaults;
304
    }
305
306 26
    private function checkPromise($value): void
307
    {
308 26
        if (!\is_callable([$value, 'then'])) {
309 20
            throw new \InvalidArgumentException('This is not a valid promise.');
310
        }
311 6
    }
312
}
313