/** * 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 `serialize` method of GraphQL type * definition. * * Otherwise, the field type expects a sub-selection set, and will complete the * value by evaluating all sub-selections. */ private static function completeValue(ExecutionContext $exeContext, Type $returnType, $fieldASTs, ResolveInfo $info, &$result) { // If field type is NonNull, complete for inner type, and throw field error // if result is null. if ($returnType instanceof NonNull) { $completed = self::completeValue($exeContext, $returnType->getWrappedType(), $fieldASTs, $info, $result); if ($completed === null) { throw new Error('Cannot return null for non-nullable type.', $fieldASTs instanceof \ArrayObject ? $fieldASTs->getArrayCopy() : $fieldASTs); } return $completed; } // If result is null-like, return null. if (null === $result) { return null; } // If field type is Scalar or Enum, serialize to a valid value, returning // null if serialization is not possible. if ($returnType instanceof ScalarType || $returnType instanceof EnumType) { return $returnType->serialize($result); } // If field type is List, and return type is Composite - complete by executing these fields with list value as parameter if ($returnType instanceof ListOfType) { $itemType = $returnType->getWrappedType(); Utils::invariant(is_array($result) || $result instanceof \Traversable, 'User Error: expected iterable, but did not find one.'); // For Object[]: // Allow all object fields to process list value in it's `map` callback: if ($itemType instanceof ObjectType) { // Filter out nulls (as `map` doesn't expect it): $list = []; foreach ($result as $index => $item) { if (null !== $item) { $list[] = $item; } } $subFieldASTs = self::collectSubFields($exeContext, $itemType, $fieldASTs); $mapped = self::executeFields($exeContext, $itemType, $list, $subFieldASTs); $i = 0; $completed = []; foreach ($result as $index => $item) { if (null === $item) { // Complete nulls separately $completed[] = self::completeValueCatchingError($exeContext, $itemType, $fieldASTs, $info, $item); } else { // Assuming same order of mapped values $completed[] = $mapped[$i++]; } } return $completed; } else { if ($itemType instanceof AbstractType) { // Values sharded by ObjectType $listPerObjectType = []; // Helper structures to restore ordering after resolve calls $resultTypeMap = []; $typeNameMap = []; $cursors = []; $copied = []; foreach ($result as $index => $item) { $copied[$index] = $item; if (null !== $item) { $objectType = $itemType->getObjectType($item, $info); if ($objectType && !$itemType->isPossibleType($objectType)) { $exeContext->addError(new Error("Runtime Object type \"{$objectType}\" is not a possible type for \"{$itemType}\".")); $copied[$index] = null; } else { $listPerObjectType[$objectType->name][] = $item; $resultTypeMap[$index] = $objectType->name; $typeNameMap[$objectType->name] = $objectType; } } } $mapped = []; foreach ($listPerObjectType as $typeName => $list) { $objectType = $typeNameMap[$typeName]; $subFieldASTs = self::collectSubFields($exeContext, $objectType, $fieldASTs); $mapped[$typeName] = self::executeFields($exeContext, $objectType, $list, $subFieldASTs); $cursors[$typeName] = 0; } // Restore order: $completed = []; foreach ($copied as $index => $item) { if (null === $item) { // Complete nulls separately $completed[] = self::completeValueCatchingError($exeContext, $itemType, $fieldASTs, $info, $item); } else { $typeName = $resultTypeMap[$index]; $completed[] = $mapped[$typeName][$cursors[$typeName]++]; } } return $completed; } else { // For simple lists: $tmp = []; foreach ($result as $item) { $tmp[] = self::completeValueCatchingError($exeContext, $itemType, $fieldASTs, $info, $item); } return $tmp; } } } if ($returnType instanceof ObjectType) { $objectType = $returnType; } else { if ($returnType instanceof AbstractType) { $objectType = $returnType->getObjectType($result, $info); if ($objectType && !$returnType->isPossibleType($objectType)) { throw new Error("Runtime Object type \"{$objectType}\" is not a possible type for \"{$returnType}\"."); } } else { $objectType = null; } } if (!$objectType) { return null; } // If there is an isTypeOf predicate function, call it with the // current result. If isTypeOf returns false, then raise an error rather // than continuing execution. if (false === $objectType->isTypeOf($result, $info)) { throw new Error("Expected value of type {$objectType} but got: {$result}.", $fieldASTs); } // Collect sub-fields to execute to complete this value. $subFieldASTs = self::collectSubFields($exeContext, $objectType, $fieldASTs); $executed = self::executeFields($exeContext, $objectType, [$result], $subFieldASTs); return isset($executed[0]) ? $executed[0] : null; }