private function buildFieldArguments(FieldNode $node) { $rawVariableValues = $this->getRawVariableValues(); $astFieldInfo = $this->astFieldInfo($node); $fieldDef = $astFieldInfo[1]; $args = []; if ($fieldDef instanceof FieldDefinition) { $variableValues = Values::getVariableValues($this->context->getSchema(), $this->variableDefs, $rawVariableValues); $args = Values::getArgumentValues($fieldDef, $node, $variableValues); } return $args; }
/** * Resolves the field on the given source object. In particular, this * figures out the value that the field returns by calling its resolve function, * then calls completeValue to complete promises, serialize scalars, or execute * the sub-selection-set for objects. */ private static function resolveField(ExecutionContext $exeContext, ObjectType $parentType, $source, $fieldASTs, $path) { $fieldAST = $fieldASTs[0]; $fieldName = $fieldAST->name->value; $fieldDef = self::getFieldDef($exeContext->schema, $parentType, $fieldName); if (!$fieldDef) { return self::$UNDEFINED; } $returnType = $fieldDef->getType(); // Build hash of arguments from the field.arguments AST, using the // variables scope to fulfill any variable references. $args = Values::getArgumentValues($fieldDef->args, $fieldAST->arguments, $exeContext->variableValues); // The resolve function's optional third argument is a collection of // information about the current execution state. $info = new ResolveInfo(['fieldName' => $fieldName, 'fieldASTs' => $fieldASTs, 'returnType' => $returnType, 'parentType' => $parentType, 'path' => $path, 'schema' => $exeContext->schema, 'fragments' => $exeContext->fragments, 'rootValue' => $exeContext->rootValue, 'operation' => $exeContext->operation, 'variableValues' => $exeContext->variableValues]); if (isset($fieldDef->resolveFn)) { $resolveFn = $fieldDef->resolveFn; } else { if (isset($parentType->resolveFieldFn)) { $resolveFn = $parentType->resolveFieldFn; } else { $resolveFn = self::$defaultResolveFn; } } // The resolve function's optional third argument is a context value that // is provided to every resolve function within an execution. It is commonly // used to represent an authenticated user, or request-specific caches. $context = $exeContext->contextValue; // Get the resolve function, regardless of if its result is normal // or abrupt (error). $result = self::resolveOrError($resolveFn, $source, $args, $context, $info); $result = self::completeValueCatchingError($exeContext, $returnType, $fieldASTs, $info, $path, $result); return $result; }
/** * Isolates the "ReturnOrAbrupt" behavior to not de-opt the `resolveField` * function. Returns the result of resolveFn or the abrupt-return Error object. * * @param ExecutionContext $exeContext * @param FieldDefinition $fieldDef * @param FieldNode $fieldNode * @param callable $resolveFn * @param mixed $source * @param mixed $context * @param ResolveInfo $info * @return \Exception|mixed */ private static function resolveOrError($exeContext, $fieldDef, $fieldNode, $resolveFn, $source, $context, $info) { try { // Build hash of arguments from the field.arguments AST, using the // variables scope to fulfill any variable references. $args = Values::getArgumentValues($fieldDef, $fieldNode, $exeContext->variableValues); return call_user_func($resolveFn, $source, $args, $context, $info); } catch (\Exception $error) { return $error; } }
/** * Given a type and any value, return a runtime value coerced to match the type. */ private static function coerceValue(Type $type, $value) { if ($type instanceof NonNull) { // Note: we're not checking that the result of coerceValue is non-null. // We only call this function after calling isValidValue. return self::coerceValue($type->getWrappedType(), $value); } if (null === $value) { return null; } if ($type instanceof ListOfType) { $itemType = $type->getWrappedType(); // TODO: support iterable input if (is_array($value)) { return array_map(function ($item) use($itemType) { return Values::coerceValue($itemType, $item); }, $value); } else { return [self::coerceValue($itemType, $value)]; } } if ($type instanceof InputObjectType) { $fields = $type->getFields(); $obj = []; foreach ($fields as $fieldName => $field) { $fieldValue = self::coerceValue($field->getType(), isset($value[$fieldName]) ? $value[$fieldName] : null); if (null === $fieldValue) { $fieldValue = $field->defaultValue; } if (null !== $fieldValue) { $obj[$fieldName] = $fieldValue; } } return $obj; } Utils::invariant($type instanceof ScalarType || $type instanceof EnumType, 'Must be input type'); return $type->parseValue($value); }
/** * Resolves the field on the given source object. In particular, this * figures out the value that the field returns by calling its resolve function, * then calls completeValue to complete promises, serialize scalars, or execute * the sub-selection-set for objects. */ private static function resolveField(ExecutionContext $exeContext, ObjectType $parentType, $source, $fieldASTs) { $fieldAST = $fieldASTs[0]; $fieldName = $fieldAST->name->value; $fieldDef = self::getFieldDef($exeContext->schema, $parentType, $fieldName); if (!$fieldDef) { return self::$UNDEFINED; } $returnType = $fieldDef->getType(); if (isset($fieldDef->resolve)) { $resolveFn = $fieldDef->resolve; } else { if (isset(self::$defaultResolveFn)) { $resolveFn = self::$defaultResolveFn; } else { $resolveFn = [__CLASS__, 'defaultResolveFn']; } } // Build hash of arguments from the field.arguments AST, using the // variables scope to fulfill any variable references. // TODO: find a way to memoize, in case this field is within a List type. $args = Values::getArgumentValues($fieldDef->args, $fieldAST->arguments, $exeContext->variableValues); // The resolve function's optional third argument is a collection of // information about the current execution state. $info = new ResolveInfo(['fieldName' => $fieldName, 'fieldASTs' => $fieldASTs, 'returnType' => $returnType, 'parentType' => $parentType, 'schema' => $exeContext->schema, 'fragments' => $exeContext->fragments, 'rootValue' => $exeContext->rootValue, 'operation' => $exeContext->operation, 'variableValues' => $exeContext->variableValues]); // If an error occurs while calling the field `resolve` function, ensure that // it is wrapped as a GraphQLError with locations. Log this error and return // null if allowed, otherwise throw the error so the parent field can handle // it. try { $result = call_user_func($resolveFn, $source, $args, $info); } catch (\Exception $error) { $reportedError = Error::createLocatedError($error, $fieldASTs); if ($returnType instanceof NonNull) { throw $reportedError; } $exeContext->addError($reportedError); return null; } return self::completeValueCatchingError($exeContext, $returnType, $fieldASTs, $info, $result); }
/** * Given list of parent type values returns corresponding list of field values * * In particular, this * figures out the value that the field returns by calling its `resolve` or `map` function, * then calls `completeValue` on each value to serialize scalars, or execute the sub-selection-set * for objects. * * @param ExecutionContext $exeContext * @param ObjectType $parentType * @param $sourceValueList * @param $fieldASTs * @return array * @throws Error */ private static function resolveField(ExecutionContext $exeContext, ObjectType $parentType, $sourceValueList, $fieldASTs, $responseName, &$resolveResult) { $fieldAST = $fieldASTs[0]; $fieldName = $fieldAST->name->value; $fieldDef = self::getFieldDef($exeContext->schema, $parentType, $fieldName); if (!$fieldDef) { return; } $returnType = $fieldDef->getType(); // Build hash of arguments from the field.arguments AST, using the // variables scope to fulfill any variable references. // TODO: find a way to memoize, in case this field is within a List type. $args = Values::getArgumentValues($fieldDef->args, $fieldAST->arguments, $exeContext->variableValues); // The resolve function's optional third argument is a collection of // information about the current execution state. $info = new ResolveInfo(['fieldName' => $fieldName, 'fieldASTs' => $fieldASTs, 'returnType' => $returnType, 'parentType' => $parentType, 'schema' => $exeContext->schema, 'fragments' => $exeContext->fragments, 'rootValue' => $exeContext->rootValue, 'operation' => $exeContext->operation, 'variableValues' => $exeContext->variableValues]); $mapFn = $fieldDef->mapFn; // If an error occurs while calling the field `map` or `resolve` function, ensure that // it is wrapped as a GraphQLError with locations. Log this error and return // null if allowed, otherwise throw the error so the parent field can handle // it. if ($mapFn) { try { $mapped = call_user_func($mapFn, $sourceValueList, $args, $info); $validType = is_array($mapped) || $mapped instanceof \Traversable && $mapped instanceof \Countable; $mappedCount = count($mapped); $sourceCount = count($sourceValueList); Utils::invariant($validType && count($mapped) === count($sourceValueList), "Function `map` of {$parentType}.{$fieldName} is expected to return array or " . "countable traversable with exact same number of items as list being mapped. " . "Got '%s' with count '{$mappedCount}' against '{$sourceCount}' expected.", Utils::getVariableType($mapped)); } catch (\Exception $error) { $reportedError = Error::createLocatedError($error, $fieldASTs); if ($returnType instanceof NonNull) { throw $reportedError; } $exeContext->addError($reportedError); return null; } foreach ($mapped as $index => $value) { $resolveResult[$index][$responseName] = self::completeValueCatchingError($exeContext, $returnType, $fieldASTs, $info, $value); } } else { if (isset($fieldDef->resolveFn)) { $resolveFn = $fieldDef->resolveFn; } else { if (isset($parentType->resolveFieldFn)) { $resolveFn = $parentType->resolveFieldFn; } else { $resolveFn = self::$defaultResolveFn; } } foreach ($sourceValueList as $index => $value) { try { $resolved = call_user_func($resolveFn, $value, $args, $info); } catch (\Exception $error) { $reportedError = Error::createLocatedError($error, $fieldASTs); if ($returnType instanceof NonNull) { throw $reportedError; } $exeContext->addError($reportedError); $resolved = null; } $resolveResult[$index][$responseName] = self::completeValueCatchingError($exeContext, $returnType, $fieldASTs, $info, $resolved); } } }
/** * Given list of parent type values returns corresponding list of field values * * In particular, this * figures out the value that the field returns by calling its `resolve` or `map` function, * then calls `completeValue` on each value to serialize scalars, or execute the sub-selection-set * for objects. * * @param ExecutionContext $exeContext * @param ObjectType $parentType * @param $sourceValueList * @param $fieldASTs * @return array * @throws Error */ private static function resolveField(ExecutionContext $exeContext, ObjectType $parentType, $sourceValueList, $fieldASTs, $responseName, &$resolveResult) { $fieldAST = $fieldASTs[0]; $uid = self::getUid($fieldAST); // Get memoized variables if they exist if (isset(self::$memoized['resolveField'][$uid])) { $memoized = self::$memoized['resolveField'][$uid]; $fieldDef = $memoized['fieldDef']; $returnType = $fieldDef->getType(); $args = $memoized['args']; $info = $memoized['info']; } else { $fieldName = $fieldAST->name->value; $fieldDef = self::getFieldDef($exeContext->schema, $parentType, $fieldName); if (!$fieldDef) { return; } $returnType = $fieldDef->getType(); // Build hash of arguments from the field.arguments AST, using the // variables scope to fulfill any variable references. $args = Values::getArgumentValues($fieldDef->args, $fieldAST->arguments, $exeContext->variableValues); // The resolve function's optional third argument is a collection of // information about the current execution state. $info = new ResolveInfo(['fieldName' => $fieldName, 'fieldASTs' => $fieldASTs, 'returnType' => $returnType, 'parentType' => $parentType, 'schema' => $exeContext->schema, 'fragments' => $exeContext->fragments, 'rootValue' => $exeContext->rootValue, 'operation' => $exeContext->operation, 'variableValues' => $exeContext->variableValues]); self::$memoized['resolveField'][$uid] = ['fieldDef' => $fieldDef, 'args' => $args, 'info' => $info, 'results' => new \SplObjectStorage()]; } $mapFn = $fieldDef->mapFn; // If an error occurs while calling the field `map` or `resolve` function, ensure that // it is wrapped as a GraphQLError with locations. Log this error and return // null if allowed, otherwise throw the error so the parent field can handle // it. if ($mapFn) { try { $mapped = call_user_func($mapFn, $sourceValueList, $args, $info); Utils::invariant(is_array($mapped) && count($mapped) === count($sourceValueList), "Function `map` of {$parentType}.{$fieldName} is expected to return array " . "with exact same number of items as list being mapped (first argument of `map`)"); } catch (\Exception $error) { $reportedError = Error::createLocatedError($error, $fieldASTs); if ($returnType instanceof NonNull) { throw $reportedError; } $exeContext->addError($reportedError); return null; } foreach ($mapped as $index => $value) { $resolveResult[$index][$responseName] = self::completeValueCatchingError($exeContext, $returnType, $fieldASTs, $info, $value); } } else { if (isset($fieldDef->resolveFn)) { $resolveFn = $fieldDef->resolveFn; } else { if (isset($parentType->resolveFieldFn)) { $resolveFn = $parentType->resolveFieldFn; } else { $resolveFn = self::$defaultResolveFn; } } foreach ($sourceValueList as $index => $value) { // If FieldAST uid and parent object are both the same as in a previous run, // use its result instead to prevent unnecessary work. Works for objects only. $isObject = is_object($value); if ($isObject && isset(self::$memoized['resolveField'][$uid]['results'][$value])) { $result = self::$memoized['resolveField'][$uid]['results'][$value]; } else { try { $resolved = call_user_func($resolveFn, $value, $args, $info); } catch (\Exception $error) { $reportedError = Error::createLocatedError($error, $fieldASTs); if ($returnType instanceof NonNull) { throw $reportedError; } $exeContext->addError($reportedError); $resolved = null; } $result = self::completeValueCatchingError($exeContext, $returnType, $fieldASTs, $info, $resolved); if ($isObject) { self::$memoized['resolveField'][$uid]['results'][$value] = $result; } } $resolveResult[$index][$responseName] = $result; } } }
/** * Resolves the field on the given source object. In particular, this * figures out the value that the field returns by calling its resolve function, * then calls completeValue to complete promises, serialize scalars, or execute * the sub-selection-set for objects. */ private static function resolveField(ExecutionContext $exeContext, ObjectType $parentType, $source, $fieldASTs) { $fieldAST = $fieldASTs[0]; $uid = self::getFieldUid($fieldAST, $parentType); // Get memoized variables if they exist if (isset($exeContext->memoized['resolveField'][$uid])) { $memoized = $exeContext->memoized['resolveField'][$uid]; $fieldDef = $memoized['fieldDef']; $returnType = $fieldDef->getType(); $args = $memoized['args']; $info = $memoized['info']; } else { $fieldName = $fieldAST->name->value; $fieldDef = self::getFieldDef($exeContext->schema, $parentType, $fieldName); if (!$fieldDef) { return self::$UNDEFINED; } $returnType = $fieldDef->getType(); // Build hash of arguments from the field.arguments AST, using the // variables scope to fulfill any variable references. $args = Values::getArgumentValues($fieldDef->args, $fieldAST->arguments, $exeContext->variableValues); // The resolve function's optional third argument is a collection of // information about the current execution state. $info = new ResolveInfo(['fieldName' => $fieldName, 'fieldASTs' => $fieldASTs, 'returnType' => $returnType, 'parentType' => $parentType, 'schema' => $exeContext->schema, 'fragments' => $exeContext->fragments, 'rootValue' => $exeContext->rootValue, 'operation' => $exeContext->operation, 'variableValues' => $exeContext->variableValues]); // Memoizing results for same query field // (useful for lists when several values are resolved against the same field) $exeContext->memoized['resolveField'][$uid] = $memoized = ['fieldDef' => $fieldDef, 'args' => $args, 'info' => $info, 'results' => new \SplObjectStorage()]; } // When source value is object it is possible to memoize certain subset of results $isObject = is_object($source); if ($isObject && isset($memoized['results'][$source])) { $result = $memoized['results'][$source]; } else { if (isset($fieldDef->resolveFn)) { $resolveFn = $fieldDef->resolveFn; } else { if (isset($parentType->resolveFieldFn)) { $resolveFn = $parentType->resolveFieldFn; } else { $resolveFn = self::$defaultResolveFn; } } // Get the resolve function, regardless of if its result is normal // or abrupt (error). $result = self::resolveOrError($resolveFn, $source, $args, $info); $result = self::completeValueCatchingError($exeContext, $returnType, $fieldASTs, $info, $result); if ($isObject) { $memoized['results'][$source] = $result; } } return $result; }
/** * Given a type and any value, return a runtime value coerced to match the type. */ private static function coerceValue(Type $type, $value) { if ($type instanceof NonNull) { // Note: we're not checking that the result of coerceValue is non-null. // We only call this function after calling isValidPHPValue. 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) { return Utils::map($value, function ($item) use($itemType) { return Values::coerceValue($itemType, $item); }); } else { return [self::coerceValue($itemType, $value)]; } } if ($type instanceof InputObjectType) { $fields = $type->getFields(); $obj = []; foreach ($fields as $fieldName => $field) { $fieldValue = self::coerceValue($field->getType(), isset($value[$fieldName]) ? $value[$fieldName] : null); if (null === $fieldValue) { $fieldValue = $field->defaultValue; } if (null !== $fieldValue) { $obj[$fieldName] = $fieldValue; } } return $obj; } if ($type instanceof LeafType) { return $type->parseValue($value); } throw new InvariantViolation('Must be input type'); }
/** * Resolves the field on the given source object. In particular, this * figures out the object that the field returns using the resolve function, * then calls completeField to coerce scalars or execute the sub * selection set for objects. */ private static function resolveFieldOrError(ExecutionContext $exeContext, ObjectType $parentType, $source, $fieldASTs, FieldDefinition $fieldDef) { $fieldAST = $fieldASTs[0]; $fieldType = $fieldDef->getType(); $resolveFn = $fieldDef->resolve ?: [__CLASS__, 'defaultResolveFn']; // Build a JS object of arguments from the field.arguments AST, using the // variables scope to fulfill any variable references. // TODO: find a way to memoize, in case this field is within a Array type. $args = Values::getArgumentValues($fieldDef->args, $fieldAST->arguments, $exeContext->variables); try { $result = call_user_func($resolveFn, $source, $args, $exeContext->root, $fieldAST, $fieldType, $parentType, $exeContext->schema); } catch (\Exception $error) { throw new Error($error->getMessage(), [$fieldAST], $error->getTrace()); } return self::completeField($exeContext, $fieldType, $fieldASTs, $result); }