Example #1
0
 /**
  * Format error for output.
  *
  * @param  Error  $e
  * @return array
  */
 public function formatError(Error $e)
 {
     $error = ['message' => $e->getMessage()];
     $locations = $e->getLocations();
     if (!empty($locations)) {
         $error['locations'] = array_map(function ($location) {
             return $location->toArray();
         }, $locations);
     }
     $previous = $e->getPrevious();
     if ($previous && $previous instanceof ValidationError) {
         $error['validation'] = $previous->getValidatorMessages();
     }
     return $error;
 }
Example #2
0
 /**
  * @param Schema $schema
  * @param $requestString
  * @param mixed $rootValue
  * @param array <string, string>|null $variableValues
  * @param string|null $operationName
  * @return array
  */
 public static function execute(Schema $schema, $requestString, $rootValue = null, $variableValues = null, $operationName = null)
 {
     try {
         $source = new Source($requestString ?: '', 'GraphQL request');
         $documentAST = Parser::parse($source);
         $validationErrors = DocumentValidator::validate($schema, $documentAST);
         if (!empty($validationErrors)) {
             return ['errors' => array_map(['GraphQL\\Error', 'formatError'], $validationErrors)];
         } else {
             return Executor::execute($schema, $documentAST, $rootValue, $variableValues, $operationName)->toArray();
         }
     } catch (Error $e) {
         return ['errors' => [Error::formatError($e)]];
     }
 }
Example #3
0
 /**
  * @param Schema $schema
  * @param $requestString
  * @param mixed $rootObject
  * @param array <string, string>|null $variableValues
  * @param string|null $operationName
  * @return array
  */
 public static function execute(Schema $schema, $requestString, $rootObject = null, $variableValues = null, $operationName = null)
 {
     try {
         $source = new Source($requestString ?: '', 'GraphQL request');
         $ast = Parser::parse($source);
         $validationResult = DocumentValidator::validate($schema, $ast);
         if (empty($validationResult['isValid'])) {
             return ['errors' => $validationResult['errors']];
         } else {
             return Executor::execute($schema, $rootObject, $ast, $operationName, $variableValues);
         }
     } catch (\Exception $e) {
         return ['errors' => Error::formatError($e)];
     }
 }
Example #4
0
 /**
  * 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);
 }
Example #5
0
 /**
  * @param Error $error
  * @return array
  */
 public static function formatError(Error $error)
 {
     return FormattedError::create($error->getMessage(), $error->getLocations());
 }
 /**
  * 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);
         }
     }
 }
Example #7
0
 /**
  * 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;
         }
     }
 }
Example #8
0
 /**
  * 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 ($result instanceof \Exception) {
         throw Error::createLocatedError($result, $fieldASTs);
     }
     // 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 List, complete each item in the list with the inner type
     if ($returnType instanceof ListOfType) {
         $itemType = $returnType->getWrappedType();
         Utils::invariant(is_array($result) || $result instanceof \Traversable, 'User Error: expected iterable, but did not find one.');
         $tmp = [];
         foreach ($result as $item) {
             $tmp[] = self::completeValueCatchingError($exeContext, $itemType, $fieldASTs, $info, $item);
         }
         return $tmp;
     }
     // 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) {
         Utils::invariant(method_exists($returnType, 'serialize'), 'Missing serialize method on type');
         return $returnType->serialize($result);
     }
     // Field type must be Object, Interface or Union and expect sub-selections.
     if ($returnType instanceof ObjectType) {
         $runtimeType = $returnType;
     } else {
         if ($returnType instanceof AbstractType) {
             $runtimeType = $returnType->getObjectType($result, $info);
             if ($runtimeType && !$returnType->isPossibleType($runtimeType)) {
                 throw new Error("Runtime Object type \"{$runtimeType}\" is not a possible type for \"{$returnType}\".");
             }
         } else {
             $runtimeType = null;
         }
     }
     if (!$runtimeType) {
         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 === $runtimeType->isTypeOf($result, $info)) {
         throw new Error("Expected value of type {$runtimeType} but got: " . Utils::getVariableType($result), $fieldASTs);
     }
     // Collect sub-fields to execute to complete this value.
     $subFieldASTs = new \ArrayObject();
     $visitedFragmentNames = new \ArrayObject();
     for ($i = 0; $i < count($fieldASTs); $i++) {
         // Get memoized value if it exists
         $uid = self::getFieldUid($fieldASTs[$i], $runtimeType);
         if (isset($exeContext->memoized['collectSubFields'][$uid])) {
             $subFieldASTs = $exeContext->memoized['collectSubFields'][$uid];
         } else {
             $selectionSet = $fieldASTs[$i]->selectionSet;
             if ($selectionSet) {
                 $subFieldASTs = self::collectFields($exeContext, $runtimeType, $selectionSet, $subFieldASTs, $visitedFragmentNames);
                 $exeContext->memoized['collectSubFields'][$uid] = $subFieldASTs;
             }
         }
     }
     return self::executeFields($exeContext, $runtimeType, $result, $subFieldASTs);
 }
Example #9
0
 public function testDoesNotAllowNonNullListsOfNonNullsToContainNull()
 {
     $doc = '
     query q($input:[String!]!) {
       nnListNN(input: $input)
     }
     ';
     $ast = Parser::parse($doc);
     $expected = FormattedError::create('Variable $input expected value of type [String!]! but got: ["A",null,"B"].', [new SourceLocation(2, 17)]);
     try {
         Executor::execute($this->schema(), $ast, null, ['input' => ['A', null, 'B']]);
     } catch (Error $e) {
         $this->assertEquals($expected, Error::formatError($e));
     }
 }
Example #10
0
 /**
  * @param Source $source
  * @param int $position
  * @param string $description
  */
 public function __construct(Source $source, $position, $description)
 {
     $location = $source->getLocation($position);
     $syntaxError = "Syntax Error {$source->name} ({$location->line}:{$location->column}) {$description}\n\n" . self::highlightSourceAtLocation($source, $location);
     parent::__construct($syntaxError, null, null, $source, [$position]);
 }