diff --git a/src/Execution/Execution.php b/src/Execution/Execution.php index ae887b25..c2f49c60 100644 --- a/src/Execution/Execution.php +++ b/src/Execution/Execution.php @@ -4,11 +4,7 @@ use Digia\GraphQL\Error\GraphQLError; use Digia\GraphQL\Language\AST\Node\DocumentNode; -use Digia\GraphQL\Language\AST\Node\FieldNode; -use Digia\GraphQL\Language\AST\Node\OperationDefinitionNode; -use Digia\GraphQL\Language\AST\Node\SelectionSetNode; use Digia\GraphQL\Language\AST\NodeKindEnum; -use Digia\GraphQL\Type\Definition\ObjectType; use Digia\GraphQL\Type\Schema; /** @@ -49,8 +45,7 @@ public static function execute( $variableValues = null, $operationName = null, callable $fieldResolver = null - ) - { + ) { try { $context = self::buildExecutionContext( $schema, @@ -62,19 +57,16 @@ public static function execute( $fieldResolver ); } catch (GraphQLError $error) { - return new ExecutionResult(null, [$error]); + return new ExecutionResult(['data' => null], [$error]); } - $executor = new self($context); + $data = $context->getExecutionStrategy()->execute(); - return $executor->executeOperation( - $context, - $context->getOperation(), - $rootValue - ); + return new ExecutionResult($data, $context->getErrors()); } /** + * @TODO: Consider to create a ExecutionContextBuilder * @param Schema $schema * @param DocumentNode $documentNode * @param $rootValue @@ -93,12 +85,11 @@ private static function buildExecutionContext( $rawVariableValues, $operationName = null, callable $fieldResolver = null - ): ExecutionContext - { + ): ExecutionContext { //TODO: Validate raw variables, operation name etc. //TODO: Validate document definition - $errors = []; + $errors = []; $fragments = []; $operation = null; @@ -110,17 +101,18 @@ private static function buildExecutionContext( 'Must provide operation name if query contains multiple operations.' ); } + if (!$operationName || (!empty($definition->getName()) && $definition->getName()->getValue() === $operationName)) { $operation = $definition; } break; case NodeKindEnum::FRAGMENT_DEFINITION: + case NodeKindEnum::FRAGMENT_SPREAD: $fragments[$definition->getName()->getValue()] = $definition; break; default: throw new GraphQLError( - "GraphQL cannot execute a request containing a {$definition->getKind()}.", - [$definition] + "GraphQL cannot execute a request containing a {$definition->getKind()}." ); } } @@ -138,107 +130,4 @@ private static function buildExecutionContext( return $executionContext; } - - /** - * @param ExecutionContext $context - * @param OperationDefinitionNode $operation - * @param $rootValue - * - * @return ExecutionResult - */ - private function executeOperation( - ExecutionContext $context, - OperationDefinitionNode $operation, - $rootValue - ): ExecutionResult - { - //MUTATION - //SUBSCRIPTION - //QUERY - - //result = executionStrategy.execute(executionContext, parameters); - // type: query|mutation|suscription - $query = $context->getSchema()->getQuery(); - $fields = $this->collectFields($query, $operation->getSelectionSet(), [], []); - $path = []; - - if ($context->getOperation()->getName()->getValue() === 'query') { - $data = $this->executeFields($query, $rootValue, $path, $fields); - - return new ExecutionResult($data, []); - } - - return new ExecutionResult([], []); - } - - private function collectFields( - ObjectType $runtimeType, - SelectionSetNode $selectionSet, - $fields, - $visitedFragmentNames - ) - { - foreach ($selectionSet->getSelections() as $selection) { - switch ($selection->getKind()) { - case NodeKindEnum::FIELD: - /** @var FieldNode $selection */ - $name = $selection->getName()->getValue(); - $fields[$name][] = $selection; - break; - } - } - - return $fields; - } - - /** - * Implements the "Evaluating selection sets" section of the spec - * for "read" mode. - * @param ObjectType $parentType - * @param $source - * @param $path - * @param $fields - * - * @return array - */ - private function executeFields(ObjectType $parentType, $source, $path, $fields): array - { - $finalResults = []; - foreach ($fields as $responseName => $fieldNodes) { - $fieldPath = $path; - $fieldPath[] = $responseName; - - $result = $this->resolveField($parentType, $source, $fieldNodes, $fieldPath); - - $finalResults[$responseName] = $result; - } - - return $finalResults; - } - - /** - * @param ObjectType $parentType - * @param $source - * @param $fieldNodes - * @param $path - * - * @return array|\Exception|mixed|null - */ - private function resolveField(ObjectType $parentType, $source, $fieldNodes, $path) - { - /** @var FieldNode $fieldNode */ - $fieldNode = $fieldNodes[0]; - - $field = $parentType->getFields()[$fieldNode->getName()->getValue()]; - - $inputValues = $fieldNode->getArguments() ?? []; - - $args = []; - - foreach($inputValues as $value) { - $args[] = $value->getDefaultValue()->getValue(); - } - - return $field->resolve(...$args); - } } diff --git a/src/Execution/ExecutionContext.php b/src/Execution/ExecutionContext.php index d4dfc170..7fa4d669 100644 --- a/src/Execution/ExecutionContext.php +++ b/src/Execution/ExecutionContext.php @@ -97,6 +97,25 @@ public function getSchema(): Schema return $this->schema; } + /** + * @return array|FragmentDefinitionNode[] + */ + public function getFragments() + { + return $this->fragments; + } + + /** + * Create proper ExecutionStrategy when needed + * + * @return ExecutionStrategy + */ + public function getExecutionStrategy(): ExecutionStrategy + { + //We can probably return different strategy in the future e.g:AsyncExecutionStrategy + return new ExecutorExecutionStrategy($this, $this->operation, $this->rootValue); + } + /** * @param GraphQLError $error * @return ExecutionContext @@ -106,4 +125,12 @@ public function addError(GraphQLError $error) $this->errors[] = $error; return $this; } + + /** + * @return array|GraphQLError[] + */ + public function getErrors() + { + return $this->errors; + } } diff --git a/src/Execution/ExecutionResult.php b/src/Execution/ExecutionResult.php index 7eda2434..413f128c 100644 --- a/src/Execution/ExecutionResult.php +++ b/src/Execution/ExecutionResult.php @@ -27,6 +27,14 @@ public function __construct(array $data, array $errors) $this->data = $data; } + /** + * @return array|GraphQLError[] + */ + public function getErrors(): array + { + return $this->errors; + } + /** * @param GraphQLError $error * @return ExecutionResult diff --git a/src/Execution/ExecutionStrategy.php b/src/Execution/ExecutionStrategy.php new file mode 100644 index 00000000..70b2fd90 --- /dev/null +++ b/src/Execution/ExecutionStrategy.php @@ -0,0 +1,210 @@ +context = $context; + $this->operation = $operation; + $this->rootValue = $rootValue; + } + + /** + * @return array|null + */ + abstract function execute(): ?array; + + /** + * @param ObjectType $runtimeType + * @param SelectionSetNode $selectionSet + * @param $fields + * @param $visitedFragmentNames + * @return \ArrayObject + */ + protected function collectFields( + ObjectType $runtimeType, + SelectionSetNode $selectionSet, + $fields, + $visitedFragmentNames + ) { + foreach ($selectionSet->getSelections() as $selection) { + /** @var FieldNode $selection */ + switch ($selection->getKind()) { + case NodeKindEnum::FIELD: + $name = $selection->getNameValue(); + if (!isset($fields[$name])) { + $fields[$name] = new \ArrayObject(); + } + $fields[$name][] = $selection; + break; + case NodeKindEnum::INLINE_FRAGMENT: + //TODO check if should include this node + $this->collectFields( + $runtimeType, + $selection->getSelectionSet(), + $fields, + $visitedFragmentNames + ); + break; + case NodeKindEnum::FRAGMENT_SPREAD: + //TODO check if should include this node + if (!empty($visitedFragmentNames[$selection->getNameValue()])) { + continue; + } + $visitedFragmentNames[$selection->getNameValue()] = true; + /** @var FragmentDefinitionNode $fragment */ + $fragment = $this->context->getFragments()[$selection->getNameValue()]; + $this->collectFields( + $runtimeType, + $fragment->getSelectionSet(), + $fields, + $visitedFragmentNames + ); + break; + } + } + + return $fields; + } + + /** + * Implements the "Evaluating selection sets" section of the spec + * for "read" mode. + * @param ObjectType $parentType + * @param $source + * @param $path + * @param $fields + * + * @return array + * + * @throws GraphQLError|\Exception + */ + protected function executeFields( + ObjectType $parentType, + $source, + $path, + $fields): array + { + $finalResults = []; + + foreach ($fields as $fieldName => $fieldNodes) { + $fieldPath = $path; + $fieldPath[] = $fieldName; + if (!$this->isDefinedField($parentType, $fieldName)) { + continue; + } + + $result = $this->resolveField($parentType, + $source, + $fieldNodes, + $fieldPath + ); + + $finalResults[$fieldName] = $result; + } + + return $finalResults; + } + + /** + * @param ObjectType $parentType + * @param string $fieldName + * @return bool + * @throws \Exception + */ + protected function isDefinedField(ObjectType $parentType, string $fieldName) + { + return isset($parentType->getFields()[$fieldName]); + } + + /** + * @param ObjectType $parentType + * @param $source + * @param $fieldNodes + * @param $path + * + * @return mixed + * + * @throws GraphQLError|\Exception + */ + protected function resolveField( + ObjectType $parentType, + $source, + $fieldNodes, + $path) + { + /** @var FieldNode $fieldNode */ + $fieldNode = $fieldNodes[0]; + + $field = $parentType->getFields()[$fieldNode->getNameValue()]; + + $inputValues = $fieldNode->getArguments() ?? []; + + $args = []; + + foreach ($inputValues as $value) { + if ($value instanceof ArgumentNode) { + $args[] = $value->getValue()->getValue(); + } elseif ($value instanceof InputValueDefinitionNode) { + $args[] = $value->getDefaultValue()->getValue(); + } + } + + $result = $field->resolve(...$args); + + //TODO Resolve sub fields + + return $result; + } + + private function completeValue( + $returnType, + $fieldNodes, + $info, + $path, + &$result) + { + + + } +} diff --git a/src/Execution/ExecutorExecutionStrategy.php b/src/Execution/ExecutorExecutionStrategy.php new file mode 100644 index 00000000..5f1ea23e --- /dev/null +++ b/src/Execution/ExecutorExecutionStrategy.php @@ -0,0 +1,36 @@ +context->getOperation()->getOperation(); + $schema = $this->context->getSchema(); + + $objectType = ($operation === 'mutation') + ? $schema->getMutation() + : $schema->getQuery(); + + $fields = $this->collectFields($objectType, $this->operation->getSelectionSet(), new \ArrayObject(), new \ArrayObject()); + $path = []; + + try { + $data = $this->executeFields($objectType, $this->rootValue, $path, $fields); + } catch (\Exception $ex) { + $this->context->addError( + new GraphQLError($ex->getMessage()) + ); + return null; + } + + return $data; + } +} diff --git a/src/Language/AST/Builder/FragmentSpreadBuilder.php b/src/Language/AST/Builder/FragmentSpreadBuilder.php index b7ade45d..96652c36 100644 --- a/src/Language/AST/Builder/FragmentSpreadBuilder.php +++ b/src/Language/AST/Builder/FragmentSpreadBuilder.php @@ -15,9 +15,10 @@ class FragmentSpreadBuilder extends AbstractBuilder public function build(array $ast): NodeInterface { return new FragmentSpreadNode([ - 'name' => $this->buildOne($ast, 'name'), - 'directives' => $this->buildMany($ast, 'directives'), - 'location' => $this->createLocation($ast), + 'name' => $this->buildOne($ast, 'name'), + 'directives' => $this->buildMany($ast, 'directives'), + 'selectionSet' => $this->buildOne($ast, 'selectionSet'), + 'location' => $this->createLocation($ast), ]); } diff --git a/src/Language/AST/Node/Contract/ValueNodeInterface.php b/src/Language/AST/Node/Contract/ValueNodeInterface.php index 5f3f3e76..b587ee58 100644 --- a/src/Language/AST/Node/Contract/ValueNodeInterface.php +++ b/src/Language/AST/Node/Contract/ValueNodeInterface.php @@ -4,5 +4,4 @@ interface ValueNodeInterface extends NodeInterface { - } diff --git a/src/Language/AST/Node/FragmentDefinitionNode.php b/src/Language/AST/Node/FragmentDefinitionNode.php index dbfe217f..f00d4543 100644 --- a/src/Language/AST/Node/FragmentDefinitionNode.php +++ b/src/Language/AST/Node/FragmentDefinitionNode.php @@ -2,6 +2,7 @@ namespace Digia\GraphQL\Language\AST\Node; +use Digia\GraphQL\Language\AST\Node\Behavior\SelectionSetTrait; use Digia\GraphQL\Language\AST\NodeKindEnum; use Digia\GraphQL\Language\AST\Node\Behavior\NameTrait; use Digia\GraphQL\Language\AST\Node\Behavior\TypeConditionTrait; @@ -14,6 +15,7 @@ class FragmentDefinitionNode extends AbstractNode implements ExecutableDefinitio use NameTrait; use TypeConditionTrait; use VariableDefinitionsTrait; + use SelectionSetTrait; /** * @var string diff --git a/src/Language/AST/Node/FragmentSpreadNode.php b/src/Language/AST/Node/FragmentSpreadNode.php index 5a595f4b..dc2e00f2 100644 --- a/src/Language/AST/Node/FragmentSpreadNode.php +++ b/src/Language/AST/Node/FragmentSpreadNode.php @@ -2,6 +2,7 @@ namespace Digia\GraphQL\Language\AST\Node; +use Digia\GraphQL\Language\AST\Node\Behavior\SelectionSetTrait; use Digia\GraphQL\Language\AST\NodeKindEnum; use Digia\GraphQL\Language\AST\Node\Behavior\DirectivesTrait; use Digia\GraphQL\Language\AST\Node\Behavior\NameTrait; @@ -12,6 +13,7 @@ class FragmentSpreadNode extends AbstractNode implements NodeInterface use NameTrait; use DirectivesTrait; + use SelectionSetTrait; /** * @var string diff --git a/src/Type/Definition/Behavior/FieldsTrait.php b/src/Type/Definition/Behavior/FieldsTrait.php index 908de395..b14f72ad 100644 --- a/src/Type/Definition/Behavior/FieldsTrait.php +++ b/src/Type/Definition/Behavior/FieldsTrait.php @@ -42,6 +42,8 @@ public function addField(Field $field) /** * @param array $fields * @return $this + * + * @throws \Exception */ public function addFields(array $fields) { diff --git a/src/Util/helpers.php b/src/Util/helpers.php index 62297ead..e9d05538 100644 --- a/src/Util/helpers.php +++ b/src/Util/helpers.php @@ -2,15 +2,17 @@ namespace Digia\GraphQL\Util; +use Digia\GraphQL\Error\GraphQLError; + /** * @param bool $condition * @param string $message - * @throws \Exception + * @throws GraphQLError */ function invariant(bool $condition, string $message) { if (!$condition) { - throw new \Exception($message); + throw new GraphQLError($message); } } diff --git a/tests/Functional/Excecution/ExecutionTest.php b/tests/Functional/Execution/ExecutionTest.php similarity index 67% rename from tests/Functional/Excecution/ExecutionTest.php rename to tests/Functional/Execution/ExecutionTest.php index 2b192e3a..00acfb71 100644 --- a/tests/Functional/Excecution/ExecutionTest.php +++ b/tests/Functional/Execution/ExecutionTest.php @@ -1,6 +1,6 @@ [ new OperationDefinitionNode([ - 'kind' => NodeKindEnum::OPERATION_DEFINITION, - 'name' => new NameNode([ + 'kind' => NodeKindEnum::OPERATION_DEFINITION, + 'name' => new NameNode([ 'value' => 'query', ]), - 'selectionSet' => new SelectionSetNode([ + 'selectionSet' => new SelectionSetNode([ 'selections' => [ new FieldNode([ - 'name' => new NameNode([ - 'value' => 'hello', + 'name' => new NameNode([ + 'value' => 'hello', 'location' => new Location( 15, 20, @@ -67,8 +68,8 @@ public function testExecuteHelloQuery() ]) ] ]), - 'operation' => 'query', - 'directives' => [], + 'operation' => 'query', + 'directives' => [], 'variableDefinitions' => [] ]) ], @@ -119,28 +120,27 @@ public function testExecuteQueryHelloWithArgs() $documentNode = new DocumentNode([ 'definitions' => [ new OperationDefinitionNode([ - 'kind' => NodeKindEnum::OPERATION_DEFINITION, - 'name' => new NameNode([ + 'kind' => NodeKindEnum::OPERATION_DEFINITION, + 'name' => new NameNode([ 'value' => 'query' ]), - 'selectionSet' => new SelectionSetNode([ + 'selectionSet' => new SelectionSetNode([ 'selections' => [ new FieldNode([ - 'name' => new NameNode([ - 'value' => 'greeting', + 'name' => new NameNode([ + 'value' => 'greeting', 'location' => new Location( 15, 20, - new Source('query Hello($name: String) {greeting(name: $name)}', 'GraphQL', - new SourceLocation()) + new Source('query Hello($name: String) {greeting(name: $name)}', 'GraphQL', new SourceLocation()) ) ]), 'arguments' => [ new InputValueDefinitionNode([ - 'name' => new NameNode([ + 'name' => new NameNode([ 'value' => 'name' ]), - 'type' => GraphQLString(), + 'type' => GraphQLString(), 'defaultValue' => new StringValueNode([ 'value' => 'Han Solo', ]), @@ -149,8 +149,8 @@ public function testExecuteQueryHelloWithArgs() ]) ] ]), - 'operation' => 'query', - 'directives' => [], + 'operation' => 'query', + 'directives' => [], 'variableDefinitions' => [] ]) ], @@ -189,32 +189,32 @@ public function testExecuteQueryWithMultipleFields() new ObjectType([ 'name' => 'Human', 'fields' => [ - 'id' => [ + 'id' => [ 'type' => GraphQLInt(), 'resolve' => function () { return 1000; } ], - 'type' => [ + 'type' => [ 'type' => GraphQLString(), 'resolve' => function () { return 'Human'; } ], - 'friends' => [ + 'friends' => [ 'type' => GraphQLList(GraphQLString()), 'resolve' => function () { return ['1002', '1003', '2000', '2001']; } ], - 'appearsIn' => [ - 'type' => GraphQLList(GraphQLInt()), + 'appearsIn' => [ + 'type' => GraphQLList(GraphQLInt()), 'resolve' => function () { return [4, 5, 6]; } ], 'homePlanet' => [ - 'type' => GraphQLString(), + 'type' => GraphQLString(), 'resolve' => function () { return 'Tatooine'; } @@ -226,71 +226,66 @@ public function testExecuteQueryWithMultipleFields() $documentNode = new DocumentNode([ 'definitions' => [ new OperationDefinitionNode([ - 'kind' => NodeKindEnum::OPERATION_DEFINITION, - 'name' => new NameNode([ + 'kind' => NodeKindEnum::OPERATION_DEFINITION, + 'name' => new NameNode([ 'value' => 'query' ]), - 'selectionSet' => new SelectionSetNode([ + 'selectionSet' => new SelectionSetNode([ 'selections' => [ new FieldNode([ 'name' => new NameNode([ - 'value' => 'id', + 'value' => 'id', 'location' => new Location( 15, 20, - new Source('query Human {id, type, friends, appearsIn, homePlanet}', 'GraphQL', - new SourceLocation()) + new Source('query Human {id, type, friends, appearsIn, homePlanet}', 'GraphQL', new SourceLocation()) ) ]), ]), new FieldNode([ 'name' => new NameNode([ - 'value' => 'type', + 'value' => 'type', 'location' => new Location( 15, 20, - new Source('query Human {id, type, friends, appearsIn, homePlanet}', 'GraphQL', - new SourceLocation()) + new Source('query Human {id, type, friends, appearsIn, homePlanet}', 'GraphQL', new SourceLocation()) ) ]), ]), new FieldNode([ 'name' => new NameNode([ - 'value' => 'friends', + 'value' => 'friends', 'location' => new Location( 15, 20, - new Source('query Human {id, type, friends, appearsIn, homePlanet}', 'GraphQL', - new SourceLocation()) + new Source('query Human {id, type, friends, appearsIn, homePlanet}', 'GraphQL', new SourceLocation()) ) ]), ]), new FieldNode([ 'name' => new NameNode([ - 'value' => 'appearsIn', + 'value' => 'appearsIn', 'location' => new Location( 15, 20, - new Source('query Human {id, type, friends, appearsIn, homePlanet}', 'GraphQL', - new SourceLocation()) + new Source('query Human {id, type, friends, appearsIn, homePlanet}', 'GraphQL', new SourceLocation()) ) ]), ]), new FieldNode([ 'name' => new NameNode([ - 'value' => 'homePlanet', + 'value' => 'homePlanet', 'location' => new Location( 15, 20, - new Source('query Human {id, type, friends, appearsIn, homePlanet}', 'GraphQL', - new SourceLocation()) + new Source('query Human {id, type, friends, appearsIn, homePlanet}', 'GraphQL', new SourceLocation()) ) ]), ]) ] ]), - 'operation' => 'query', - 'directives' => [], + 'operation' => 'query', + 'directives' => [], 'variableDefinitions' => [] ]) ], @@ -317,10 +312,99 @@ public function testExecuteQueryWithMultipleFields() 'id' => 1000, 'type' => 'Human', 'friends' => ['1002', '1003', '2000', '2001'], - 'appearsIn' => [4, 5, 6], + 'appearsIn' => [4,5,6], 'homePlanet' => 'Tatooine' ], []); $this->assertEquals($expected, $executionResult); } + + /** + * @throws \Digia\GraphQL\Error\GraphQLError + * @throws \Exception + */ + public function testHandleFragments() + { + $documentNode = parser()->parse(new Source(' + { a, ...FragOne, ...FragTwo } + + fragment FragOne on Type { + b + deep { b, deeper: deep { b } } + } + + fragment FragTwo on Type { + c + deep { c, deeper: deep { c } } + }')); + + $Type = new ObjectType([ + 'name' => 'Type', + 'fields' => function() use (&$Type) { + return [ + 'a' => [ + 'type' => GraphQLString(), + 'resolve' => function () { + return 'Apple'; + } + ], + 'b' => [ + 'type' => GraphQLString(), + 'resolve' => function () { + return 'Banana'; + } + ], + 'c' => [ + 'type' => GraphQLString(), + 'resolve' => function () { + return 'Cherry'; + } + ], + 'deep' => [ + 'type' => $Type, + 'resolve' => function () { + return []; + } + ] + ]; + } + ]); + + $schema = new Schema([ + 'query' => $Type + ]); + + $rootValue = []; + $contextValue = ''; + $variableValues = []; + $operationName = ''; + $fieldResolver = null; + + /** @var ExecutionResult $executionResult */ + $executionResult = Execution::execute( + $schema, + $documentNode, + $rootValue, + $contextValue, + $variableValues, + $operationName, + $fieldResolver + ); + + $expected = new ExecutionResult([ + 'a' => 'Apple', + 'b' => 'Banana', + 'c' => 'Cherry', + 'deep' => [ +// 'b' => 'Banana', +// 'c' => 'Cherry', +// 'deeper' => [ +// 'b' => 'Banana', +// 'c' => 'Cherry' +// ] + ] + ], []); + + $this->assertEquals($expected, $executionResult); + } } diff --git a/tests/Functional/Execution/MutationTest.php b/tests/Functional/Execution/MutationTest.php new file mode 100644 index 00000000..cdcc1ff4 --- /dev/null +++ b/tests/Functional/Execution/MutationTest.php @@ -0,0 +1,148 @@ + + new ObjectType([ + 'name' => 'M', + 'fields' => [ + 'greeting' => [ + 'type' => new ObjectType([ + 'name' => 'GreetingType', + 'fields' => [ + 'message' => [ + 'type' => GraphQLString(), + ] + ], + ]), + 'resolve' => function ($name) { + return [ + 'message' => sprintf('Hello %s.', $name) + ]; + } + ] + ] + ]) + ]); + + /** @var DocumentNode $documentNode */ + $documentNode = parser()->parse(new Source(' + mutation M{ + greeting(name:"Han Solo") { + message + } + } + ')); + + $rootValue = []; + $contextValue = ''; + $variableValues = []; + $operationName = 'M'; + $fieldResolver = null; + + /** @var ExecutionResult $executionResult */ + $executionResult = Execution::execute( + $schema, + $documentNode, + $rootValue, + $contextValue, + $variableValues, + $operationName, + $fieldResolver + ); + + $expected = new ExecutionResult([ + 'greeting' => [ + 'message' => 'Hello Han Solo.' + ] + ], []); + + $this->assertEquals($expected, $executionResult); + } + + /** + * @throws \Digia\GraphQL\Error\GraphQLError + * @throws \Exception + */ + public function testDoesNotIncludeIllegalFieldsInOutput() + { + /** @var DocumentNode $documentNode */ + $documentNode = parser()->parse(new Source(' + mutation M { + thisIsIllegalDontIncludeMe + }' + )); + + $schema = GraphQLSchema([ + 'mutation' => + new ObjectType([ + 'name' => 'M', + 'fields' => [ + 'd' => [ + 'type' => GraphQLString(), + 'resolve' => function () { + return 'd'; + } + ] + ] + ]) + ]); + + $schema = GraphQLSchema([ + 'query' => new ObjectType([ + 'name' => 'Q', + 'fields' => [ + 'a' => ['type' => GraphQLString()], + ] + ]), + 'mutation' => new ObjectType([ + 'name' => 'M', + 'fields' => [ + 'c' => ['type' => GraphQLString()], + ] + ]) + ]); + + + $rootValue = []; + $contextValue = ''; + $variableValues = []; + $operationName = 'M'; + $fieldResolver = null; + + + /** @var ExecutionResult $executionResult */ + $executionResult = Execution::execute( + $schema, + $documentNode, + $rootValue, + $contextValue, + $variableValues, + $operationName, + $fieldResolver + ); + + $expected = new ExecutionResult([], []); + + $this->assertEquals($expected, $executionResult); + } +}