/** * Given a type and any value, return a runtime value coerced to match the type. */ private static function coerceValue(Type $type, $value) { $undefined = Utils::undefined(); if ($value === $undefined) { return $undefined; } if ($type instanceof NonNull) { if ($value === null) { // Intentionally return no value. return $undefined; } return self::coerceValue($type->getWrappedType(), $value); } if (null === $value) { return null; } if ($type instanceof ListOfType) { $itemType = $type->getWrappedType(); if (is_array($value) || $value instanceof \Traversable) { $coercedValues = []; foreach ($value as $item) { $itemValue = self::coerceValue($itemType, $item); if ($undefined === $itemValue) { // Intentionally return no value. return $undefined; } $coercedValues[] = $itemValue; } return $coercedValues; } else { $coercedValue = self::coerceValue($itemType, $value); if ($coercedValue === $undefined) { // Intentionally return no value. return $undefined; } return [$coercedValue]; } } if ($type instanceof InputObjectType) { $coercedObj = []; $fields = $type->getFields(); foreach ($fields as $fieldName => $field) { if (!array_key_exists($fieldName, $value)) { if ($field->defaultValueExists()) { $coercedObj[$fieldName] = $field->defaultValue; } else { if ($field->getType() instanceof NonNull) { // Intentionally return no value. return $undefined; } } continue; } $fieldValue = self::coerceValue($field->getType(), $value[$fieldName]); if ($fieldValue === $undefined) { // Intentionally return no value. return $undefined; } $coercedObj[$fieldName] = $fieldValue; } return $coercedObj; } if ($type instanceof LeafType) { $parsed = $type->parseValue($value); if (null === $parsed) { // null or invalid values represent a failure to parse correctly, // in which case no value is returned. return $undefined; } return $parsed; } throw new InvariantViolation('Must be input type'); }
/** * Produces a PHP value given a GraphQL Value AST. * * A GraphQL type must be provided, which will be used to interpret different * GraphQL Value literals. * * Returns `null` when the value could not be validly coerced according to * the provided type. * * | GraphQL Value | PHP Value | * | -------------------- | ------------- | * | Input Object | Assoc Array | * | List | Array | * | Boolean | Boolean | * | String | String | * | Int / Float | Int / Float | * | Enum Value | Mixed | * | Null Value | stdClass | instance of NullValue::getNullValue() * * @param $valueNode * @param InputType $type * @param null $variables * @return array|null|\stdClass * @throws \Exception */ public static function valueFromAST($valueNode, InputType $type, $variables = null) { $undefined = Utils::undefined(); if (!$valueNode) { // When there is no AST, then there is also no value. // Importantly, this is different from returning the GraphQL null value. return $undefined; } if ($type instanceof NonNull) { if ($valueNode instanceof NullValueNode) { // Invalid: intentionally return no value. return $undefined; } return self::valueFromAST($valueNode, $type->getWrappedType(), $variables); } if ($valueNode instanceof NullValueNode) { // This is explicitly returning the value null. return null; } if ($valueNode instanceof VariableNode) { $variableName = $valueNode->name->value; if (!$variables || !array_key_exists($variableName, $variables)) { // No valid return value. return $undefined; } // Note: we're not doing any checking that this variable is correct. We're // assuming that this query has been validated and the variable usage here // is of the correct type. return $variables[$variableName]; } if ($type instanceof ListOfType) { $itemType = $type->getWrappedType(); if ($valueNode instanceof ListValueNode) { $coercedValues = []; $itemNodes = $valueNode->values; foreach ($itemNodes as $itemNode) { if (self::isMissingVariable($itemNode, $variables)) { // If an array contains a missing variable, it is either coerced to // null or if the item type is non-null, it considered invalid. if ($itemType instanceof NonNull) { // Invalid: intentionally return no value. return $undefined; } $coercedValues[] = null; } else { $itemValue = self::valueFromAST($itemNode, $itemType, $variables); if ($undefined === $itemValue) { // Invalid: intentionally return no value. return $undefined; } $coercedValues[] = $itemValue; } } return $coercedValues; } $coercedValue = self::valueFromAST($valueNode, $itemType, $variables); if ($undefined === $coercedValue) { // Invalid: intentionally return no value. return $undefined; } return [$coercedValue]; } if ($type instanceof InputObjectType) { if (!$valueNode instanceof ObjectValueNode) { // Invalid: intentionally return no value. return $undefined; } $coercedObj = []; $fields = $type->getFields(); $fieldNodes = Utils::keyMap($valueNode->fields, function ($field) { return $field->name->value; }); foreach ($fields as $field) { /** @var ValueNode $fieldNode */ $fieldName = $field->name; $fieldNode = isset($fieldNodes[$fieldName]) ? $fieldNodes[$fieldName] : null; if (!$fieldNode || self::isMissingVariable($fieldNode->value, $variables)) { if ($field->defaultValueExists()) { $coercedObj[$fieldName] = $field->defaultValue; } else { if ($field->getType() instanceof NonNull) { // Invalid: intentionally return no value. return $undefined; } } continue; } $fieldValue = self::valueFromAST($fieldNode ? $fieldNode->value : null, $field->getType(), $variables); if ($undefined === $fieldValue) { // Invalid: intentionally return no value. return $undefined; } $coercedObj[$fieldName] = $fieldValue; } return $coercedObj; } if ($type instanceof LeafType) { $parsed = $type->parseLiteral($valueNode); if (null === $parsed) { // null represent a failure to parse correctly, // in which case no value is returned. return $undefined; } return $parsed; } throw new InvariantViolation('Must be input type'); }
/** * @it omits input object fields for unprovided variables */ public function testOmitsInputObjectFieldsForUnprovidedVariables() { $testInputObj = $this->inputObj(); $this->runTestCaseWithVars([], $testInputObj, '{ int: $foo, bool: $foo, requiredBool: true }', ['int' => 42, 'requiredBool' => true]); $this->runTestCaseWithVars([], $testInputObj, '{ requiredBool: $foo }', Utils::undefined()); $this->runTestCaseWithVars(['foo' => true], $testInputObj, '{ requiredBool: $foo }', ['int' => 42, 'requiredBool' => true]); }