1 | <?php |
||||||
2 | |||||||
3 | declare(strict_types=1); |
||||||
4 | |||||||
5 | namespace GraphQL\Utils; |
||||||
6 | |||||||
7 | use ArrayAccess; |
||||||
8 | use Exception; |
||||||
9 | use GraphQL\Error\Error; |
||||||
10 | use GraphQL\Error\InvariantViolation; |
||||||
11 | use GraphQL\Language\AST\BooleanValueNode; |
||||||
12 | use GraphQL\Language\AST\DocumentNode; |
||||||
13 | use GraphQL\Language\AST\EnumValueNode; |
||||||
14 | use GraphQL\Language\AST\FloatValueNode; |
||||||
15 | use GraphQL\Language\AST\IntValueNode; |
||||||
16 | use GraphQL\Language\AST\ListTypeNode; |
||||||
17 | use GraphQL\Language\AST\ListValueNode; |
||||||
18 | use GraphQL\Language\AST\Location; |
||||||
19 | use GraphQL\Language\AST\NamedTypeNode; |
||||||
20 | use GraphQL\Language\AST\NameNode; |
||||||
21 | use GraphQL\Language\AST\Node; |
||||||
22 | use GraphQL\Language\AST\NodeKind; |
||||||
23 | use GraphQL\Language\AST\NodeList; |
||||||
24 | use GraphQL\Language\AST\NonNullTypeNode; |
||||||
25 | use GraphQL\Language\AST\NullValueNode; |
||||||
26 | use GraphQL\Language\AST\ObjectFieldNode; |
||||||
27 | use GraphQL\Language\AST\ObjectValueNode; |
||||||
28 | use GraphQL\Language\AST\OperationDefinitionNode; |
||||||
29 | use GraphQL\Language\AST\StringValueNode; |
||||||
30 | use GraphQL\Language\AST\ValueNode; |
||||||
31 | use GraphQL\Language\AST\VariableNode; |
||||||
32 | use GraphQL\Type\Definition\EnumType; |
||||||
33 | use GraphQL\Type\Definition\IDType; |
||||||
34 | use GraphQL\Type\Definition\InputObjectType; |
||||||
35 | use GraphQL\Type\Definition\InputType; |
||||||
36 | use GraphQL\Type\Definition\ListOfType; |
||||||
37 | use GraphQL\Type\Definition\NonNull; |
||||||
38 | use GraphQL\Type\Definition\ScalarType; |
||||||
39 | use GraphQL\Type\Definition\Type; |
||||||
40 | use GraphQL\Type\Schema; |
||||||
41 | use stdClass; |
||||||
42 | use Throwable; |
||||||
43 | use Traversable; |
||||||
44 | use function array_combine; |
||||||
45 | use function array_key_exists; |
||||||
46 | use function array_map; |
||||||
47 | use function count; |
||||||
48 | use function floatval; |
||||||
49 | use function intval; |
||||||
50 | use function is_array; |
||||||
51 | use function is_bool; |
||||||
52 | use function is_float; |
||||||
53 | use function is_int; |
||||||
54 | use function is_object; |
||||||
55 | use function is_string; |
||||||
56 | use function iterator_to_array; |
||||||
57 | use function property_exists; |
||||||
58 | |||||||
59 | /** |
||||||
60 | * Various utilities dealing with AST |
||||||
61 | */ |
||||||
62 | class AST |
||||||
63 | { |
||||||
64 | /** |
||||||
65 | * Convert representation of AST as an associative array to instance of GraphQL\Language\AST\Node. |
||||||
66 | * |
||||||
67 | * For example: |
||||||
68 | * |
||||||
69 | * ```php |
||||||
70 | * AST::fromArray([ |
||||||
71 | * 'kind' => 'ListValue', |
||||||
72 | * 'values' => [ |
||||||
73 | * ['kind' => 'StringValue', 'value' => 'my str'], |
||||||
74 | * ['kind' => 'StringValue', 'value' => 'my other str'] |
||||||
75 | * ], |
||||||
76 | * 'loc' => ['start' => 21, 'end' => 25] |
||||||
77 | * ]); |
||||||
78 | * ``` |
||||||
79 | * |
||||||
80 | * Will produce instance of `ListValueNode` where `values` prop is a lazily-evaluated `NodeList` |
||||||
81 | * returning instances of `StringValueNode` on access. |
||||||
82 | * |
||||||
83 | * This is a reverse operation for AST::toArray($node) |
||||||
84 | * |
||||||
85 | * @param mixed[] $node |
||||||
86 | * |
||||||
87 | * @api |
||||||
88 | */ |
||||||
89 | 2 | public static function fromArray(array $node) : Node |
|||||
90 | { |
||||||
91 | 2 | if (! isset($node['kind']) || ! isset(NodeKind::$classMap[$node['kind']])) { |
|||||
92 | throw new InvariantViolation('Unexpected node structure: ' . Utils::printSafeJson($node)); |
||||||
93 | } |
||||||
94 | |||||||
95 | 2 | $kind = $node['kind'] ?? null; |
|||||
96 | 2 | $class = NodeKind::$classMap[$kind]; |
|||||
97 | 2 | $instance = new $class([]); |
|||||
98 | |||||||
99 | 2 | if (isset($node['loc'], $node['loc']['start'], $node['loc']['end'])) { |
|||||
100 | 1 | $instance->loc = Location::create($node['loc']['start'], $node['loc']['end']); |
|||||
101 | } |
||||||
102 | |||||||
103 | 2 | foreach ($node as $key => $value) { |
|||||
104 | 2 | if ($key === 'loc' || $key === 'kind') { |
|||||
105 | 2 | continue; |
|||||
106 | } |
||||||
107 | 2 | if (is_array($value)) { |
|||||
108 | 2 | if (isset($value[0]) || empty($value)) { |
|||||
109 | 2 | $value = new NodeList($value); |
|||||
110 | } else { |
||||||
111 | 2 | $value = self::fromArray($value); |
|||||
112 | } |
||||||
113 | } |
||||||
114 | 2 | $instance->{$key} = $value; |
|||||
115 | } |
||||||
116 | |||||||
117 | 2 | return $instance; |
|||||
118 | } |
||||||
119 | |||||||
120 | /** |
||||||
121 | * Convert AST node to serializable array |
||||||
122 | * |
||||||
123 | * @return mixed[] |
||||||
124 | * |
||||||
125 | * @api |
||||||
126 | */ |
||||||
127 | public static function toArray(Node $node) |
||||||
128 | { |
||||||
129 | return $node->toArray(true); |
||||||
130 | } |
||||||
131 | |||||||
132 | /** |
||||||
133 | * Produces a GraphQL Value AST given a PHP value. |
||||||
134 | * |
||||||
135 | * Optionally, a GraphQL type may be provided, which will be used to |
||||||
136 | * disambiguate between value primitives. |
||||||
137 | * |
||||||
138 | * | PHP Value | GraphQL Value | |
||||||
139 | * | ------------- | -------------------- | |
||||||
140 | * | Object | Input Object | |
||||||
141 | * | Assoc Array | Input Object | |
||||||
142 | * | Array | List | |
||||||
143 | * | Boolean | Boolean | |
||||||
144 | * | String | String / Enum Value | |
||||||
145 | * | Int | Int | |
||||||
146 | * | Float | Int / Float | |
||||||
147 | * | Mixed | Enum Value | |
||||||
148 | * | null | NullValue | |
||||||
149 | * |
||||||
150 | * @param Type|mixed|null $value |
||||||
151 | * |
||||||
152 | * @return ObjectValueNode|ListValueNode|BooleanValueNode|IntValueNode|FloatValueNode|EnumValueNode|StringValueNode|NullValueNode|null |
||||||
153 | * |
||||||
154 | * @api |
||||||
155 | */ |
||||||
156 | 28 | public static function astFromValue($value, InputType $type) |
|||||
157 | { |
||||||
158 | 28 | if ($type instanceof NonNull) { |
|||||
159 | 4 | $astValue = self::astFromValue($value, $type->getWrappedType()); |
|||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||||
160 | 4 | if ($astValue instanceof NullValueNode) { |
|||||
161 | 4 | return null; |
|||||
162 | } |
||||||
163 | |||||||
164 | 1 | return $astValue; |
|||||
165 | } |
||||||
166 | |||||||
167 | 28 | if ($value === null) { |
|||||
168 | 7 | return new NullValueNode([]); |
|||||
169 | } |
||||||
170 | |||||||
171 | // Convert PHP array to GraphQL list. If the GraphQLType is a list, but |
||||||
172 | // the value is not an array, convert the value using the list's item type. |
||||||
173 | 26 | if ($type instanceof ListOfType) { |
|||||
174 | 2 | $itemType = $type->getWrappedType(); |
|||||
175 | 2 | if (is_array($value) || ($value instanceof Traversable)) { |
|||||
176 | 1 | $valuesNodes = []; |
|||||
177 | 1 | foreach ($value as $item) { |
|||||
178 | 1 | $itemNode = self::astFromValue($item, $itemType); |
|||||
179 | 1 | if (! $itemNode) { |
|||||
180 | continue; |
||||||
181 | } |
||||||
182 | |||||||
183 | 1 | $valuesNodes[] = $itemNode; |
|||||
184 | } |
||||||
185 | |||||||
186 | 1 | return new ListValueNode(['values' => new NodeList($valuesNodes)]); |
|||||
187 | } |
||||||
188 | |||||||
189 | 1 | return self::astFromValue($value, $itemType); |
|||||
190 | } |
||||||
191 | |||||||
192 | // Populate the fields of the input object by creating ASTs from each value |
||||||
193 | // in the PHP object according to the fields in the input type. |
||||||
194 | 26 | if ($type instanceof InputObjectType) { |
|||||
195 | 2 | $isArray = is_array($value); |
|||||
196 | 2 | $isArrayLike = $isArray || $value instanceof ArrayAccess; |
|||||
197 | 2 | if ($value === null || (! $isArrayLike && ! is_object($value))) { |
|||||
198 | return null; |
||||||
199 | } |
||||||
200 | 2 | $fields = $type->getFields(); |
|||||
201 | 2 | $fieldNodes = []; |
|||||
202 | 2 | foreach ($fields as $fieldName => $field) { |
|||||
203 | 2 | if ($isArrayLike) { |
|||||
204 | 2 | $fieldValue = $value[$fieldName] ?? null; |
|||||
205 | } else { |
||||||
206 | 1 | $fieldValue = $value->{$fieldName} ?? null; |
|||||
207 | } |
||||||
208 | |||||||
209 | // Have to check additionally if key exists, since we differentiate between |
||||||
210 | // "no key" and "value is null": |
||||||
211 | 2 | if ($fieldValue !== null) { |
|||||
212 | 1 | $fieldExists = true; |
|||||
213 | 1 | } elseif ($isArray) { |
|||||
214 | 1 | $fieldExists = array_key_exists($fieldName, $value); |
|||||
0 ignored issues
–
show
It seems like
$value can also be of type ArrayAccess and GraphQL\Type\Definition\Type and GraphQL\Type\Definition\Type&ArrayAccess ; however, parameter $search of array_key_exists() 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
![]() |
|||||||
215 | } elseif ($isArrayLike) { |
||||||
216 | $fieldExists = $value->offsetExists($fieldName); |
||||||
0 ignored issues
–
show
The method
offsetExists() does not exist on GraphQL\Type\Definition\Type .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
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. ![]() |
|||||||
217 | } else { |
||||||
218 | $fieldExists = property_exists($value, $fieldName); |
||||||
219 | } |
||||||
220 | |||||||
221 | 2 | if (! $fieldExists) { |
|||||
222 | 1 | continue; |
|||||
223 | } |
||||||
224 | |||||||
225 | 2 | $fieldNode = self::astFromValue($fieldValue, $field->getType()); |
|||||
226 | |||||||
227 | 2 | if (! $fieldNode) { |
|||||
228 | continue; |
||||||
229 | } |
||||||
230 | |||||||
231 | 2 | $fieldNodes[] = new ObjectFieldNode([ |
|||||
232 | 2 | 'name' => new NameNode(['value' => $fieldName]), |
|||||
233 | 2 | 'value' => $fieldNode, |
|||||
234 | ]); |
||||||
235 | } |
||||||
236 | |||||||
237 | 2 | return new ObjectValueNode(['fields' => new NodeList($fieldNodes)]); |
|||||
238 | } |
||||||
239 | |||||||
240 | 25 | if ($type instanceof ScalarType || $type instanceof EnumType) { |
|||||
241 | // Since value is an internally represented value, it must be serialized |
||||||
242 | // to an externally represented value before converting into an AST. |
||||||
243 | try { |
||||||
244 | 25 | $serialized = $type->serialize($value); |
|||||
245 | 3 | } catch (Throwable $error) { |
|||||
246 | 3 | if ($error instanceof Error && $type instanceof EnumType) { |
|||||
247 | 1 | return null; |
|||||
248 | } |
||||||
249 | 2 | throw $error; |
|||||
250 | } |
||||||
251 | |||||||
252 | // Others serialize based on their corresponding PHP scalar types. |
||||||
253 | 23 | if (is_bool($serialized)) { |
|||||
254 | 7 | return new BooleanValueNode(['value' => $serialized]); |
|||||
255 | } |
||||||
256 | 21 | if (is_int($serialized)) { |
|||||
257 | 5 | return new IntValueNode(['value' => $serialized]); |
|||||
258 | } |
||||||
259 | 16 | if (is_float($serialized)) { |
|||||
260 | // int cast with == used for performance reasons |
||||||
261 | // phpcs:ignore |
||||||
262 | 2 | if ((int) $serialized == $serialized) { |
|||||
263 | 2 | return new IntValueNode(['value' => $serialized]); |
|||||
264 | } |
||||||
265 | |||||||
266 | 1 | return new FloatValueNode(['value' => $serialized]); |
|||||
267 | } |
||||||
268 | 15 | if (is_string($serialized)) { |
|||||
269 | // Enum types use Enum literals. |
||||||
270 | 15 | if ($type instanceof EnumType) { |
|||||
271 | 4 | return new EnumValueNode(['value' => $serialized]); |
|||||
272 | } |
||||||
273 | |||||||
274 | // ID types can use Int literals. |
||||||
275 | 13 | $asInt = (int) $serialized; |
|||||
276 | 13 | if ($type instanceof IDType && (string) $asInt === $serialized) { |
|||||
277 | 1 | return new IntValueNode(['value' => $serialized]); |
|||||
278 | } |
||||||
279 | |||||||
280 | // Use json_encode, which uses the same string encoding as GraphQL, |
||||||
281 | // then remove the quotes. |
||||||
282 | 13 | return new StringValueNode(['value' => $serialized]); |
|||||
283 | } |
||||||
284 | |||||||
285 | throw new InvariantViolation('Cannot convert value to AST: ' . Utils::printSafe($serialized)); |
||||||
286 | } |
||||||
287 | |||||||
288 | throw new Error('Unknown type: ' . Utils::printSafe($type) . '.'); |
||||||
289 | } |
||||||
290 | |||||||
291 | /** |
||||||
292 | * Produces a PHP value given a GraphQL Value AST. |
||||||
293 | * |
||||||
294 | * A GraphQL type must be provided, which will be used to interpret different |
||||||
295 | * GraphQL Value literals. |
||||||
296 | * |
||||||
297 | * Returns `null` when the value could not be validly coerced according to |
||||||
298 | * the provided type. |
||||||
299 | * |
||||||
300 | * | GraphQL Value | PHP Value | |
||||||
301 | * | -------------------- | ------------- | |
||||||
302 | * | Input Object | Assoc Array | |
||||||
303 | * | List | Array | |
||||||
304 | * | Boolean | Boolean | |
||||||
305 | * | String | String | |
||||||
306 | * | Int / Float | Int / Float | |
||||||
307 | * | Enum Value | Mixed | |
||||||
308 | * | Null Value | null | |
||||||
309 | * |
||||||
310 | * @param VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode|null $valueNode |
||||||
311 | * @param mixed[]|null $variables |
||||||
312 | * |
||||||
313 | * @return mixed[]|stdClass|null |
||||||
314 | * |
||||||
315 | * @throws Exception |
||||||
316 | * |
||||||
317 | * @api |
||||||
318 | */ |
||||||
319 | 107 | public static function valueFromAST(?ValueNode $valueNode, Type $type, ?array $variables = null) |
|||||
320 | { |
||||||
321 | 107 | $undefined = Utils::undefined(); |
|||||
322 | |||||||
323 | 107 | if ($valueNode === null) { |
|||||
324 | // When there is no AST, then there is also no value. |
||||||
325 | // Importantly, this is different from returning the GraphQL null value. |
||||||
326 | 1 | return $undefined; |
|||||
327 | } |
||||||
328 | |||||||
329 | 106 | if ($type instanceof NonNull) { |
|||||
330 | 39 | if ($valueNode instanceof NullValueNode) { |
|||||
331 | // Invalid: intentionally return no value. |
||||||
332 | 5 | return $undefined; |
|||||
333 | } |
||||||
334 | |||||||
335 | 38 | return self::valueFromAST($valueNode, $type->getWrappedType(), $variables); |
|||||
336 | } |
||||||
337 | |||||||
338 | 106 | if ($valueNode instanceof NullValueNode) { |
|||||
339 | // This is explicitly returning the value null. |
||||||
340 | 9 | return null; |
|||||
341 | } |
||||||
342 | |||||||
343 | 104 | if ($valueNode instanceof VariableNode) { |
|||||
344 | 11 | $variableName = $valueNode->name->value; |
|||||
345 | |||||||
346 | 11 | if (! $variables || ! array_key_exists($variableName, $variables)) { |
|||||
347 | // No valid return value. |
||||||
348 | 1 | return $undefined; |
|||||
349 | } |
||||||
350 | |||||||
351 | 11 | $variableValue = $variables[$variableName] ?? null; |
|||||
352 | 11 | if ($variableValue === null && $type instanceof NonNull) { |
|||||
353 | return $undefined; // Invalid: intentionally return no value. |
||||||
354 | } |
||||||
355 | |||||||
356 | // Note: This does no further checking that this variable is correct. |
||||||
357 | // This assumes that this query has been validated and the variable |
||||||
358 | // usage here is of the correct type. |
||||||
359 | 11 | return $variables[$variableName]; |
|||||
360 | } |
||||||
361 | |||||||
362 | 95 | if ($type instanceof ListOfType) { |
|||||
363 | 7 | $itemType = $type->getWrappedType(); |
|||||
364 | |||||||
365 | 7 | if ($valueNode instanceof ListValueNode) { |
|||||
366 | 7 | $coercedValues = []; |
|||||
367 | 7 | $itemNodes = $valueNode->values; |
|||||
368 | 7 | foreach ($itemNodes as $itemNode) { |
|||||
369 | 7 | if (self::isMissingVariable($itemNode, $variables)) { |
|||||
0 ignored issues
–
show
$itemNode of type GraphQL\Language\AST\Node is incompatible with the type GraphQL\Language\AST\ValueNode expected by parameter $valueNode of GraphQL\Utils\AST::isMissingVariable() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
370 | // If an array contains a missing variable, it is either coerced to |
||||||
371 | // null or if the item type is non-null, it considered invalid. |
||||||
372 | 1 | if ($itemType instanceof NonNull) { |
|||||
373 | // Invalid: intentionally return no value. |
||||||
374 | 1 | return $undefined; |
|||||
375 | } |
||||||
376 | 1 | $coercedValues[] = null; |
|||||
377 | } else { |
||||||
378 | 7 | $itemValue = self::valueFromAST($itemNode, $itemType, $variables); |
|||||
0 ignored issues
–
show
$itemNode of type GraphQL\Language\AST\Node is incompatible with the type GraphQL\Language\AST\ValueNode|null expected by parameter $valueNode of GraphQL\Utils\AST::valueFromAST() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
379 | 7 | if ($undefined === $itemValue) { |
|||||
380 | // Invalid: intentionally return no value. |
||||||
381 | 4 | return $undefined; |
|||||
382 | } |
||||||
383 | 7 | $coercedValues[] = $itemValue; |
|||||
384 | } |
||||||
385 | } |
||||||
386 | |||||||
387 | 7 | return $coercedValues; |
|||||
388 | } |
||||||
389 | 5 | $coercedValue = self::valueFromAST($valueNode, $itemType, $variables); |
|||||
390 | 5 | if ($undefined === $coercedValue) { |
|||||
391 | // Invalid: intentionally return no value. |
||||||
392 | 4 | return $undefined; |
|||||
393 | } |
||||||
394 | |||||||
395 | 5 | return [$coercedValue]; |
|||||
396 | } |
||||||
397 | |||||||
398 | 94 | if ($type instanceof InputObjectType) { |
|||||
399 | 4 | if (! $valueNode instanceof ObjectValueNode) { |
|||||
400 | // Invalid: intentionally return no value. |
||||||
401 | 2 | return $undefined; |
|||||
402 | } |
||||||
403 | |||||||
404 | 4 | $coercedObj = []; |
|||||
405 | 4 | $fields = $type->getFields(); |
|||||
406 | 4 | $fieldNodes = Utils::keyMap( |
|||||
407 | 4 | $valueNode->fields, |
|||||
408 | static function ($field) { |
||||||
409 | 4 | return $field->name->value; |
|||||
410 | 4 | } |
|||||
411 | ); |
||||||
412 | 4 | foreach ($fields as $field) { |
|||||
413 | 4 | $fieldName = $field->name; |
|||||
414 | /** @var VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode $fieldNode */ |
||||||
415 | 4 | $fieldNode = $fieldNodes[$fieldName] ?? null; |
|||||
416 | |||||||
417 | 4 | if ($fieldNode === null || self::isMissingVariable($fieldNode->value, $variables)) { |
|||||
0 ignored issues
–
show
$fieldNode->value of type boolean|string is incompatible with the type GraphQL\Language\AST\ValueNode expected by parameter $valueNode of GraphQL\Utils\AST::isMissingVariable() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
418 | 4 | if ($field->defaultValueExists()) { |
|||||
419 | 2 | $coercedObj[$fieldName] = $field->defaultValue; |
|||||
420 | 4 | } elseif ($field->getType() instanceof NonNull) { |
|||||
421 | // Invalid: intentionally return no value. |
||||||
422 | 2 | return $undefined; |
|||||
423 | } |
||||||
424 | 4 | continue; |
|||||
425 | } |
||||||
426 | |||||||
427 | 4 | $fieldValue = self::valueFromAST( |
|||||
428 | 4 | $fieldNode !== null ? $fieldNode->value : null, |
|||||
0 ignored issues
–
show
It seems like
$fieldNode !== null ? $fieldNode->value : null can also be of type boolean and string ; however, parameter $valueNode of GraphQL\Utils\AST::valueFromAST() does only seem to accept GraphQL\Language\AST\ValueNode|null , 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
![]() |
|||||||
429 | 4 | $field->getType(), |
|||||
430 | 4 | $variables |
|||||
431 | ); |
||||||
432 | |||||||
433 | 4 | if ($undefined === $fieldValue) { |
|||||
434 | // Invalid: intentionally return no value. |
||||||
435 | 1 | return $undefined; |
|||||
436 | } |
||||||
437 | 4 | $coercedObj[$fieldName] = $fieldValue; |
|||||
438 | } |
||||||
439 | |||||||
440 | 4 | return $coercedObj; |
|||||
441 | } |
||||||
442 | |||||||
443 | 94 | if ($type instanceof EnumType) { |
|||||
444 | 9 | if (! $valueNode instanceof EnumValueNode) { |
|||||
445 | 1 | return $undefined; |
|||||
446 | } |
||||||
447 | 9 | $enumValue = $type->getValue($valueNode->value); |
|||||
448 | 9 | if (! $enumValue) { |
|||||
449 | return $undefined; |
||||||
450 | } |
||||||
451 | |||||||
452 | 9 | return $enumValue->value; |
|||||
453 | } |
||||||
454 | |||||||
455 | 87 | if ($type instanceof ScalarType) { |
|||||
456 | // Scalars fulfill parsing a literal value via parseLiteral(). |
||||||
457 | // Invalid values represent a failure to parse correctly, in which case |
||||||
458 | // no value is returned. |
||||||
459 | try { |
||||||
460 | 87 | return $type->parseLiteral($valueNode, $variables); |
|||||
461 | 8 | } catch (Throwable $error) { |
|||||
462 | 8 | return $undefined; |
|||||
463 | } |
||||||
464 | } |
||||||
465 | |||||||
466 | throw new Error('Unknown type: ' . Utils::printSafe($type) . '.'); |
||||||
467 | } |
||||||
468 | |||||||
469 | /** |
||||||
470 | * Returns true if the provided valueNode is a variable which is not defined |
||||||
471 | * in the set of variables. |
||||||
472 | * |
||||||
473 | * @param VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode $valueNode |
||||||
474 | * @param mixed[] $variables |
||||||
475 | * |
||||||
476 | * @return bool |
||||||
477 | */ |
||||||
478 | 9 | private static function isMissingVariable(ValueNode $valueNode, $variables) |
|||||
479 | { |
||||||
480 | 9 | return $valueNode instanceof VariableNode && |
|||||
481 | 9 | (count($variables) === 0 || ! array_key_exists($valueNode->name->value, $variables)); |
|||||
482 | } |
||||||
483 | |||||||
484 | /** |
||||||
485 | * Produces a PHP value given a GraphQL Value AST. |
||||||
486 | * |
||||||
487 | * Unlike `valueFromAST()`, no type is provided. The resulting PHP value |
||||||
488 | * will reflect the provided GraphQL value AST. |
||||||
489 | * |
||||||
490 | * | GraphQL Value | PHP Value | |
||||||
491 | * | -------------------- | ------------- | |
||||||
492 | * | Input Object | Assoc Array | |
||||||
493 | * | List | Array | |
||||||
494 | * | Boolean | Boolean | |
||||||
495 | * | String | String | |
||||||
496 | * | Int / Float | Int / Float | |
||||||
497 | * | Enum | Mixed | |
||||||
498 | * | Null | null | |
||||||
499 | * |
||||||
500 | * @param Node $valueNode |
||||||
501 | * @param mixed[]|null $variables |
||||||
502 | * |
||||||
503 | * @return mixed |
||||||
504 | * |
||||||
505 | * @throws Exception |
||||||
506 | * |
||||||
507 | * @api |
||||||
508 | */ |
||||||
509 | 6 | public static function valueFromASTUntyped($valueNode, ?array $variables = null) |
|||||
510 | { |
||||||
511 | switch (true) { |
||||||
512 | 6 | case $valueNode instanceof NullValueNode: |
|||||
513 | 2 | return null; |
|||||
514 | 6 | case $valueNode instanceof IntValueNode: |
|||||
515 | 3 | return intval($valueNode->value, 10); |
|||||
516 | 5 | case $valueNode instanceof FloatValueNode: |
|||||
517 | 2 | return floatval($valueNode->value); |
|||||
518 | 5 | case $valueNode instanceof StringValueNode: |
|||||
519 | 5 | case $valueNode instanceof EnumValueNode: |
|||||
520 | 5 | case $valueNode instanceof BooleanValueNode: |
|||||
521 | 4 | return $valueNode->value; |
|||||
522 | 4 | case $valueNode instanceof ListValueNode: |
|||||
523 | 4 | return array_map( |
|||||
524 | static function ($node) use ($variables) { |
||||||
525 | 4 | return self::valueFromASTUntyped($node, $variables); |
|||||
526 | 4 | }, |
|||||
527 | 4 | iterator_to_array($valueNode->values) |
|||||
528 | ); |
||||||
529 | 2 | case $valueNode instanceof ObjectValueNode: |
|||||
530 | 2 | return array_combine( |
|||||
531 | 2 | array_map( |
|||||
532 | static function ($field) { |
||||||
533 | 2 | return $field->name->value; |
|||||
534 | 2 | }, |
|||||
535 | 2 | iterator_to_array($valueNode->fields) |
|||||
536 | ), |
||||||
537 | 2 | array_map( |
|||||
538 | static function ($field) use ($variables) { |
||||||
539 | 2 | return self::valueFromASTUntyped($field->value, $variables); |
|||||
540 | 2 | }, |
|||||
541 | 2 | iterator_to_array($valueNode->fields) |
|||||
542 | ) |
||||||
543 | ); |
||||||
544 | 1 | case $valueNode instanceof VariableNode: |
|||||
545 | 1 | $variableName = $valueNode->name->value; |
|||||
546 | |||||||
547 | 1 | return $variables && isset($variables[$variableName]) |
|||||
548 | 1 | ? $variables[$variableName] |
|||||
549 | 1 | : null; |
|||||
550 | } |
||||||
551 | |||||||
552 | throw new Error('Unexpected value kind: ' . $valueNode->kind . '.'); |
||||||
553 | } |
||||||
554 | |||||||
555 | /** |
||||||
556 | * Returns type definition for given AST Type node |
||||||
557 | * |
||||||
558 | * @param NamedTypeNode|ListTypeNode|NonNullTypeNode $inputTypeNode |
||||||
559 | * |
||||||
560 | * @return Type|null |
||||||
561 | * |
||||||
562 | * @throws Exception |
||||||
563 | * |
||||||
564 | * @api |
||||||
565 | */ |
||||||
566 | 349 | public static function typeFromAST(Schema $schema, $inputTypeNode) |
|||||
567 | { |
||||||
568 | 349 | if ($inputTypeNode instanceof ListTypeNode) { |
|||||
569 | 19 | $innerType = self::typeFromAST($schema, $inputTypeNode->type); |
|||||
570 | |||||||
571 | 19 | return $innerType ? new ListOfType($innerType) : null; |
|||||
572 | } |
||||||
573 | 349 | if ($inputTypeNode instanceof NonNullTypeNode) { |
|||||
574 | 43 | $innerType = self::typeFromAST($schema, $inputTypeNode->type); |
|||||
575 | |||||||
576 | 43 | return $innerType ? new NonNull($innerType) : null; |
|||||
577 | } |
||||||
578 | 349 | if ($inputTypeNode instanceof NamedTypeNode) { |
|||||
0 ignored issues
–
show
|
|||||||
579 | 349 | return $schema->getType($inputTypeNode->name->value); |
|||||
580 | } |
||||||
581 | |||||||
582 | throw new Error('Unexpected type kind: ' . $inputTypeNode->kind . '.'); |
||||||
583 | } |
||||||
584 | |||||||
585 | /** |
||||||
586 | * Returns operation type ("query", "mutation" or "subscription") given a document and operation name |
||||||
587 | * |
||||||
588 | * @param string $operationName |
||||||
589 | * |
||||||
590 | * @return bool|string |
||||||
591 | * |
||||||
592 | * @api |
||||||
593 | */ |
||||||
594 | 23 | public static function getOperation(DocumentNode $document, $operationName = null) |
|||||
595 | { |
||||||
596 | 23 | if ($document->definitions) { |
|||||
597 | 23 | foreach ($document->definitions as $def) { |
|||||
598 | 23 | if (! ($def instanceof OperationDefinitionNode)) { |
|||||
599 | continue; |
||||||
600 | } |
||||||
601 | |||||||
602 | 23 | if (! $operationName || (isset($def->name->value) && $def->name->value === $operationName)) { |
|||||
603 | 23 | return $def->operation; |
|||||
604 | } |
||||||
605 | } |
||||||
606 | } |
||||||
607 | |||||||
608 | return false; |
||||||
609 | } |
||||||
610 | } |
||||||
611 |