webonyx /
graphql-php
| 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
Loading history...
|
|||||||
| 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
Loading history...
|
|||||||
| 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. Loading history...
|
|||||||
| 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
Loading history...
|
|||||||
| 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
Loading history...
|
|||||||
| 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
Loading history...
|
|||||||
| 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
Loading history...
|
|||||||
| 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 |