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

Completed
Pull Request — master (#413)
by Jérémiah
21:27
created

ConnectionBuilder::__callStatic()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 29
rs 9.456
c 0
b 0
f 0
cc 3
nc 4
nop 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
    public function __construct(callable $connectionCallback = null, callable $edgeCallback = null)
36
    {
37
        $this->connectionCallback = $connectionCallback;
38
        $this->edgeCallback = $edgeCallback;
39
    }
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
    public function connectionFromArray(array $data, $args = []): ConnectionInterface
52
    {
53
        return $this->connectionFromArraySlice(
54
            $data,
55
            $args,
56
            [
57
                'sliceStart' => 0,
58
                '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
    public function connectionFromPromisedArray($dataPromise, $args = [])
73
    {
74
        $this->checkPromise($dataPromise);
75
76
        return $dataPromise->then(function ($data) use ($args) {
77
            return $this->connectionFromArray($data, $args);
78
        });
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
    public function connectionFromArraySlice(array $arraySlice, $args, array $meta): ConnectionInterface
97
    {
98
        $connectionArguments = $this->getOptionsWithDefaults(
99
            $args instanceof Argument ? $args->getRawArguments() : $args,
100
            [
101
                'after' => '',
102
                'before' => '',
103
                'first' => null,
104
                'last' => null,
105
            ]
106
        );
107
        $arraySliceMetaInfo = $this->getOptionsWithDefaults(
108
            $meta,
109
            [
110
                'sliceStart' => 0,
111
                'arrayLength' => 0,
112
            ]
113
        );
114
115
        $arraySliceLength = \count($arraySlice);
116
        $after = $connectionArguments['after'];
117
        $before = $connectionArguments['before'];
118
        $first = $connectionArguments['first'];
119
        $last = $connectionArguments['last'];
120
        $sliceStart = $arraySliceMetaInfo['sliceStart'];
121
        $arrayLength = $arraySliceMetaInfo['arrayLength'];
122
        $sliceEnd = $sliceStart + $arraySliceLength;
123
        $beforeOffset = $this->getOffsetWithDefault($before, $arrayLength);
124
        $afterOffset = $this->getOffsetWithDefault($after, -1);
125
126
        $startOffset = \max($sliceStart - 1, $afterOffset, -1) + 1;
127
        $endOffset = \min($sliceEnd, $beforeOffset, $arrayLength);
128
129
        if (\is_numeric($first)) {
130
            if ($first < 0) {
131
                throw new \InvalidArgumentException('Argument "first" must be a non-negative integer');
132
            }
133
            $endOffset = \min($endOffset, $startOffset + $first);
134
        }
135
136
        if (\is_numeric($last)) {
137
            if ($last < 0) {
138
                throw new \InvalidArgumentException('Argument "last" must be a non-negative integer');
139
            }
140
141
            $startOffset = \max($startOffset, $endOffset - $last);
142
        }
143
144
        // If supplied slice is too large, trim it down before mapping over it.
145
        $offset = \max($startOffset - $sliceStart, 0);
146
        $length = ($arraySliceLength - ($sliceEnd - $endOffset)) - $offset;
147
148
        $slice = \array_slice(
149
            $arraySlice,
150
            $offset,
151
            $length
152
        );
153
154
        $edges = [];
155
156
        foreach ($slice as $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;
167
        }
168
169
        $firstEdge = $edges[0] ?? null;
170
        $lastEdge = \end($edges);
171
        $lowerBound = $after ? ($afterOffset + 1) : 0;
172
        $upperBound = $before ? $beforeOffset : $arrayLength;
173
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
179
        );
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);
191
    }
192
193
    /**
194
     * A version of `connectionFromArraySlice` that takes a promised array slice,
195
     * and returns a promised connection.
196
     *
197
     * @param mixed          $dataPromise a promise
198
     * @param array|Argument $args
199
     * @param array          $meta
200
     *
201
     * @return mixed a promise
202
     */
203
    public function connectionFromPromisedArraySlice($dataPromise, $args, array $meta)
204
    {
205
        $this->checkPromise($dataPromise);
206
207
        return $dataPromise->then(function ($arraySlice) use ($args, $meta) {
208
            return $this->connectionFromArraySlice($arraySlice, $args, $meta);
209
        });
210
    }
211
212
    /**
213
     * Return the cursor associated with an object in an array.
214
     *
215
     * @param array $data
216
     * @param mixed $object
217
     *
218
     * @return null|string
219
     */
220
    public function cursorForObjectInConnection(array $data, $object): ? string
221
    {
222
        $offset = null;
223
224
        foreach ($data as $i => $entry) {
225
            // When using the comparison operator (==), object variables are compared in a simple manner,
226
            // namely: Two object instances are equal if they have the same attributes and values,
227
            // and are instances of the same class.
228
            if ($entry == $object) {
229
                $offset = $i;
230
                break;
231
            }
232
        }
233
234
        if (null === $offset) {
235
            return null;
236
        }
237
238
        return $this->offsetToCursor($offset);
239
    }
240
241
    /**
242
     * Given an optional cursor and a default offset, returns the offset
243
     * to use; if the cursor contains a valid offset, that will be used,
244
     * otherwise it will be the default.
245
     *
246
     * @param string|null $cursor
247
     * @param int         $defaultOffset
248
     *
249
     * @return int
250
     */
251
    public function getOffsetWithDefault(?string $cursor, int $defaultOffset): int
252
    {
253
        if (empty($cursor)) {
254
            return $defaultOffset;
255
        }
256
        $offset = $this->cursorToOffset($cursor);
257
258
        return !\is_numeric($offset) ? $defaultOffset : (int) $offset;
259
    }
260
261
    /**
262
     * Creates the cursor string from an offset.
263
     *
264
     * @param $offset
265
     *
266
     * @return string
267
     */
268
    public function offsetToCursor($offset): string
269
    {
270
        return \base64_encode(static::PREFIX.$offset);
271
    }
272
273
    /**
274
     * Redefines the offset from the cursor string.
275
     *
276
     * @param $cursor
277
     *
278
     * @return string
279
     */
280
    public function cursorToOffset($cursor): string
281
    {
282
        if (null === $cursor) {
283
            return '';
284
        }
285
286
        return \str_replace(static::PREFIX, '', \base64_decode($cursor, true));
287
    }
288
289
    private function getOptionsWithDefaults(array $options, array $defaults)
290
    {
291
        return $options + $defaults;
292
    }
293
294
    private function checkPromise($value): void
295
    {
296
        if (!\is_callable([$value, 'then'])) {
297
            throw new \InvalidArgumentException('This is not a valid promise.');
298
        }
299
    }
300
301
    public static function __callStatic($method, $arguments)
302
    {
303
        static $deprecatedStaticMethods = [
304
            'connectionFromArray', 'connectionFromPromisedArray', 'connectionFromArraySlice',
305
            'cursorForObjectInConnection', 'getOffsetWithDefault', 'offsetToCursor', 'cursorToOffset',
306
        ];
307
        static $instance = null;
308
        if (null === $instance) {
309
            $instance = new static();
310
        }
311
312
        if (\in_array($method, $deprecatedStaticMethods)) {
313
            @\trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
314
                \sprintf(
315
                    'Calling static method %s::%s is deprecated as of 0.12 and will be removed in 0.13. '.
316
                    'You should use an object instance of %s to access "%s" method.',
317
                    __CLASS__,
318
                    $method,
319
                    __CLASS__,
320
                    $method
321
                ),
322
                \E_USER_DEPRECATED
323
            );
324
325
            return $instance->$method(...$arguments);
326
        }
327
328
        throw new \BadMethodCallException(\sprintf('Undefined static method %s::%s', __CLASS__, $method));
329
    }
330
}
331