public static function valueFromAST($valueAST, InputType $type, $variables = null) { if ($type instanceof NonNull) { return self::valueFromAST($valueAST, $type->getWrappedType(), $variables); } if (!$valueAST) { return null; } if ($valueAST instanceof Variable) { $variableName = $valueAST->name->value; if (!$variables || !isset($variables[$variableName])) { return null; } return $variables[$variableName]; } if ($type instanceof ListOfType) { $itemType = $type->getWrappedType(); if ($valueAST instanceof ListValue) { return array_map(function ($itemAST) use($itemType, $variables) { return Values::valueFromAST($itemAST, $itemType, $variables); }, $valueAST->values); } else { return [self::valueFromAST($valueAST, $itemType, $variables)]; } } if ($type instanceof InputObjectType) { $fields = $type->getFields(); if (!$valueAST instanceof ObjectValue) { return null; } $fieldASTs = Utils::keyMap($valueAST->fields, function ($field) { return $field->name->value; }); $values = []; foreach ($fields as $field) { $fieldAST = isset($fieldASTs[$field->name]) ? $fieldASTs[$field->name] : null; $fieldValue = self::valueFromAST($fieldAST ? $fieldAST->value : null, $field->getType(), $variables); if (null === $fieldValue) { $fieldValue = $field->defaultValue; } if (null !== $fieldValue) { $values[$field->name] = $fieldValue; } } return $values; } Utils::invariant($type instanceof ScalarType || $type instanceof EnumType, 'Must be input type'); return $type->parseLiteral($valueAST); }
/** * 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. * * | GraphQL Value | PHP Value | * | -------------------- | ------------- | * | Input Object | Assoc Array | * | List | Array | * | Boolean | Boolean | * | String | String | * | Int / Float | Int / Float | * | Enum Value | Mixed | * * @param $valueAST * @param InputType $type * @param null $variables * @return array|null * @throws \Exception */ public static function valueFromAST($valueAST, InputType $type, $variables = null) { if ($type instanceof NonNull) { // Note: we're not checking that the result of valueFromAST is non-null. // We're assuming that this query has been validated and the value used // here is of the correct type. return self::valueFromAST($valueAST, $type->getWrappedType(), $variables); } if (!$valueAST) { return null; } if ($valueAST instanceof Variable) { $variableName = $valueAST->name->value; if (!$variables || !isset($variables[$variableName])) { return null; } // 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 ($valueAST instanceof ListValue) { return array_map(function ($itemAST) use($itemType, $variables) { return self::valueFromAST($itemAST, $itemType, $variables); }, $valueAST->values); } else { return [self::valueFromAST($valueAST, $itemType, $variables)]; } } if ($type instanceof InputObjectType) { $fields = $type->getFields(); if (!$valueAST instanceof ObjectValue) { return null; } $fieldASTs = Utils::keyMap($valueAST->fields, function ($field) { return $field->name->value; }); $values = []; foreach ($fields as $field) { $fieldAST = isset($fieldASTs[$field->name]) ? $fieldASTs[$field->name] : null; $fieldValue = self::valueFromAST($fieldAST ? $fieldAST->value : null, $field->getType(), $variables); if (null === $fieldValue) { $fieldValue = $field->defaultValue; } if (null !== $fieldValue) { $values[$field->name] = $fieldValue; } } return $values; } if ($type instanceof LeafType) { return $type->parseLiteral($valueAST); } 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'); }
/** * Given a PHP value and a GraphQL type, determine if the value will be * accepted for that type. This is primarily useful for validating the * runtime values of query variables. * * @param $value * @param InputType $type * @return array */ private static function isValidPHPValue($value, InputType $type) { // A value must be provided if the type is non-null. if ($type instanceof NonNull) { $ofType = $type->getWrappedType(); if (null === $value) { if ($ofType->name) { return ["Expected \"{$ofType->name}!\", found null."]; } return ['Expected non-null value, found null.']; } return self::isValidPHPValue($value, $ofType); } if (null === $value) { return []; } // Lists accept a non-list value as a list of one. if ($type instanceof ListOfType) { $itemType = $type->getWrappedType(); if (is_array($value)) { $tmp = []; foreach ($value as $index => $item) { $errors = self::isValidPHPValue($item, $itemType); $tmp = array_merge($tmp, Utils::map($errors, function ($error) use($index) { return "In element #{$index}: {$error}"; })); } return $tmp; } return self::isValidPHPValue($value, $itemType); } // Input objects check each defined field. if ($type instanceof InputObjectType) { if (!is_object($value) && !is_array($value)) { return ["Expected \"{$type->name}\", found not an object."]; } $fields = $type->getFields(); $errors = []; // Ensure every provided field is defined. $props = is_object($value) ? get_object_vars($value) : $value; foreach ($props as $providedField => $tmp) { if (!isset($fields[$providedField])) { $errors[] = "In field \"{$providedField}\": Unknown field."; } } // Ensure every defined field is valid. foreach ($fields as $fieldName => $tmp) { $newErrors = self::isValidPHPValue(isset($value[$fieldName]) ? $value[$fieldName] : null, $fields[$fieldName]->getType()); $errors = array_merge($errors, Utils::map($newErrors, function ($error) use($fieldName) { return "In field \"{$fieldName}\": {$error}"; })); } return $errors; } if ($type instanceof LeafType) { // Scalar/Enum input checks to ensure the type can parse the value to // a non-null value. $parseResult = $type->parseValue($value); if (null === $parseResult) { $v = json_encode($value); return ["Expected type \"{$type->name}\", found {$v}."]; } return []; } throw new InvariantViolation('Must be input type'); }