Allow providing default field resolver for execution call

This commit is contained in:
Vladimir Razuvaev 2017-07-05 16:22:01 +07:00
parent 9f4980ce49
commit 78d9ba0d5e
4 changed files with 147 additions and 10 deletions

View File

@ -44,12 +44,17 @@ class ExecutionContext
*/ */
public $variableValues; public $variableValues;
/**
* @var callable
*/
public $fieldResolver;
/** /**
* @var array * @var array
*/ */
public $errors; public $errors;
public function __construct($schema, $fragments, $root, $contextValue, $operation, $variables, $errors) public function __construct($schema, $fragments, $root, $contextValue, $operation, $variables, $errors, $fieldResolver)
{ {
$this->schema = $schema; $this->schema = $schema;
$this->fragments = $fragments; $this->fragments = $fragments;
@ -58,6 +63,7 @@ class ExecutionContext
$this->operation = $operation; $this->operation = $operation;
$this->variableValues = $variables; $this->variableValues = $variables;
$this->errors = $errors ?: []; $this->errors = $errors ?: [];
$this->fieldResolver = $fieldResolver;
} }
public function addError(Error $error) public function addError(Error $error)

View File

@ -89,9 +89,18 @@ class Executor
* @param $contextValue * @param $contextValue
* @param array|\ArrayAccess $variableValues * @param array|\ArrayAccess $variableValues
* @param null $operationName * @param null $operationName
* @param callable $fieldResolver
* @return ExecutionResult|Promise * @return ExecutionResult|Promise
*/ */
public static function execute(Schema $schema, DocumentNode $ast, $rootValue = null, $contextValue = null, $variableValues = null, $operationName = null) public static function execute(
Schema $schema,
DocumentNode $ast,
$rootValue = null,
$contextValue = null,
$variableValues = null,
$operationName = null,
$fieldResolver = null
)
{ {
if (null !== $variableValues) { if (null !== $variableValues) {
Utils::invariant( Utils::invariant(
@ -106,7 +115,15 @@ class Executor
); );
} }
$exeContext = self::buildExecutionContext($schema, $ast, $rootValue, $contextValue, $variableValues, $operationName); $exeContext = self::buildExecutionContext(
$schema,
$ast,
$rootValue,
$contextValue,
$variableValues,
$operationName,
$fieldResolver
);
$promiseAdapter = self::getPromiseAdapter(); $promiseAdapter = self::getPromiseAdapter();
$executor = new self($exeContext, $promiseAdapter); $executor = new self($exeContext, $promiseAdapter);
@ -129,6 +146,8 @@ class Executor
* @param $contextValue * @param $contextValue
* @param $rawVariableValues * @param $rawVariableValues
* @param string $operationName * @param string $operationName
* @param callable $fieldResolver
*
* @return ExecutionContext * @return ExecutionContext
* @throws Error * @throws Error
*/ */
@ -138,7 +157,8 @@ class Executor
$rootValue, $rootValue,
$contextValue, $contextValue,
$rawVariableValues, $rawVariableValues,
$operationName = null $operationName = null,
$fieldResolver = null
) )
{ {
$errors = []; $errors = [];
@ -183,7 +203,16 @@ class Executor
$rawVariableValues ?: [] $rawVariableValues ?: []
); );
$exeContext = new ExecutionContext($schema, $fragments, $rootValue, $contextValue, $operation, $variableValues, $errors); $exeContext = new ExecutionContext(
$schema,
$fragments,
$rootValue,
$contextValue,
$operation,
$variableValues,
$errors,
$fieldResolver ?: self::$defaultFieldResolver
);
return $exeContext; return $exeContext;
} }
@ -625,7 +654,7 @@ class Executor
} else if (isset($parentType->resolveFieldFn)) { } else if (isset($parentType->resolveFieldFn)) {
$resolveFn = $parentType->resolveFieldFn; $resolveFn = $parentType->resolveFieldFn;
} else { } else {
$resolveFn = self::$defaultFieldResolver; $resolveFn = $this->exeContext->fieldResolver;
} }
// The resolve function's optional third argument is a context value that // The resolve function's optional third argument is a context value that

View File

@ -17,16 +17,60 @@ use GraphQL\Validator\Rules\QueryComplexity;
class GraphQL class GraphQL
{ {
/** /**
* This is the primary entry point function for fulfilling GraphQL operations
* by parsing, validating, and executing a GraphQL document along side a
* GraphQL schema.
*
* More sophisticated GraphQL servers, such as those which persist queries,
* may wish to separate the validation and execution phases to a static time
* tooling step, and a server runtime step.
*
* schema:
* The GraphQL type system to use when validating and executing a query.
* requestString:
* A GraphQL language formatted string representing the requested operation.
* rootValue:
* The value provided as the first argument to resolver functions on the top
* level type (e.g. the query object type).
* variableValues:
* A mapping of variable name to runtime value to use for all variables
* defined in the requestString.
* operationName:
* The name of the operation to use if requestString contains multiple
* possible operations. Can be omitted if requestString contains only
* one operation.
* fieldResolver:
* A resolver function to use when one is not provided by the schema.
* If not provided, the default field resolver is used (which looks for a
* value or method on the source value with the field's name).
*
* @param Schema $schema * @param Schema $schema
* @param string|DocumentNode $requestString * @param string|DocumentNode $requestString
* @param mixed $rootValue * @param mixed $rootValue
* @param array|null $variableValues * @param array|null $variableValues
* @param string|null $operationName * @param string|null $operationName
* @param callable $fieldResolver
* @return Promise|array * @return Promise|array
*/ */
public static function execute(Schema $schema, $requestString, $rootValue = null, $contextValue = null, $variableValues = null, $operationName = null) public static function execute(
Schema $schema,
$requestString,
$rootValue = null,
$contextValue = null,
$variableValues = null,
$operationName = null,
$fieldResolver = null
)
{ {
$result = self::executeAndReturnResult($schema, $requestString, $rootValue, $contextValue, $variableValues, $operationName); $result = self::executeAndReturnResult(
$schema,
$requestString,
$rootValue,
$contextValue,
$variableValues,
$operationName,
$fieldResolver
);
if ($result instanceof ExecutionResult) { if ($result instanceof ExecutionResult) {
return $result->toArray(); return $result->toArray();
@ -40,14 +84,26 @@ class GraphQL
} }
/** /**
* Same as `execute`, but returns instance of ExecutionResult instead of array,
* which can be used for custom error formatting or adding extensions to response
*
* @param Schema $schema * @param Schema $schema
* @param string|DocumentNode $requestString * @param string|DocumentNode $requestString
* @param mixed $rootValue * @param mixed $rootValue
* @param array|null $variableValues * @param array|null $variableValues
* @param string|null $operationName * @param string|null $operationName
* @param callable $fieldResolver
* @return ExecutionResult|Promise * @return ExecutionResult|Promise
*/ */
public static function executeAndReturnResult(Schema $schema, $requestString, $rootValue = null, $contextValue = null, $variableValues = null, $operationName = null) public static function executeAndReturnResult(
Schema $schema,
$requestString,
$rootValue = null,
$contextValue = null,
$variableValues = null,
$operationName = null,
$fieldResolver = null
)
{ {
try { try {
if ($requestString instanceof DocumentNode) { if ($requestString instanceof DocumentNode) {
@ -66,7 +122,15 @@ class GraphQL
if (!empty($validationErrors)) { if (!empty($validationErrors)) {
return new ExecutionResult(null, $validationErrors); return new ExecutionResult(null, $validationErrors);
} else { } else {
return Executor::execute($schema, $documentNode, $rootValue, $contextValue, $variableValues, $operationName); return Executor::execute(
$schema,
$documentNode,
$rootValue,
$contextValue,
$variableValues,
$operationName,
$fieldResolver
);
} }
} catch (Error $e) { } catch (Error $e) {
return new ExecutionResult(null, [$e]); return new ExecutionResult(null, [$e]);

View File

@ -972,6 +972,44 @@ class ExecutorTest extends \PHPUnit_Framework_TestCase
} }
} }
/**
* @it uses a custom field resolver
*/
public function testUsesACustomFieldResolver()
{
$query = Parser::parse('{ foo }');
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'fields' => [
'foo' => ['type' => Type::string()]
]
])
]);
// For the purposes of test, just return the name of the field!
$customResolver = function ($source, $args, $context, ResolveInfo $info) {
return $info->fieldName;
};
$result = Executor::execute(
$schema,
$query,
null,
null,
null,
null,
$customResolver
);
$expected = [
'data' => ['foo' => 'foo']
];
$this->assertEquals($expected, $result->toArray());
}
public function testSubstitutesArgumentWithDefaultValue() public function testSubstitutesArgumentWithDefaultValue()
{ {
$schema = new Schema([ $schema = new Schema([