/** * Given a type and a value AST node known to match this type, build a * runtime value. * * @param TypeInterface $type * @param $ast * @param null $variables * * @return array|mixed|null|string */ protected static function coerceValueAST(TypeInterface $type, $ast, $variables = NULL) { if ($type instanceof NonNullModifier) { // Note: we're not checking that the result of coerceValueAST is non-null. // We're assuming that this query has been validated and the value used // here is of the correct type. return self::coerceValueAST($type->getWrappedType(), $ast, $variables); } if (!$ast) { return NULL; } if ($ast::KIND === Node::KIND_VARIABLE) { $variableName = $ast->get('name')->get('value'); if (!isset($variables, $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 ListModifier) { $itemType = $type->getWrappedType(); if ($ast::KIND === Node::KIND_ARRAY_VALUE) { $tmp = []; foreach ($ast->get('values') as $itemAST) { $tmp[] = self::coerceValueAST($itemType, $itemAST, $variables); } return $tmp; } else { return [self::coerceValueAST($itemType, $ast, $variables)]; } } if ($type instanceof InputObjectType) { $fields = $type->getFields(); if ($ast::KIND !== Node::KIND_OBJECT_VALUE) { return NULL; } $asts = array_reduce($ast->get('fields'), function ($carry, $field) { $carry[$field->get('name')->get('value')] = $field; return $carry; }, []); $object = []; foreach ($fields as $name => $item) { $field = $asts[$name]; $fieldValue = self::coerceValueAST($item->getType(), $field ? $field->get('value') : NULL, $variables); $object[$name] = $fieldValue === NULL ? $item->getDefaultValue() : $fieldValue; } return $object; } if ($type instanceof ScalarType || $type instanceof EnumType) { $coerced = $type->coerceLiteral($ast); if (isset($coerced)) { return $coerced; } } return NULL; }
/** * Implements the instructions for completeValue as defined in the * "Field entries" section of the spec. * * If the field type is Non-Null, then this recursively completes the value * for the inner type. It throws a field error if that completion returns null, * as per the "Nullability" section of the spec. * * If the field type is a List, then this recursively completes the value * for the inner type on each item in the list. * * If the field type is a Scalar or Enum, ensures the completed value is a legal * value of the type by calling the `coerce` method of GraphQL type definition. * * Otherwise, the field type expects a sub-selection set, and will complete the * value by evaluating all sub-selections. * * @param ExecutionContext $context * @param TypeInterface $type * @param $asts * @param $result * * @return array|mixed|null|string * * @throws \Exception */ protected static function completeField(ExecutionContext $context, TypeInterface $type, $asts, &$result) { // If field type is NonNullModifier, complete for inner type, and throw field error // if result is null. if ($type instanceof NonNullModifier) { $completed = self::completeField($context, $type->getWrappedType(), $asts, $result); if ($completed === NULL) { throw new \Exception('Cannot return null for non-nullable type.'); } return $completed; } // If result is null-like, return null. if (!isset($result)) { return NULL; } // If field type is List, complete each item in the list with the inner type if ($type instanceof ListModifier) { $itemType = $type->getWrappedType(); if (!(is_array($result) || $result instanceof \ArrayObject)) { throw new \Exception('User Error: expected iterable, but did not find one.'); } $tmp = []; foreach ($result as $item) { $tmp[] = self::completeField($context, $itemType, $asts, $item); } return $tmp; } // If field type is Scalar or Enum, coerce to a valid value, returning // null if coercion is not possible. if ($type instanceof ScalarType || $type instanceof EnumType) { if (!method_exists($type, 'coerce')) { throw new \Exception('Missing coerce method on type.'); } return $type->coerce($result); } // Field type must be Object, Interface or Union and expect // sub-selections. $objectType = $type instanceof ObjectType ? $type : ($type instanceof InterfaceType || $type instanceof UnionType ? $type->resolveType($result) : NULL); if (!$objectType) { return NULL; } // Collect sub-fields to execute to complete this value. $subFieldASTs = new \ArrayObject(); $visitedFragmentNames = new \ArrayObject(); $count = count($asts); for ($i = 0; $i < $count; $i++) { $selectionSet = $asts[$i]->get('selectionSet'); if ($selectionSet) { $subFieldASTs = self::collectFields($context, $objectType, $selectionSet, $subFieldASTs, $visitedFragmentNames); } } return self::executeFields($context, $objectType, $result, $subFieldASTs); }