Passed
Push — master ( 7cc72b...baad32 )
by Vladimir
10:39
created

Error::getPath()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 0
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQL\Error;
6
7
use Exception;
8
use GraphQL\Language\AST\Node;
9
use GraphQL\Language\Source;
10
use GraphQL\Language\SourceLocation;
11
use GraphQL\Utils\Utils;
12
use JsonSerializable;
13
use Throwable;
14
use Traversable;
15
use function array_filter;
16
use function array_map;
17
use function array_values;
18
use function is_array;
19
use function iterator_to_array;
20
21
/**
22
 * Describes an Error found during the parse, validate, or
23
 * execute phases of performing a GraphQL operation. In addition to a message
24
 * and stack trace, it also includes information about the locations in a
25
 * GraphQL document and/or execution result that correspond to the Error.
26
 *
27
 * When the error was caused by an exception thrown in resolver, original exception
28
 * is available via `getPrevious()`.
29
 *
30
 * Also read related docs on [error handling](error-handling.md)
31
 *
32
 * Class extends standard PHP `\Exception`, so all standard methods of base `\Exception` class
33
 * are available in addition to those listed below.
34
 */
35
class Error extends Exception implements JsonSerializable, ClientAware
36
{
37
    const CATEGORY_GRAPHQL  = 'graphql';
38
    const CATEGORY_INTERNAL = 'internal';
39
40
    /**
41
     * A message describing the Error for debugging purposes.
42
     *
43
     * @var string
44
     */
45
    public $message;
46
47
    /** @var SourceLocation[] */
48
    private $locations;
49
50
    /**
51
     * An array describing the JSON-path into the execution response which
52
     * corresponds to this error. Only included for errors during execution.
53
     *
54
     * @var mixed[]|null
55
     */
56
    public $path;
57
58
    /**
59
     * An array of GraphQL AST Nodes corresponding to this error.
60
     *
61
     * @var Node[]|null
62
     */
63
    public $nodes;
64
65
    /**
66
     * The source GraphQL document for the first location of this error.
67
     *
68
     * Note that if this Error represents more than one node, the source may not
69
     * represent nodes after the first node.
70
     *
71
     * @var Source|null
72
     */
73
    private $source;
74
75
    /** @var int[]|null */
76
    private $positions;
77
78
    /** @var bool */
79
    private $isClientSafe;
80
81
    /** @var string */
82
    protected $category;
83
84
    /** @var mixed[]|null */
85
    protected $extensions;
86
87
    /**
88
     * @param string                       $message
89
     * @param Node|Node[]|Traversable|null $nodes
90
     * @param mixed[]|null                 $positions
91
     * @param mixed[]|null                 $path
92
     * @param Throwable                    $previous
93
     * @param mixed[]                      $extensions
94
     */
95 551
    public function __construct(
96
        $message,
97
        $nodes = null,
98
        ?Source $source = null,
99
        $positions = null,
100
        $path = null,
101
        $previous = null,
102
        array $extensions = []
103
    ) {
104 551
        parent::__construct($message, 0, $previous);
105
106
        // Compute list of blame nodes.
107 551
        if ($nodes instanceof Traversable) {
108 49
            $nodes = iterator_to_array($nodes);
109 551
        } elseif ($nodes && ! is_array($nodes)) {
110 70
            $nodes = [$nodes];
111
        }
112
113 551
        $this->nodes      = $nodes;
0 ignored issues
show
Documentation Bug introduced by
It seems like $nodes can also be of type GraphQL\Language\AST\Node. However, the property $nodes is declared as type GraphQL\Language\AST\Node[]|null. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
114 551
        $this->source     = $source;
115 551
        $this->positions  = $positions;
116 551
        $this->path       = $path;
117 551
        $this->extensions = $extensions ?: (
118 550
        $previous && $previous instanceof self
119 41
            ? $previous->extensions
120 550
            : []
121
        );
122
123 551
        if ($previous instanceof ClientAware) {
124 71
            $this->isClientSafe = $previous->isClientSafe();
125 71
            $this->category     = $previous->getCategory() ?: self::CATEGORY_INTERNAL;
126 528
        } elseif ($previous) {
127 28
            $this->isClientSafe = false;
128 28
            $this->category     = self::CATEGORY_INTERNAL;
129
        } else {
130 504
            $this->isClientSafe = true;
131 504
            $this->category     = self::CATEGORY_GRAPHQL;
132
        }
133 551
    }
134
135
    /**
136
     * Given an arbitrary Error, presumably thrown while attempting to execute a
137
     * GraphQL operation, produce a new GraphQLError aware of the location in the
138
     * document responsible for the original Error.
139
     *
140
     * @param mixed        $error
141
     * @param Node[]|null  $nodes
142
     * @param mixed[]|null $path
143
     *
144
     * @return Error
145
     */
146 61
    public static function createLocatedError($error, $nodes = null, $path = null)
147
    {
148 61
        if ($error instanceof self) {
149 28
            if ($error->path && $error->nodes) {
150 18
                return $error;
151
            }
152
153 10
            $nodes = $nodes ?: $error->nodes;
154 10
            $path  = $path ?: $error->path;
155
        }
156
157 61
        $source     = $positions = $originalError = null;
158 61
        $extensions = [];
159
160 61
        if ($error instanceof self) {
161 10
            $message       = $error->getMessage();
162 10
            $originalError = $error;
163 10
            $nodes         = $error->nodes ?: $nodes;
164 10
            $source        = $error->source;
165 10
            $positions     = $error->positions;
166 10
            $extensions    = $error->extensions;
167 53
        } elseif ($error instanceof Throwable) {
168 52
            $message       = $error->getMessage();
169 52
            $originalError = $error;
170
        } else {
171 1
            $message = (string) $error;
172
        }
173
174 61
        return new static(
175 61
            $message ?: 'An unknown error occurred.',
176 61
            $nodes,
177 61
            $source,
178 61
            $positions,
179 61
            $path,
180 61
            $originalError,
181 61
            $extensions
0 ignored issues
show
Bug introduced by
It seems like $extensions can also be of type null; however, parameter $extensions of GraphQL\Error\Error::__construct() does only seem to accept 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

181
            /** @scrutinizer ignore-type */ $extensions
Loading history...
182
        );
183
    }
184
185
    /**
186
     * @return mixed[]
187
     */
188 202
    public static function formatError(Error $error)
189
    {
190 202
        return $error->toSerializableArray();
0 ignored issues
show
Deprecated Code introduced by
The function GraphQL\Error\Error::toSerializableArray() has been deprecated: Use FormattedError::createFromException() instead ( Ignorable by Annotation )

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

190
        return /** @scrutinizer ignore-deprecated */ $error->toSerializableArray();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
191
    }
192
193
    /**
194
     * @inheritdoc
195
     */
196 104
    public function isClientSafe()
197
    {
198 104
        return $this->isClientSafe;
199
    }
200
201
    /**
202
     * @inheritdoc
203
     */
204 103
    public function getCategory()
205
    {
206 103
        return $this->category;
207
    }
208
209
    /**
210
     * @return Source|null
211
     */
212 417
    public function getSource()
213
    {
214 417
        if ($this->source === null) {
215 350
            if (! empty($this->nodes[0]) && ! empty($this->nodes[0]->loc)) {
216 295
                $this->source = $this->nodes[0]->loc->source;
217
            }
218
        }
219
220 417
        return $this->source;
221
    }
222
223
    /**
224
     * @return int[]
225
     */
226 416
    public function getPositions()
227
    {
228 416
        if ($this->positions === null && ! empty($this->nodes)) {
229 303
            $positions = array_map(
230
                static function ($node) {
231 303
                    return isset($node->loc) ? $node->loc->start : null;
232 303
                },
233 303
                $this->nodes
234
            );
235
236 303
            $positions = array_filter(
237 303
                $positions,
238
                static function ($p) {
239 303
                    return $p !== null;
240 303
                }
241
            );
242
243 303
            $this->positions = array_values($positions);
244
        }
245
246 416
        return $this->positions;
247
    }
248
249
    /**
250
     * An array of locations within the source GraphQL document which correspond to this error.
251
     *
252
     * Each entry has information about `line` and `column` within source GraphQL document:
253
     * $location->line;
254
     * $location->column;
255
     *
256
     * Errors during validation often contain multiple locations, for example to
257
     * point out to field mentioned in multiple fragments. Errors during execution include a
258
     * single location, the field which produced the error.
259
     *
260
     * @return SourceLocation[]
261
     *
262
     * @api
263
     */
264 406
    public function getLocations()
265
    {
266 406
        if ($this->locations === null) {
267 406
            $positions = $this->getPositions();
268 406
            $source    = $this->getSource();
269 406
            $nodes     = $this->nodes;
270
271 406
            if ($positions && $source) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $positions of type integer[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
272 360
                $this->locations = array_map(
273
                    static function ($pos) use ($source) {
274 360
                        return $source->getLocation($pos);
275 360
                    },
276 360
                    $positions
277
                );
278 49
            } elseif ($nodes) {
279
                $locations       = array_filter(
280
                    array_map(
281
                        static function ($node) {
282
                            if ($node->loc && $node->loc->source) {
283
                                return $node->loc->source->getLocation($node->loc->start);
284
                            }
285
                        },
286
                        $nodes
287
                    )
288
                );
289
                $this->locations = array_values($locations);
290
            } else {
291 49
                $this->locations = [];
292
            }
293
        }
294
295 406
        return $this->locations;
296
    }
297
298
    /**
299
     * @return Node[]|null
300
     */
301 14
    public function getNodes()
302
    {
303 14
        return $this->nodes;
304
    }
305
306
    /**
307
     * Returns an array describing the path from the root value to the field which produced this error.
308
     * Only included for execution errors.
309
     *
310
     * @return mixed[]|null
311
     *
312
     * @api
313
     */
314 15
    public function getPath()
315
    {
316 15
        return $this->path;
317
    }
318
319
    /**
320
     * @return mixed[]
321
     */
322 85
    public function getExtensions()
323
    {
324 85
        return $this->extensions;
325
    }
326
327
    /**
328
     * Returns array representation of error suitable for serialization
329
     *
330
     * @deprecated Use FormattedError::createFromException() instead
331
     *
332
     * @return mixed[]
333
     *
334
     * @codeCoverageIgnore
335
     */
336
    public function toSerializableArray()
337
    {
338
        $arr = [
339
            'message' => $this->getMessage(),
340
        ];
341
342
        $locations = Utils::map(
343
            $this->getLocations(),
344
            static function (SourceLocation $loc) {
345
                return $loc->toSerializableArray();
346
            }
347
        );
348
349
        if (! empty($locations)) {
350
            $arr['locations'] = $locations;
351
        }
352
        if (! empty($this->path)) {
353
            $arr['path'] = $this->path;
354
        }
355
        if (! empty($this->extensions)) {
356
            $arr['extensions'] = $this->extensions;
357
        }
358
359
        return $arr;
360
    }
361
362
    /**
363
     * Specify data which should be serialized to JSON
364
     *
365
     * @link http://php.net/manual/en/jsonserializable.jsonserialize.php
366
     *
367
     * @return mixed data which can be serialized by <b>json_encode</b>,
368
     * which is a value of any type other than a resource.
369
     */
370
    public function jsonSerialize()
371
    {
372
        return $this->toSerializableArray();
0 ignored issues
show
Deprecated Code introduced by
The function GraphQL\Error\Error::toSerializableArray() has been deprecated: Use FormattedError::createFromException() instead ( Ignorable by Annotation )

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

372
        return /** @scrutinizer ignore-deprecated */ $this->toSerializableArray();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
373
    }
374
375
    /**
376
     * @return string
377
     */
378 10
    public function __toString()
379
    {
380 10
        return FormattedError::printError($this);
381
    }
382
}
383