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;
/**
* @var callable
*/
public $fieldResolver;
/**
* @var array
*/
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->fragments = $fragments;
@ -58,6 +63,7 @@ class ExecutionContext
$this->operation = $operation;
$this->variableValues = $variables;
$this->errors = $errors ?: [];
$this->fieldResolver = $fieldResolver;
}
public function addError(Error $error)

View File

@ -89,9 +89,18 @@ class Executor
* @param $contextValue
* @param array|\ArrayAccess $variableValues
* @param null $operationName
* @param callable $fieldResolver
* @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) {
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();
$executor = new self($exeContext, $promiseAdapter);
@ -129,6 +146,8 @@ class Executor
* @param $contextValue
* @param $rawVariableValues
* @param string $operationName
* @param callable $fieldResolver
*
* @return ExecutionContext
* @throws Error
*/
@ -138,7 +157,8 @@ class Executor
$rootValue,
$contextValue,
$rawVariableValues,
$operationName = null
$operationName = null,
$fieldResolver = null
)
{
$errors = [];
@ -183,7 +203,16 @@ class Executor
$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;
}
@ -625,7 +654,7 @@ class Executor
} else if (isset($parentType->resolveFieldFn)) {
$resolveFn = $parentType->resolveFieldFn;
} else {
$resolveFn = self::$defaultFieldResolver;
$resolveFn = $this->exeContext->fieldResolver;
}
// 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
{
/**
* 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 string|DocumentNode $requestString
* @param mixed $rootValue
* @param array|null $variableValues
* @param string|null $operationName
* @param callable $fieldResolver
* @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) {
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 string|DocumentNode $requestString
* @param mixed $rootValue
* @param array|null $variableValues
* @param string|null $operationName
* @param callable $fieldResolver
* @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 {
if ($requestString instanceof DocumentNode) {
@ -66,7 +122,15 @@ class GraphQL
if (!empty($validationErrors)) {
return new ExecutionResult(null, $validationErrors);
} else {
return Executor::execute($schema, $documentNode, $rootValue, $contextValue, $variableValues, $operationName);
return Executor::execute(
$schema,
$documentNode,
$rootValue,
$contextValue,
$variableValues,
$operationName,
$fieldResolver
);
}
} catch (Error $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()
{
$schema = new Schema([