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.
Failed Conditions
Pull Request — master (#1)
by Šimon
03:29
created

FormattedError::createFromException()   C

Complexity

Conditions 10
Paths 54

Size

Total Lines 47
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 10.4096

Importance

Changes 0
Metric Value
dl 0
loc 47
ccs 21
cts 25
cp 0.84
rs 5.1578
c 0
b 0
f 0
cc 10
eloc 27
nc 54
nop 3
crap 10.4096

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQL\Error;
6
7
use GraphQL\Language\AST\Node;
8
use GraphQL\Language\Source;
9
use GraphQL\Language\SourceLocation;
10
use GraphQL\Type\Definition\Type;
11
use GraphQL\Type\Definition\WrappingType;
12
use GraphQL\Utils\Utils;
13
use function addcslashes;
14
use function array_filter;
15
use function array_intersect_key;
16
use function array_map;
17
use function array_merge;
18
use function array_shift;
19
use function count;
20
use function get_class;
21
use function gettype;
22
use function implode;
23
use function is_array;
24
use function is_bool;
25
use function is_object;
26
use function is_scalar;
27
use function is_string;
28
use function mb_strlen;
29
use function preg_split;
30
use function sprintf;
31
use function str_repeat;
32
use function strlen;
33
34
/**
35
 * This class is used for [default error formatting](error-handling.md).
36
 * It converts PHP exceptions to [spec-compliant errors](https://facebook.github.io/graphql/#sec-Errors)
37
 * and provides tools for error debugging.
38
 */
39
class FormattedError
40
{
41
    /** @var string */
42
    private static $internalErrorMessage = 'Internal server error';
43
44
    /**
45
     * Set default error message for internal errors formatted using createFormattedError().
46
     * This value can be overridden by passing 3rd argument to `createFormattedError()`.
47
     *
48
     * @api
49
     * @param string $msg
50
     */
51
    public static function setInternalErrorMessage($msg)
52
    {
53
        self::$internalErrorMessage = $msg;
54
    }
55
56
    /**
57
     * Prints a GraphQLError to a string, representing useful location information
58
     * about the error's position in the source.
59
     *
60
     * @return string
61
     */
62 11
    public static function printError(Error $error)
63
    {
64 11
        $printedLocations = [];
65 11
        if ($error->nodes) {
66
            /** @var Node $node */
67 1
            foreach ($error->nodes as $node) {
68 1
                if (! $node->loc) {
69
                    continue;
70
                }
71
72 1
                $printedLocations[] = self::highlightSourceAtLocation(
73 1
                    $node->loc->source,
0 ignored issues
show
Bug introduced by
It seems like $node->loc->source can also be of type null; however, parameter $source of GraphQL\Error\FormattedE...lightSourceAtLocation() does only seem to accept GraphQL\Language\Source, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

73
                    /** @scrutinizer ignore-type */ $node->loc->source,
Loading history...
74 1
                    $node->loc->source->getLocation($node->loc->start)
0 ignored issues
show
Bug introduced by
The method getLocation() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

74
                    $node->loc->source->/** @scrutinizer ignore-call */ 
75
                                        getLocation($node->loc->start)

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
75
                );
76
            }
77 10
        } elseif ($error->getSource() && $error->getLocations()) {
78 9
            $source = $error->getSource();
79 9
            foreach ($error->getLocations() as $location) {
80 9
                $printedLocations[] = self::highlightSourceAtLocation($source, $location);
81
            }
82
        }
83
84 11
        return ! $printedLocations
85 1
            ? $error->getMessage()
86 11
            : implode("\n\n", array_merge([$error->getMessage()], $printedLocations)) . "\n";
87
    }
88
89
    /**
90
     * Render a helpful description of the location of the error in the GraphQL
91
     * Source document.
92
     *
93
     * @return string
94
     */
95 10
    private static function highlightSourceAtLocation(Source $source, SourceLocation $location)
96
    {
97 10
        $line          = $location->line;
98 10
        $lineOffset    = $source->locationOffset->line - 1;
99 10
        $columnOffset  = self::getColumnOffset($source, $location);
100 10
        $contextLine   = $line + $lineOffset;
101 10
        $contextColumn = $location->column + $columnOffset;
102 10
        $prevLineNum   = (string) ($contextLine - 1);
103 10
        $lineNum       = (string) $contextLine;
104 10
        $nextLineNum   = (string) ($contextLine + 1);
105 10
        $padLen        = strlen($nextLineNum);
106 10
        $lines         = preg_split('/\r\n|[\n\r]/', $source->body);
107
108 10
        $lines[0] = self::whitespace($source->locationOffset->column - 1) . $lines[0];
109
110
        $outputLines = [
111 10
            sprintf('%s (%s:%s)', $source->name, $contextLine, $contextColumn),
112 10
            $line >= 2 ? (self::lpad($padLen, $prevLineNum) . ': ' . $lines[$line - 2]) : null,
113 10
            self::lpad($padLen, $lineNum) . ': ' . $lines[$line - 1],
114 10
            self::whitespace(2 + $padLen + $contextColumn - 1) . '^',
115 10
            $line < count($lines) ? self::lpad($padLen, $nextLineNum) . ': ' . $lines[$line] : null,
0 ignored issues
show
Bug introduced by
It seems like $lines can also be of type false; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

115
            $line < count(/** @scrutinizer ignore-type */ $lines) ? self::lpad($padLen, $nextLineNum) . ': ' . $lines[$line] : null,
Loading history...
116
        ];
117
118 10
        return implode("\n", array_filter($outputLines));
119
    }
120
121
    /**
122
     * @return int
123
     */
124 10
    private static function getColumnOffset(Source $source, SourceLocation $location)
125
    {
126 10
        return $location->line === 1 ? $source->locationOffset->column - 1 : 0;
127
    }
128
129
    /**
130
     * @param int $len
131
     * @return string
132
     */
133 10
    private static function whitespace($len)
134
    {
135 10
        return str_repeat(' ', $len);
136
    }
137
138
    /**
139
     * @param int $len
140
     * @return string
141
     */
142 10
    private static function lpad($len, $str)
143
    {
144 10
        return self::whitespace($len - mb_strlen($str)) . $str;
145
    }
146
147
    /**
148
     * Standard GraphQL error formatter. Converts any exception to array
149
     * conforming to GraphQL spec.
150
     *
151
     * This method only exposes exception message when exception implements ClientAware interface
152
     * (or when debug flags are passed).
153
     *
154
     * For a list of available debug flags see GraphQL\Error\Debug constants.
155
     *
156
     * @api
157
     * @param \Throwable $e
158
     * @param bool|int   $debug
159
     * @param string     $internalErrorMessage
160
     * @return mixed[]
161
     * @throws \Throwable
162
     */
163 65
    public static function createFromException($e, $debug = false, $internalErrorMessage = null)
164
    {
165 65
        Utils::invariant(
166 65
            $e instanceof \Exception || $e instanceof \Throwable,
167 65
            'Expected exception, got %s',
168 65
            Utils::getVariableType($e)
169
        );
170
171 65
        $internalErrorMessage = $internalErrorMessage ?: self::$internalErrorMessage;
172
173 65
        if ($e instanceof ClientAware) {
174
            $formattedError = [
175 65
                'message'  => $e->isClientSafe() ? $e->getMessage() : $internalErrorMessage,
0 ignored issues
show
Bug introduced by
The method getMessage() does not exist on GraphQL\Error\ClientAware. Since it exists in all sub-types, consider adding an abstract or default implementation to GraphQL\Error\ClientAware. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

175
                'message'  => $e->isClientSafe() ? $e->/** @scrutinizer ignore-call */ getMessage() : $internalErrorMessage,
Loading history...
176 65
                'category' => $e->getCategory(),
177
            ];
178
        } else {
179
            $formattedError = [
180
                'message'  => $internalErrorMessage,
181
                'category' => Error::CATEGORY_INTERNAL,
182
            ];
183
        }
184
185 65
        if ($e instanceof Error) {
186 65
            if ($e->getExtensions()) {
187
                $formattedError = array_merge($e->getExtensions(), $formattedError);
188
            }
189
190 65
            $locations = Utils::map(
191 65
                $e->getLocations(),
192
                function (SourceLocation $loc) {
193 58
                    return $loc->toSerializableArray();
194 65
                }
195
            );
196
197 65
            if (! empty($locations)) {
198 58
                $formattedError['locations'] = $locations;
199
            }
200 65
            if (! empty($e->path)) {
201 44
                $formattedError['path'] = $e->path;
202
            }
203
        }
204
205 65
        if ($debug) {
206
            $formattedError = self::addDebugEntries($formattedError, $e, $debug);
0 ignored issues
show
Bug introduced by
It seems like $debug can also be of type integer; however, parameter $debug of GraphQL\Error\FormattedError::addDebugEntries() does only seem to accept boolean, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

206
            $formattedError = self::addDebugEntries($formattedError, $e, /** @scrutinizer ignore-type */ $debug);
Loading history...
207
        }
208
209 65
        return $formattedError;
210
    }
211
212
    /**
213
     * Decorates spec-compliant $formattedError with debug entries according to $debug flags
214
     * (see GraphQL\Error\Debug for available flags)
215
     *
216
     * @param mixed[]    $formattedError
217
     * @param \Throwable $e
218
     * @param bool       $debug
219
     * @return mixed[]
220
     * @throws \Throwable
221
     */
222 26
    public static function addDebugEntries(array $formattedError, $e, $debug)
223
    {
224 26
        if (! $debug) {
225
            return $formattedError;
226
        }
227
228 26
        Utils::invariant(
229 26
            $e instanceof \Exception || $e instanceof \Throwable,
230 26
            'Expected exception, got %s',
231 26
            Utils::getVariableType($e)
232
        );
233
234 26
        $debug = (int) $debug;
235
236 26
        if ($debug & Debug::RETHROW_INTERNAL_EXCEPTIONS) {
237
            if (! $e instanceof Error) {
238
                throw $e;
239
            }
240
241
            if ($e->getPrevious()) {
242
                throw $e->getPrevious();
243
            }
244
        }
245
246 26
        $isInternal = ! $e instanceof ClientAware || ! $e->isClientSafe();
247
248 26
        if (($debug & Debug::INCLUDE_DEBUG_MESSAGE) && $isInternal) {
249
            // Displaying debugMessage as a first entry:
250 21
            $formattedError = ['debugMessage' => $e->getMessage()] + $formattedError;
251
        }
252
253 26
        if ($debug & Debug::INCLUDE_TRACE) {
254 2
            if ($e instanceof \ErrorException || $e instanceof \Error) {
255
                $formattedError += [
256
                    'file' => $e->getFile(),
257
                    'line' => $e->getLine(),
258
                ];
259
            }
260
261 2
            $isTrivial = $e instanceof Error && ! $e->getPrevious();
262
263 2
            if (! $isTrivial) {
264 2
                $debugging               = $e->getPrevious() ?: $e;
265 2
                $formattedError['trace'] = static::toSafeTrace($debugging);
266
            }
267
        }
268
269 26
        return $formattedError;
270
    }
271
272
    /**
273
     * Prepares final error formatter taking in account $debug flags.
274
     * If initial formatter is not set, FormattedError::createFromException is used
275
     *
276
     * @param  bool $debug
277
     * @return callable|\Closure
278
     */
279 67
    public static function prepareFormatter(?callable $formatter = null, $debug)
280
    {
281
        $formatter = $formatter ?: function ($e) {
282 65
            return FormattedError::createFromException($e);
283 67
        };
284 67
        if ($debug) {
285
            $formatter = function ($e) use ($formatter, $debug) {
286 26
                return FormattedError::addDebugEntries($formatter($e), $e, $debug);
287 26
            };
288
        }
289
290 67
        return $formatter;
291
    }
292
293
    /**
294
     * Returns error trace as serializable array
295
     *
296
     * @api
297
     * @param \Throwable $error
298
     * @return mixed[]
299
     */
300 2
    public static function toSafeTrace($error)
301
    {
302 2
        $trace = $error->getTrace();
303
304 2
        if (isset($trace[0]['function']) && isset($trace[0]['class']) &&
305
            // Remove invariant entries as they don't provide much value:
306 2
            ($trace[0]['class'] . '::' . $trace[0]['function'] === 'GraphQL\Utils\Utils::invariant')) {
307
            array_shift($trace);
308 2
        } elseif (! isset($trace[0]['file'])) {
309
            // Remove root call as it's likely error handler trace:
310
            array_shift($trace);
311
        }
312
313
        return array_map(function ($err) {
314 2
            $safeErr = array_intersect_key($err, ['file' => true, 'line' => true]);
315
316 2
            if (isset($err['function'])) {
317 2
                $func    = $err['function'];
318 2
                $args    = ! empty($err['args']) ? array_map([__CLASS__, 'printVar'], $err['args']) : [];
319 2
                $funcStr = $func . '(' . implode(', ', $args) . ')';
320
321 2
                if (isset($err['class'])) {
322 2
                    $safeErr['call'] = $err['class'] . '::' . $funcStr;
323
                } else {
324
                    $safeErr['function'] = $funcStr;
325
                }
326
            }
327
328 2
            return $safeErr;
329 2
        },
330 2
            $trace);
331
    }
332
333
    /**
334
     * @param mixed $var
335
     * @return string
336
     */
337 2
    public static function printVar($var)
338
    {
339 2
        if ($var instanceof Type) {
340
            // FIXME: Replace with schema printer call
341 2
            if ($var instanceof WrappingType) {
342
                $var = $var->getWrappedType(true);
343
            }
344
345 2
            return 'GraphQLType: ' . $var->name;
346
        }
347
348 2
        if (is_object($var)) {
349 2
            return 'instance of ' . get_class($var) . ($var instanceof \Countable ? '(' . count($var) . ')' : '');
350
        }
351 2
        if (is_array($var)) {
352 2
            return 'array(' . count($var) . ')';
353
        }
354 2
        if ($var === '') {
355
            return '(empty string)';
356
        }
357 2
        if (is_string($var)) {
358 2
            return "'" . addcslashes($var, "'") . "'";
359
        }
360 2
        if (is_bool($var)) {
361 2
            return $var ? 'true' : 'false';
362
        }
363 2
        if (is_scalar($var)) {
364
            return $var;
365
        }
366 2
        if ($var === null) {
367 2
            return 'null';
368
        }
369
370
        return gettype($var);
371
    }
372
373
    /**
374
     * @deprecated as of v0.8.0
375
     * @param string           $error
376
     * @param SourceLocation[] $locations
377
     * @return mixed[]
378
     */
379
    public static function create($error, array $locations = [])
380
    {
381
        $formatted = ['message' => $error];
382
383
        if (! empty($locations)) {
384
            $formatted['locations'] = array_map(function ($loc) {
385
                return $loc->toArray();
386
            },
387
                $locations);
388
        }
389
390
        return $formatted;
391
    }
392
393
    /**
394
     * @deprecated as of v0.10.0, use general purpose method createFromException() instead
395
     * @return mixed[]
396
     */
397
    public static function createFromPHPError(\ErrorException $e)
398
    {
399
        return [
400
            'message'  => $e->getMessage(),
401
            'severity' => $e->getSeverity(),
402
            'trace'    => self::toSafeTrace($e),
403
        ];
404
    }
405
}
406