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.
Completed
Pull Request — master (#1)
by Šimon
05:05 queued 02:04
created

FormattedError::createFromPHPError()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 0
cts 0
cp 0
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 1
crap 2
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
                if ($node->loc->source === null) {
73
                    continue;
74
                }
75
76 1
                $printedLocations[] = self::highlightSourceAtLocation(
77 1
                    $node->loc->source,
78 1
                    $node->loc->source->getLocation($node->loc->start)
79
                );
80
            }
81 10
        } elseif ($error->getSource() && $error->getLocations()) {
82 9
            $source = $error->getSource();
83 9
            foreach ($error->getLocations() as $location) {
84 9
                $printedLocations[] = self::highlightSourceAtLocation($source, $location);
85
            }
86
        }
87
88 11
        return ! $printedLocations
89 1
            ? $error->getMessage()
90 11
            : implode("\n\n", array_merge([$error->getMessage()], $printedLocations)) . "\n";
91
    }
92
93
    /**
94
     * Render a helpful description of the location of the error in the GraphQL
95
     * Source document.
96
     *
97
     * @return string
98
     */
99 10
    private static function highlightSourceAtLocation(Source $source, SourceLocation $location)
100
    {
101 10
        $line          = $location->line;
102 10
        $lineOffset    = $source->locationOffset->line - 1;
103 10
        $columnOffset  = self::getColumnOffset($source, $location);
104 10
        $contextLine   = $line + $lineOffset;
105 10
        $contextColumn = $location->column + $columnOffset;
106 10
        $prevLineNum   = (string) ($contextLine - 1);
107 10
        $lineNum       = (string) $contextLine;
108 10
        $nextLineNum   = (string) ($contextLine + 1);
109 10
        $padLen        = strlen($nextLineNum);
110 10
        $lines         = preg_split('/\r\n|[\n\r]/', $source->body);
111
112 10
        $lines[0] = self::whitespace($source->locationOffset->column - 1) . $lines[0];
113
114
        $outputLines = [
115 10
            sprintf('%s (%s:%s)', $source->name, $contextLine, $contextColumn),
116 10
            $line >= 2 ? (self::lpad($padLen, $prevLineNum) . ': ' . $lines[$line - 2]) : null,
117 10
            self::lpad($padLen, $lineNum) . ': ' . $lines[$line - 1],
118 10
            self::whitespace(2 + $padLen + $contextColumn - 1) . '^',
119 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

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

179
                'message'  => $e->isClientSafe() ? $e->/** @scrutinizer ignore-call */ getMessage() : $internalErrorMessage,
Loading history...
180 65
                'category' => $e->getCategory(),
181
            ];
182
        } else {
183
            $formattedError = [
184
                'message'  => $internalErrorMessage,
185
                'category' => Error::CATEGORY_INTERNAL,
186
            ];
187
        }
188
189 65
        if ($e instanceof Error) {
190 65
            if ($e->getExtensions()) {
191
                $formattedError = array_merge($e->getExtensions(), $formattedError);
192
            }
193
194 65
            $locations = Utils::map(
195 65
                $e->getLocations(),
196
                function (SourceLocation $loc) {
197 58
                    return $loc->toSerializableArray();
198 65
                }
199
            );
200
201 65
            if (! empty($locations)) {
202 58
                $formattedError['locations'] = $locations;
203
            }
204 65
            if (! empty($e->path)) {
205 44
                $formattedError['path'] = $e->path;
206
            }
207
        }
208
209 65
        if ($debug) {
210
            $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

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