GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Utils::printCharCode()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 11
ccs 5
cts 5
cp 1
rs 10
c 0
b 0
f 0
cc 3
nc 3
nop 1
crap 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQL\Utils;
6
7
use ErrorException;
8
use Exception;
9
use GraphQL\Error\Error;
10
use GraphQL\Error\InvariantViolation;
11
use GraphQL\Error\Warning;
12
use GraphQL\Language\AST\Node;
13
use GraphQL\Type\Definition\Type;
14
use GraphQL\Type\Definition\WrappingType;
15
use InvalidArgumentException;
16
use LogicException;
17
use stdClass;
18
use Traversable;
19
use function array_keys;
20
use function array_map;
21
use function array_reduce;
22
use function array_shift;
23
use function array_slice;
24
use function array_values;
25
use function asort;
26
use function count;
27
use function dechex;
28
use function func_get_args;
29
use function func_num_args;
30
use function get_class;
31
use function gettype;
32
use function is_array;
33
use function is_int;
34
use function is_object;
35
use function is_scalar;
36
use function is_string;
37
use function json_encode;
38
use function levenshtein;
39
use function max;
40
use function mb_convert_encoding;
41
use function mb_strlen;
42
use function mb_substr;
43
use function method_exists;
44
use function ord;
45
use function pack;
46
use function preg_match;
47
use function property_exists;
48
use function range;
49
use function restore_error_handler;
50
use function set_error_handler;
51
use function sprintf;
52
use function strtolower;
53
use function unpack;
54
55
class Utils
56
{
57 384
    public static function undefined()
58
    {
59 384
        static $undefined;
60
61 384
        return $undefined ?: $undefined = new stdClass();
62
    }
63
64
    /**
65
     * Check if the value is invalid
66
     *
67
     * @param mixed $value
68
     *
69
     * @return bool
70
     */
71 75
    public static function isInvalid($value)
72
    {
73 75
        return self::undefined() === $value;
74
    }
75
76
    /**
77
     * @param object   $obj
78
     * @param mixed[]  $vars
79
     * @param string[] $requiredKeys
80
     *
81
     * @return object
82
     */
83 1012
    public static function assign($obj, array $vars, array $requiredKeys = [])
84
    {
85 1012
        foreach ($requiredKeys as $key) {
86 1
            if (! isset($vars[$key])) {
87 1
                throw new InvalidArgumentException(sprintf('Key %s is expected to be set and not to be null', $key));
88
            }
89
        }
90
91 1011
        foreach ($vars as $key => $value) {
92 1011
            if (! property_exists($obj, $key)) {
93
                $cls = get_class($obj);
94
                Warning::warn(
95
                    sprintf("Trying to set non-existing property '%s' on class '%s'", $key, $cls),
96
                    Warning::WARNING_ASSIGN
97
                );
98
            }
99 1011
            $obj->{$key} = $value;
100
        }
101
102 1011
        return $obj;
103
    }
104
105
    /**
106
     * @param iterable<mixed> $iterable
107
     *
108
     * @return mixed|null
109
     */
110 535
    public static function find($iterable, callable $predicate)
111
    {
112 535
        self::invariant(
113 535
            is_array($iterable) || $iterable instanceof Traversable,
114 535
            __METHOD__ . ' expects array or Traversable'
115
        );
116
117 535
        foreach ($iterable as $key => $value) {
118 238
            if ($predicate($value, $key)) {
119 238
                return $value;
120
            }
121
        }
122
123 371
        return null;
124
    }
125
126
    /**
127
     * @param iterable<mixed> $iterable
128
     *
129
     * @return array<mixed>
130
     *
131
     * @throws Exception
132
     */
133 284
    public static function filter($iterable, callable $predicate) : array
134
    {
135 284
        self::invariant(
136 284
            is_array($iterable) || $iterable instanceof Traversable,
137 284
            __METHOD__ . ' expects array or Traversable'
138
        );
139
140 284
        $result = [];
141 284
        $assoc  = false;
142 284
        foreach ($iterable as $key => $value) {
143 252
            if (! $assoc && ! is_int($key)) {
144
                $assoc = true;
145
            }
146 252
            if (! $predicate($value, $key)) {
147 95
                continue;
148
            }
149
150 250
            $result[$key] = $value;
151
        }
152
153 284
        return $assoc ? $result : array_values($result);
154
    }
155
156
    /**
157
     * @param iterable<mixed> $iterable
158
     *
159
     * @return array<mixed>
160
     *
161
     * @throws Exception
162
     */
163 399
    public static function map($iterable, callable $fn) : array
164
    {
165 399
        self::invariant(
166 399
            is_array($iterable) || $iterable instanceof Traversable,
167 399
            __METHOD__ . ' expects array or Traversable'
168
        );
169
170 399
        $map = [];
171 399
        foreach ($iterable as $key => $value) {
172 367
            $map[$key] = $fn($value, $key);
173
        }
174
175 397
        return $map;
176
    }
177
178
    /**
179
     * @param iterable<mixed> $iterable
180
     *
181
     * @return array<mixed>
182
     *
183
     * @throws Exception
184
     */
185
    public static function mapKeyValue($iterable, callable $fn) : array
186
    {
187
        self::invariant(
188
            is_array($iterable) || $iterable instanceof Traversable,
189
            __METHOD__ . ' expects array or Traversable'
190
        );
191
192
        $map = [];
193
        foreach ($iterable as $key => $value) {
194
            [$newKey, $newValue] = $fn($value, $key);
195
            $map[$newKey]        = $newValue;
196
        }
197
198
        return $map;
199
    }
200
201
    /**
202
     * @param iterable<mixed> $iterable
203
     *
204
     * @return array<mixed>
205
     *
206
     * @throws Exception
207
     */
208 217
    public static function keyMap($iterable, callable $keyFn) : array
209
    {
210 217
        self::invariant(
211 217
            is_array($iterable) || $iterable instanceof Traversable,
212 217
            __METHOD__ . ' expects array or Traversable'
213
        );
214
215 217
        $map = [];
216 217
        foreach ($iterable as $key => $value) {
217 216
            $newKey = $keyFn($value, $key);
218 216
            if (! is_scalar($newKey)) {
219
                continue;
220
            }
221
222 216
            $map[$newKey] = $value;
223
        }
224
225 217
        return $map;
226
    }
227
228
    /**
229
     * @param iterable<mixed> $iterable
230
     */
231
    public static function each($iterable, callable $fn) : void
232
    {
233
        self::invariant(
234
            is_array($iterable) || $iterable instanceof Traversable,
235
            __METHOD__ . ' expects array or Traversable'
236
        );
237
238
        foreach ($iterable as $key => $item) {
239
            $fn($item, $key);
240
        }
241
    }
242
243
    /**
244
     * Splits original iterable to several arrays with keys equal to $keyFn return
245
     *
246
     * E.g. Utils::groupBy([1, 2, 3, 4, 5], function($value) {return $value % 3}) will output:
247
     * [
248
     *    1 => [1, 4],
249
     *    2 => [2, 5],
250
     *    0 => [3],
251
     * ]
252
     *
253
     * $keyFn is also allowed to return array of keys. Then value will be added to all arrays with given keys
254
     *
255
     * @param iterable<mixed> $iterable
256 146
     *
257
     * @return array<array<mixed>>
258 146
     */
259 146
    public static function groupBy($iterable, callable $keyFn) : array
260 146
    {
261
        self::invariant(
262
            is_array($iterable) || $iterable instanceof Traversable,
263 146
            __METHOD__ . ' expects array or Traversable'
264 146
        );
265 28
266 28
        $grouped = [];
267 28
        foreach ($iterable as $key => $value) {
268
            $newKeys = (array) $keyFn($value, $key);
269
            foreach ($newKeys as $newKey) {
270
                $grouped[$newKey][] = $value;
271 146
            }
272
        }
273
274
        return $grouped;
275
    }
276
277
    /**
278
     * @param iterable<mixed> $iterable
279 181
     *
280
     * @return array<mixed>
281 181
     */
282 181
    public static function keyValMap($iterable, callable $keyFn, callable $valFn) : array
283 164
    {
284
        $map = [];
285
        foreach ($iterable as $item) {
286 180
            $map[$keyFn($item)] = $valFn($item);
287
        }
288
289
        return $map;
290
    }
291
292
    /**
293
     * @param iterable<mixed> $iterable
294 81
     */
295
    public static function every($iterable, callable $predicate) : bool
296 81
    {
297 81
        foreach ($iterable as $key => $value) {
298 81
            if (! $predicate($value, $key)) {
299
                return false;
300
            }
301
        }
302 79
303
        return true;
304
    }
305
306
    /**
307
     * @param iterable<mixed> $iterable
308
     */
309
    public static function some($iterable, callable $predicate) : bool
310 7
    {
311
        foreach ($iterable as $key => $value) {
312 7
            if ($predicate($value, $key)) {
313 7
                return true;
314 7
            }
315
        }
316
317
        return false;
318 2
    }
319
320
    /**
321
     * @param bool   $test
322
     * @param string $message
323
     */
324
    public static function invariant($test, $message = '')
325 1272
    {
326
        if (! $test) {
327 1272
            if (func_num_args() > 2) {
328 34
                $args = func_get_args();
329 7
                array_shift($args);
330 7
                $message = sprintf(...$args);
331 7
            }
332
            // TODO switch to Error here
333
            throw new InvariantViolation($message);
334 34
        }
335
    }
336 1268
337
    /**
338
     * @param Type|mixed $var
339
     *
340
     * @return string
341
     */
342
    public static function getVariableType($var)
343 1142
    {
344
        if ($var instanceof Type) {
345 1142
            // FIXME: Replace with schema printer call
346
            if ($var instanceof WrappingType) {
347
                $var = $var->getWrappedType(true);
348
            }
349
350
            return $var->name;
351
        }
352
353
        return is_object($var) ? get_class($var) : gettype($var);
354 1142
    }
355
356
    /**
357
     * @param mixed $var
358
     *
359
     * @return string
360
     */
361
    public static function printSafeJson($var)
362 39
    {
363
        if ($var instanceof stdClass) {
364 39
            $var = (array) $var;
365
        }
366
        if (is_array($var)) {
367 39
            return json_encode($var);
368 16
        }
369
        if ($var === '') {
370 24
            return '(empty string)';
371
        }
372
        if ($var === null) {
373 24
            return 'null';
374
        }
375
        if ($var === false) {
376 24
            return 'false';
377 1
        }
378
        if ($var === true) {
379 24
            return 'true';
380 1
        }
381
        if (is_string($var)) {
382 23
            return sprintf('"%s"', $var);
383 16
        }
384
        if (is_scalar($var)) {
385 8
            return (string) $var;
386 8
        }
387
388
        return gettype($var);
389
    }
390
391
    /**
392
     * @param Type|mixed $var
393
     *
394
     * @return string
395
     */
396
    public static function printSafe($var)
397 278
    {
398
        if ($var instanceof Type) {
399 278
            return $var->toString();
400 63
        }
401
        if (is_object($var)) {
402 259
            if (method_exists($var, '__toString')) {
403 178
                return (string) $var;
404
            }
405
406
            return 'instance of ' . get_class($var);
407 178
        }
408
        if (is_array($var)) {
409 93
            return json_encode($var);
410 19
        }
411
        if ($var === '') {
412 84
            return '(empty string)';
413 12
        }
414
        if ($var === null) {
415 79
            return 'null';
416 23
        }
417
        if ($var === false) {
418 64
            return 'false';
419 7
        }
420
        if ($var === true) {
421 63
            return 'true';
422 8
        }
423
        if (is_string($var)) {
424 61
            return $var;
425 33
        }
426
        if (is_scalar($var)) {
427 31
            return (string) $var;
428 31
        }
429
430
        return gettype($var);
431
    }
432
433
    /**
434
     * UTF-8 compatible chr()
435
     *
436
     * @param string $ord
437
     * @param string $encoding
438
     *
439
     * @return string
440
     */
441
    public static function chr($ord, $encoding = 'UTF-8')
442 27
    {
443
        if ($encoding === 'UCS-4BE') {
444 27
            return pack('N', $ord);
445 27
        }
446
447
        return mb_convert_encoding(self::chr($ord, 'UCS-4BE'), $encoding, 'UCS-4BE');
448 27
    }
449
450
    /**
451
     * UTF-8 compatible ord()
452
     *
453
     * @param string $char
454
     * @param string $encoding
455
     *
456
     * @return mixed
457
     */
458
    public static function ord($char, $encoding = 'UTF-8')
459 5
    {
460
        if (! $char && $char !== '0') {
461 5
            return 0;
462
        }
463
        if (! isset($char[1])) {
464 5
            return ord($char);
465
        }
466
        if ($encoding !== 'UCS-4BE') {
467 5
            $char = mb_convert_encoding($char, 'UCS-4BE', $encoding);
468 5
        }
469
470
        return unpack('N', $char)[1];
471 5
    }
472
473
    /**
474
     * Returns UTF-8 char code at given $positing of the $string
475
     *
476
     * @param string $string
477
     * @param int    $position
478
     *
479
     * @return mixed
480
     */
481
    public static function charCodeAt($string, $position)
482
    {
483
        $char = mb_substr($string, $position, 1, 'UTF-8');
484
485
        return self::ord($char);
486
    }
487
488
    /**
489
     * @param int|null $code
490
     *
491
     * @return string
492
     */
493
    public static function printCharCode($code)
494 22
    {
495
        if ($code === null) {
496 22
            return '<EOF>';
497 2
        }
498
499
        return $code < 0x007F
500 20
            // Trust JSON for ASCII.
501
            ? json_encode(self::chr($code))
502 18
            // Otherwise print the escaped form.
503
            : '"\\u' . dechex($code) . '"';
504 20
    }
505
506
    /**
507
     * Upholds the spec rules about naming.
508
     *
509
     * @param string $name
510
     *
511
     * @throws Error
512
     */
513
    public static function assertValidName($name)
514 26
    {
515
        $error = self::isValidNameError($name);
516 26
        if ($error) {
517 25
            throw $error;
518 2
        }
519
    }
520 23
521
    /**
522
     * Returns an Error if a name is invalid.
523
     *
524
     * @param string    $name
525
     * @param Node|null $node
526
     *
527
     * @return Error|null
528
     */
529
    public static function isValidNameError($name, $node = null)
530 104
    {
531
        self::invariant(is_string($name), 'Expected string');
532 104
533
        if (isset($name[1]) && $name[0] === '_' && $name[1] === '_') {
534 103
            return new Error(
535 90
                sprintf('Name "%s" must not begin with "__", which is reserved by ', $name) .
536 90
                'GraphQL introspection.',
537 90
                $node
538 90
            );
539
        }
540
541
        if (! preg_match('/^[_a-zA-Z][_a-zA-Z0-9]*$/', $name)) {
542 102
            return new Error(
543 6
                sprintf('Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but "%s" does not.', $name),
544 6
                $node
545 6
            );
546
        }
547
548
        return null;
549 101
    }
550
551
    /**
552
     * Wraps original callable with PHP error handling (using set_error_handler).
553
     * Resulting callable will collect all PHP errors that occur during the call in $errors array.
554
     *
555
     * @param ErrorException[] $errors
556
     *
557
     * @return callable
558
     */
559
    public static function withErrorHandling(callable $fn, array &$errors)
560
    {
561
        return static function () use ($fn, &$errors) {
562
            // Catch custom errors (to report them in query results)
563
            set_error_handler(static function ($severity, $message, $file, $line) use (&$errors) {
564
                $errors[] = new ErrorException($message, 0, $severity, $file, $line);
565
            });
566
567
            try {
568
                return $fn();
569
            } finally {
570
                restore_error_handler();
571
            }
572
        };
573
    }
574
575
    /**
576
     * @param string[] $items
577
     *
578
     * @return string
579
     */
580
    public static function quotedOrList(array $items)
581 22
    {
582
        $items = array_map(
583 22
            static function ($item) {
584
                return sprintf('"%s"', $item);
585 21
            },
586 22
            $items
587 22
        );
588
589
        return self::orList($items);
590 22
    }
591
592
    /**
593
     * @param string[] $items
594
     *
595
     * @return string
596
     */
597
    public static function orList(array $items)
598 30
    {
599
        if (count($items) === 0) {
600 30
            throw new LogicException('items must not need to be empty.');
601 1
        }
602
        $selected       = array_slice($items, 0, 5);
603 29
        $selectedLength = count($selected);
604 29
        $firstSelected  = $selected[0];
605 29
606
        if ($selectedLength === 1) {
607 29
            return $firstSelected;
608 17
        }
609
610
        return array_reduce(
611 12
            range(1, $selectedLength - 1),
612 12
            static function ($list, $index) use ($selected, $selectedLength) {
613
                return $list .
614
                    ($selectedLength > 2 ? ', ' : ' ') .
615 12
                    ($index === $selectedLength - 1 ? 'or ' : '') .
616 12
                    $selected[$index];
617 12
            },
618 12
            $firstSelected
619 12
        );
620
    }
621
622
    /**
623
     * Given an invalid input string and a list of valid options, returns a filtered
624
     * list of valid options sorted based on their similarity with the input.
625
     *
626
     * Includes a custom alteration from Damerau-Levenshtein to treat case changes
627
     * as a single edit which helps identify mis-cased values with an edit distance
628
     * of 1
629
     *
630
     * @param string   $input
631
     * @param string[] $options
632
     *
633
     * @return string[]
634
     */
635
    public static function suggestionList($input, array $options)
636 44
    {
637
        $optionsByDistance = [];
638 44
        $inputThreshold    = mb_strlen($input) / 2;
639 44
        foreach ($options as $option) {
640 44
            if ($input === $option) {
641 43
                $distance = 0;
642 1
            } else {
643
                $distance = (strtolower($input) === strtolower($option)
644 43
                    ? 1
645 4
                    : levenshtein($input, $option));
646 43
            }
647
            $threshold = max($inputThreshold, mb_strlen($option) / 2, 1);
648 43
            if ($distance > $threshold) {
649 43
                continue;
650 39
            }
651
652
            $optionsByDistance[$option] = $distance;
653 19
        }
654
655
        asort($optionsByDistance);
656 44
657
        return array_keys($optionsByDistance);
658 44
    }
659
}
660