diff --git a/src/Executor/Executor.php b/src/Executor/Executor.php index babe2f9e0..c6040bdfe 100644 --- a/src/Executor/Executor.php +++ b/src/Executor/Executor.php @@ -20,7 +20,7 @@ */ class Executor { - /** @var callable|string[] */ + /** @var callable */ private static $defaultFieldResolver = [self::class, 'defaultFieldResolver']; /** @var PromiseAdapter */ @@ -35,7 +35,7 @@ public static function getDefaultFieldResolver() : callable } /** - * Custom default resolve function. + * Set a custom default resolve function. */ public static function setDefaultFieldResolver(callable $fieldResolver) { @@ -47,6 +47,9 @@ public static function getPromiseAdapter() : PromiseAdapter return self::$defaultPromiseAdapter ?? (self::$defaultPromiseAdapter = new SyncPromiseAdapter()); } + /** + * Set a custom default promise adapter. + */ public static function setPromiseAdapter(?PromiseAdapter $defaultPromiseAdapter = null) { self::$defaultPromiseAdapter = $defaultPromiseAdapter; @@ -58,9 +61,7 @@ public static function getImplementationFactory() : callable } /** - * Custom executor implementation factory. - * - * Will be called with as + * Set a custom executor implementation factory. */ public static function setImplementationFactory(callable $implementationFactory) { @@ -70,13 +71,13 @@ public static function setImplementationFactory(callable $implementationFactory) /** * Executes DocumentNode against given $schema. * - * Always returns ExecutionResult and never throws. All errors which occur during operation - * execution are collected in `$result->errors`. + * Always returns ExecutionResult and never throws. + * All errors which occur during operation execution are collected in `$result->errors`. * - * @param mixed|null $rootValue - * @param mixed|null $contextValue - * @param mixed[]|ArrayAccess|null $variableValues - * @param string|null $operationName + * @param mixed|null $rootValue + * @param mixed|null $contextValue + * @param array|ArrayAccess|null $variableValues + * @param string|null $operationName * * @return ExecutionResult|Promise * @@ -119,10 +120,10 @@ public static function execute( * * Useful for async PHP platforms. * - * @param mixed|null $rootValue - * @param mixed|null $contextValue - * @param mixed[]|null $variableValues - * @param string|null $operationName + * @param mixed|null $rootValue + * @param mixed|null $contextValue + * @param array|null $variableValues + * @param string|null $operationName * * @return Promise * @@ -161,9 +162,9 @@ public static function promiseToExecute( * and returns it as the result, or if it's a function, returns the result * of calling that function while passing along args and context. * - * @param mixed $objectValue - * @param mixed[] $args - * @param mixed|null $contextValue + * @param mixed $objectValue + * @param array $args + * @param mixed|null $contextValue * * @return mixed|null */ diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index 28ce11a30..b5dea24b4 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -17,7 +17,9 @@ use GraphQL\Language\AST\FragmentDefinitionNode; use GraphQL\Language\AST\FragmentSpreadNode; use GraphQL\Language\AST\InlineFragmentNode; +use GraphQL\Language\AST\Node; use GraphQL\Language\AST\OperationDefinitionNode; +use GraphQL\Language\AST\SelectionNode; use GraphQL\Language\AST\SelectionSetNode; use GraphQL\Type\Definition\AbstractType; use GraphQL\Type\Definition\Directive; @@ -70,6 +72,11 @@ private function __construct(ExecutionContext $context) $this->subFieldCache = new SplObjectStorage(); } + /** + * @param mixed $rootValue + * @param mixed $contextValue + * @param array|Traversable $variableValues + */ public static function create( PromiseAdapter $promiseAdapter, Schema $schema, @@ -79,7 +86,7 @@ public static function create( $variableValues, ?string $operationName, callable $fieldResolver - ) { + ) : ExecutorImplementation { $exeContext = self::buildExecutionContext( $schema, $documentNode, @@ -116,12 +123,11 @@ public function doExecute() : Promise * Constructs an ExecutionContext object from the arguments passed to * execute, which we will pass throughout the other execution methods. * - * @param mixed $rootValue - * @param mixed $contextValue - * @param mixed[]|Traversable $rawVariableValues - * @param string|null $operationName + * @param mixed $rootValue + * @param mixed $contextValue + * @param array|Traversable $rawVariableValues * - * @return ExecutionContext|Error[] + * @return ExecutionContext|array */ private static function buildExecutionContext( Schema $schema, @@ -129,7 +135,7 @@ private static function buildExecutionContext( $rootValue, $contextValue, $rawVariableValues, - $operationName = null, + ?string $operationName = null, ?callable $fieldResolver = null, ?PromiseAdapter $promiseAdapter = null ) { @@ -238,9 +244,9 @@ private function buildResponse($data) /** * Implements the "Evaluating operations" section of the spec. * - * @param mixed $rootValue + * @param mixed $rootValue * - * @return Promise|stdClass|mixed[]|null + * @return array|Promise|stdClass|null */ private function executeOperation(OperationDefinitionNode $operation, $rootValue) { @@ -282,11 +288,9 @@ function ($error) : ?Promise { /** * Extracts the root type of the operation from the schema. * - * @return ObjectType - * * @throws Error */ - private function getOperationRootType(Schema $schema, OperationDefinitionNode $operation) + private function getOperationRootType(Schema $schema, OperationDefinitionNode $operation) : ObjectType { switch ($operation->operation) { case 'query': @@ -334,18 +338,13 @@ private function getOperationRootType(Schema $schema, OperationDefinitionNode $o * CollectFields requires the "runtime type" of an object. For a field which * returns an Interface or Union type, the "runtime type" will be the actual * Object type returned by that field. - * - * @param ArrayObject $fields - * @param ArrayObject $visitedFragmentNames - * - * @return ArrayObject */ private function collectFields( ObjectType $runtimeType, SelectionSetNode $selectionSet, - $fields, - $visitedFragmentNames - ) { + ArrayObject $fields, + ArrayObject $visitedFragmentNames + ) : ArrayObject { $exeContext = $this->exeContext; foreach ($selectionSet->selections as $selection) { switch (true) { @@ -403,7 +402,7 @@ private function collectFields( * * @param FragmentSpreadNode|FieldNode|InlineFragmentNode $node */ - private function shouldIncludeNode($node) : bool + private function shouldIncludeNode(SelectionNode $node) : bool { $variableValues = $this->exeContext->variableValues; $skipDirective = Directive::skipDirective(); @@ -437,13 +436,9 @@ private static function getFieldEntryKey(FieldNode $node) : string * Determines if a fragment is applicable to the given type. * * @param FragmentDefinitionNode|InlineFragmentNode $fragment - * - * @return bool */ - private function doesFragmentConditionMatch( - $fragment, - ObjectType $type - ) { + private function doesFragmentConditionMatch(Node $fragment, ObjectType $type) : bool + { $typeConditionNode = $fragment->typeCondition; if ($typeConditionNode === null) { return true; @@ -463,13 +458,12 @@ private function doesFragmentConditionMatch( * Implements the "Evaluating selection sets" section of the spec * for "write" mode. * - * @param mixed $rootValue - * @param mixed[] $path - * @param ArrayObject $fields + * @param mixed $rootValue + * @param array $path * - * @return Promise|stdClass|mixed[] + * @return array|Promise|stdClass */ - private function executeFieldsSerially(ObjectType $parentType, $rootValue, $path, $fields) + private function executeFieldsSerially(ObjectType $parentType, $rootValue, array $path, ArrayObject $fields) { $result = $this->promiseReduce( array_keys($fields->getArrayCopy()), @@ -512,13 +506,12 @@ function ($results, $responseName) use ($path, $parentType, $rootValue, $fields) * by calling its resolve function, then calls completeValue to complete promises, * serialize scalars, or execute the sub-selection-set for objects. * - * @param mixed $rootValue - * @param FieldNode[] $fieldNodes - * @param mixed[] $path + * @param mixed $rootValue + * @param array $path * - * @return mixed[]|Exception|mixed|null + * @return array|Throwable|mixed|null */ - private function resolveField(ObjectType $parentType, $rootValue, $fieldNodes, $path) + private function resolveField(ObjectType $parentType, $rootValue, ArrayObject $fieldNodes, array $path) { $exeContext = $this->exeContext; $fieldNode = $fieldNodes[0]; @@ -607,16 +600,17 @@ private function getFieldDef(Schema $schema, ObjectType $parentType, string $fie * Isolates the "ReturnOrAbrupt" behavior to not de-opt the `resolveField` function. * Returns the result of resolveFn or the abrupt-return Error object. * - * @param FieldDefinition $fieldDef - * @param FieldNode $fieldNode - * @param callable $resolveFn - * @param mixed $rootValue - * @param ResolveInfo $info + * @param mixed $rootValue * * @return Throwable|Promise|mixed */ - private function resolveFieldValueOrError($fieldDef, $fieldNode, $resolveFn, $rootValue, $info) - { + private function resolveFieldValueOrError( + FieldDefinition $fieldDef, + FieldNode $fieldNode, + callable $resolveFn, + $rootValue, + ResolveInfo $info + ) { try { // Build a map of arguments from the field.arguments AST, using the // variables scope to fulfill any variable references. @@ -637,17 +631,16 @@ private function resolveFieldValueOrError($fieldDef, $fieldNode, $resolveFn, $ro * This is a small wrapper around completeValue which detects and logs errors * in the execution context. * - * @param FieldNode[] $fieldNodes - * @param string[] $path - * @param mixed $result + * @param array $path + * @param mixed $result * - * @return mixed[]|Promise|null + * @return array|Promise|stdClass|null */ private function completeValueCatchingError( Type $returnType, - $fieldNodes, + ArrayObject $fieldNodes, ResolveInfo $info, - $path, + array $path, $result ) { // Otherwise, error protection is applied, logging the error and resolving @@ -664,18 +657,26 @@ private function completeValueCatchingError( $promise = $this->getPromise($completed); if ($promise !== null) { - return $promise->then(null, function ($error) use ($fieldNodes, $path, $returnType) { - return $this->handleFieldError($error, $fieldNodes, $path, $returnType); + return $promise->then(null, function ($error) use ($fieldNodes, $path, $returnType) : void { + $this->handleFieldError($error, $fieldNodes, $path, $returnType); }); } return $completed; } catch (Throwable $err) { - return $this->handleFieldError($err, $fieldNodes, $path, $returnType); + $this->handleFieldError($err, $fieldNodes, $path, $returnType); + + return null; } } - private function handleFieldError($rawError, $fieldNodes, $path, $returnType) + /** + * @param mixed $rawError + * @param array $path + * + * @throws Error + */ + private function handleFieldError($rawError, ArrayObject $fieldNodes, array $path, Type $returnType) : void { $error = Error::createLocatedError( $rawError, @@ -691,8 +692,6 @@ private function handleFieldError($rawError, $fieldNodes, $path, $returnType) // Otherwise, error protection is applied, logging the error and resolving // a null value for this field if one is encountered. $this->exeContext->addError($error); - - return null; } /** @@ -716,20 +715,19 @@ private function handleFieldError($rawError, $fieldNodes, $path, $returnType) * Otherwise, the field type expects a sub-selection set, and will complete the * value by evaluating all sub-selections. * - * @param FieldNode[] $fieldNodes - * @param string[] $path - * @param mixed $result + * @param array $path + * @param mixed $result * - * @return mixed[]|mixed|Promise|null + * @return array|mixed|Promise|null * * @throws Error * @throws Throwable */ private function completeValue( Type $returnType, - $fieldNodes, + ArrayObject $fieldNodes, ResolveInfo $info, - $path, + array $path, &$result ) { // If result is an Error, throw a located error. @@ -800,10 +798,8 @@ private function completeValue( /** * @param mixed $value - * - * @return bool */ - private function isPromise($value) + private function isPromise($value) : bool { return $value instanceof Promise || $this->exeContext->promiseAdapter->isThenable($value); } @@ -813,10 +809,8 @@ private function isPromise($value) * otherwise returns null. * * @param mixed $value - * - * @return Promise|null */ - private function getPromise($value) + private function getPromise($value) : ?Promise { if ($value === null || $value instanceof Promise) { return $value; @@ -844,7 +838,7 @@ private function getPromise($value) * If the callback does not return a Promise, then this function will also not * return a Promise. * - * @param mixed[] $values + * @param array $values * @param Promise|mixed|null $initialValue * * @return Promise|mixed|null @@ -870,15 +864,14 @@ function ($previous, $value) use ($callback) { /** * Complete a list value by completing each item in the list with the inner type. * - * @param FieldNode[] $fieldNodes - * @param mixed[] $path - * @param mixed[]|Traversable $results + * @param array $path + * @param array|Traversable $results * - * @return mixed[]|Promise + * @return array|Promise|stdClass * * @throws Exception */ - private function completeListValue(ListOfType $returnType, $fieldNodes, ResolveInfo $info, $path, &$results) + private function completeListValue(ListOfType $returnType, ArrayObject $fieldNodes, ResolveInfo $info, array $path, &$results) { $itemType = $returnType->getWrappedType(); Utils::invariant( @@ -907,7 +900,7 @@ private function completeListValue(ListOfType $returnType, $fieldNodes, ResolveI /** * Complete a Scalar or Enum by serializing to a valid value, throwing if serialization is not possible. * - * @param mixed $result + * @param mixed $result * * @return mixed * @@ -930,16 +923,20 @@ private function completeLeafValue(LeafType $returnType, &$result) * Complete a value of an abstract type by determining the runtime object type * of that value, then complete the value for that type. * - * @param FieldNode[] $fieldNodes - * @param mixed[] $path - * @param mixed[] $result + * @param array $path + * @param array $result * - * @return mixed + * @return array|Promise|stdClass * * @throws Error */ - private function completeAbstractValue(AbstractType $returnType, $fieldNodes, ResolveInfo $info, $path, &$result) - { + private function completeAbstractValue( + AbstractType $returnType, + ArrayObject $fieldNodes, + ResolveInfo $info, + array $path, + &$result + ) { $exeContext = $this->exeContext; $typeCandidate = $returnType->resolveType($result, $exeContext->contextValue, $info); @@ -1062,16 +1059,20 @@ private function defaultTypeResolver($value, $contextValue, ResolveInfo $info, A /** * Complete an Object value by executing all sub-selections. * - * @param FieldNode[] $fieldNodes - * @param mixed[] $path - * @param mixed $result + * @param array $path + * @param mixed $result * - * @return mixed[]|Promise|stdClass + * @return array|Promise|stdClass * * @throws Error */ - private function completeObjectValue(ObjectType $returnType, $fieldNodes, ResolveInfo $info, $path, &$result) - { + private function completeObjectValue( + ObjectType $returnType, + ArrayObject $fieldNodes, + ResolveInfo $info, + array $path, + &$result + ) { // 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. @@ -1111,15 +1112,14 @@ private function completeObjectValue(ObjectType $returnType, $fieldNodes, Resolv } /** - * @param mixed[] $result - * @param FieldNode[] $fieldNodes + * @param array $result * * @return Error */ private function invalidReturnTypeError( ObjectType $returnType, $result, - $fieldNodes + ArrayObject $fieldNodes ) { return new Error( 'Expected value of type "' . $returnType->name . '" but got: ' . Utils::printSafe($result) . '.', @@ -1128,18 +1128,17 @@ private function invalidReturnTypeError( } /** - * @param FieldNode[] $fieldNodes - * @param mixed[] $path - * @param mixed $result + * @param array $path + * @param mixed $result * - * @return mixed[]|Promise|stdClass + * @return array|Promise|stdClass * * @throws Error */ private function collectAndExecuteSubfields( ObjectType $returnType, - $fieldNodes, - $path, + ArrayObject $fieldNodes, + array $path, &$result ) { $subFieldNodes = $this->collectSubFields($returnType, $fieldNodes); @@ -1151,10 +1150,8 @@ private function collectAndExecuteSubfields( * A memoized collection of relevant subfields with regard to the return * type. Memoizing ensures the subfields are not repeatedly calculated, which * saves overhead when resolving lists of values. - * - * @param object $fieldNodes */ - private function collectSubFields(ObjectType $returnType, $fieldNodes) : ArrayObject + private function collectSubFields(ObjectType $returnType, ArrayObject $fieldNodes) : ArrayObject { if (! isset($this->subFieldCache[$returnType])) { $this->subFieldCache[$returnType] = new SplObjectStorage(); @@ -1184,13 +1181,12 @@ private function collectSubFields(ObjectType $returnType, $fieldNodes) : ArrayOb * Implements the "Evaluating selection sets" section of the spec * for "read" mode. * - * @param mixed $rootValue - * @param mixed[] $path - * @param ArrayObject $fields + * @param mixed $rootValue + * @param array $path * - * @return Promise|stdClass|mixed[] + * @return Promise|stdClass|array */ - private function executeFields(ObjectType $parentType, $rootValue, $path, $fields) + private function executeFields(ObjectType $parentType, $rootValue, array $path, ArrayObject $fields) { $containsPromise = false; $results = []; @@ -1218,11 +1214,13 @@ private function executeFields(ObjectType $parentType, $rootValue, $path, $field } /** + * Differentiate empty objects from empty lists. + * * @see https://github.com/webonyx/graphql-php/issues/59 * - * @param mixed[] $results + * @param array|mixed $results * - * @return stdClass|mixed[] + * @return array|stdClass|mixed */ private static function fixResultsIfEmptyArray($results) { @@ -1234,17 +1232,12 @@ private static function fixResultsIfEmptyArray($results) } /** - * This function transforms a PHP `array` into - * a `Promise>` + * Transform an associative array with Promises to a Promise which resolves to an + * associative array where all Promises were resolved. * - * In other words it returns a promise which resolves to normal PHP associative array which doesn't contain - * any promises. - * - * @param (string|Promise)[] $assoc - * - * @return mixed + * @param array $assoc */ - private function promiseForAssocArray(array $assoc) + private function promiseForAssocArray(array $assoc) : Promise { $keys = array_keys($assoc); $valuesAndPromises = array_values($assoc); @@ -1264,15 +1257,13 @@ private function promiseForAssocArray(array $assoc) * @param string|ObjectType|null $runtimeTypeOrName * @param InterfaceType|UnionType $returnType * @param mixed $result - * - * @return ObjectType */ private function ensureValidRuntimeType( $runtimeTypeOrName, AbstractType $returnType, ResolveInfo $info, &$result - ) { + ) : ObjectType { $runtimeType = is_string($runtimeTypeOrName) ? $this->exeContext->schema->getType($runtimeTypeOrName) : $runtimeTypeOrName;