From df0d8e58ee79cac0a9ba08d964445166b5d7b256 Mon Sep 17 00:00:00 2001 From: spawnia Date: Fri, 19 Oct 2018 17:32:46 +0200 Subject: [PATCH] Fix the default namespace resolution --- src/Execution/QueryFilter.php | 2 +- src/Schema/AST/ASTBuilder.php | 10 +- src/Schema/AST/DocumentAST.php | 87 ++++++---- src/Schema/DirectiveRegistry.php | 53 ++++--- src/Schema/Directives/BaseDirective.php | 60 ++++--- .../Directives/Fields/CacheDirective.php | 2 +- .../Directives/Fields/ComplexityDirective.php | 10 +- .../Directives/Fields/FieldDirective.php | 33 ++-- .../Directives/Fields/GlobalIdDirective.php | 2 +- .../Directives/Fields/InjectDirective.php | 4 +- .../Directives/Fields/MiddlewareDirective.php | 6 +- .../Directives/Fields/PaginateDirective.php | 2 +- src/Schema/Directives/Nodes/NodeDirective.php | 2 +- src/Schema/Factories/FieldFactory.php | 100 ++---------- src/Schema/Factories/NodeFactory.php | 8 +- src/Schema/Values/CacheValue.php | 6 +- src/Schema/Values/FieldValue.php | 150 ++++++++++++------ src/Support/Traits/CanParseResolvers.php | 8 +- src/Support/Traits/CreatesPaginators.php | 6 +- src/Support/Traits/HandlesQueries.php | 2 +- src/Support/helpers.php | 26 +++ tests/Integration/Events/BuildingASTTest.php | 8 +- .../Fields/BelongsToManyDirectiveTest.php | 2 +- .../Directives/Fields/CreateDirectiveTest.php | 18 +-- .../Directives/Fields/DeleteDirectiveTest.php | 30 +--- .../Fields/HasManyDirectiveTest.php | 2 +- .../Fields/PaginateDirectiveTest.php | 6 +- .../Directives/Fields/UpdateDirectiveTest.php | 18 +-- .../Integration/Schema/NodeInterfaceTest.php | 12 +- tests/TestCase.php | 24 ++- tests/Unit/Schema/DirectiveRegistryTest.php | 2 +- .../Directives/Fields/CanDirectiveTest.php | 16 +- .../Fields/ComplexityDirectiveTest.php | 4 +- .../Directives/Fields/FieldDirectiveTest.php | 27 +++- .../Fields/MiddlewareDirectiveTest.php | 8 +- .../Fields/PaginateDirectiveTest.php | 10 +- .../Directives/Nodes/GroupDirectiveTest.php | 12 +- .../Schema/Factories/ValueFactoryTest.php | 43 +++-- tests/Unit/Schema/SchemaBuilderTest.php | 60 ++----- tests/Unit/Schema/SecurityTest.php | 10 +- tests/Utils/Mutations/Foo.php | 35 ++++ tests/Utils/Queries/Foo.php | 35 ++++ tests/Utils/Queries/FooBar.php | 42 +++++ 43 files changed, 547 insertions(+), 456 deletions(-) create mode 100644 tests/Utils/Mutations/Foo.php create mode 100644 tests/Utils/Queries/Foo.php create mode 100644 tests/Utils/Queries/FooBar.php diff --git a/src/Execution/QueryFilter.php b/src/Execution/QueryFilter.php index 2ef8c568af..3e9e9c2abf 100644 --- a/src/Execution/QueryFilter.php +++ b/src/Execution/QueryFilter.php @@ -41,7 +41,7 @@ class QueryFilter public static function getInstance(FieldValue $value): QueryFilter { $handler = static::QUERY_FILTER_KEY - . '.' . strtolower($value->getNodeName()) + . '.' . strtolower($value->getParentName()) . '.' . strtolower($value->getFieldName()); // Get an existing instance or register a new one diff --git a/src/Schema/AST/ASTBuilder.php b/src/Schema/AST/ASTBuilder.php index 6f72beb4ae..3b87b2c34d 100644 --- a/src/Schema/AST/ASTBuilder.php +++ b/src/Schema/AST/ASTBuilder.php @@ -54,9 +54,11 @@ public static function generate(string $schema): DocumentAST protected static function applyNodeManipulators(DocumentAST $document): DocumentAST { return $document - ->typeExtensionDefinitions() - // This is just temporarily merged together - ->concat($document->typeDefinitions()) + ->typeDefinitions() + // Iterate over both of those at once, as it does not matter at this point + ->concat( + $document->typeExtensions() + ) ->reduce( function (DocumentAST $document, Node $node) { $nodeManipulators = resolve(DirectiveRegistry::class)->nodeManipulators($node); @@ -86,7 +88,7 @@ function (ObjectTypeDefinitionNode $objectType) use ($document) { $name = $objectType->name->value; $objectType = $document - ->typeExtensionDefinitions($name) + ->extensionsForType($name) ->reduce( function (ObjectTypeDefinitionNode $relatedObjectType, ObjectTypeExtensionNode $typeExtension) { $relatedObjectType->fields = ASTHelper::mergeUniqueNodeList( diff --git a/src/Schema/AST/DocumentAST.php b/src/Schema/AST/DocumentAST.php index c0c78c8e9b..7e70344d88 100644 --- a/src/Schema/AST/DocumentAST.php +++ b/src/Schema/AST/DocumentAST.php @@ -35,25 +35,48 @@ class DocumentAST implements \Serializable * * @var Collection */ - protected $typeExtensions; - + protected $typeExtensionsMap; + /** * @param DocumentNode $documentNode */ public function __construct(DocumentNode $documentNode) { // We can not store type extensions in the map, since they do not have unique names - list($this->typeExtensions, $definitionNodes) = collect($documentNode->definitions) + list($typeExtensions, $definitionNodes) = collect($documentNode->definitions) ->partition(function(DefinitionNode $definitionNode){ return $definitionNode instanceof TypeExtensionNode; }); - + + $this->typeExtensionsMap = $typeExtensions + ->mapWithKeys(function(TypeExtensionNode $node){ + return [$this->typeExtensionUniqueKey($node) => $node]; + }); + $this->definitionMap = $definitionNodes ->mapWithKeys(function(DefinitionNode $node){ return [$node->name->value => $node]; }); } - + + /** + * Return a unique key that identifies a type extension. + * + * @param TypeExtensionNode $typeExtensionNode + * + * @return string + */ + protected function typeExtensionUniqueKey(TypeExtensionNode $typeExtensionNode): string + { + $fieldNames = collect($typeExtensionNode->fields) + ->map(function($field){ + return $field->name->value; + }) + ->implode(':'); + + return $typeExtensionNode->name->value . $fieldNames; + } + /** * Create a new DocumentAST instance from a schema. * @@ -89,16 +112,12 @@ public static function fromSource(string $schema): DocumentAST */ public function serialize(): string { - return \serialize([ - 'definitionMap' => $this->definitionMap + return \serialize( + $this->definitionMap ->mapWithKeys(function(DefinitionNode $node, string $key){ return [$key => AST::toArray($node)]; - }), - 'typeExtensions' => $this->typeExtensions - ->map(function(TypeExtensionNode $node){ - return AST::toArray($node); }) - ]); + ); } /** @@ -110,16 +129,10 @@ public function serialize(): string */ public function unserialize($serialized) { - $unserialize = \unserialize($serialized); - - $this->definitionMap = $unserialize['definitionMap'] + $this->definitionMap = \unserialize($serialized) ->mapWithKeys(function(array $node, string $key){ return [$key => AST::fromArray($node)]; }); - $this->typeExtensions = $unserialize['typeExtensions'] - ->map(function(array $node){ - return AST::fromArray($node); - }); } /** @@ -151,28 +164,30 @@ public function directiveDefinitions(): Collection } /** - * Get all definitions for type extensions. - * - * Without a name, it simply return all TypeExtensions. - * If a name is given, it may return multiple type extensions - * that apply to a named type. + * Get all extensions that apply to a named type. * - * @param string|null $extendedTypeName + * @param string $extendedTypeName * * @return Collection */ - public function typeExtensionDefinitions(string $extendedTypeName = null): Collection + public function extensionsForType(string $extendedTypeName): Collection { - if(is_null($extendedTypeName)){ - return $this->typeExtensions; - } - - return $this->typeExtensions + return $this->typeExtensionsMap ->filter(function (TypeExtensionNode $typeExtension) use ($extendedTypeName): bool { return $extendedTypeName === $typeExtension->name->value; }); } + /** + * Return all the type extensions. + * + * @return Collection + */ + public function typeExtensions(): Collection + { + return $this->typeExtensionsMap; + } + /** * Get all definitions for object types. * @@ -290,7 +305,7 @@ public function addFieldToQueryType(FieldDefinitionNode $field): DocumentAST return $this; } - + /** * @param DefinitionNode $newDefinition * @@ -299,9 +314,11 @@ public function addFieldToQueryType(FieldDefinitionNode $field): DocumentAST public function setDefinition(DefinitionNode $newDefinition): DocumentAST { if($newDefinition instanceof TypeExtensionNode){ - if(! $this->typeExtensions->search($newDefinition, true)) { - $this->typeExtensions->push($newDefinition); - } + + $this->typeExtensionsMap->put( + $this->typeExtensionUniqueKey($newDefinition), + $newDefinition + ); } else { $this->definitionMap->put( $newDefinition->name->value, diff --git a/src/Schema/DirectiveRegistry.php b/src/Schema/DirectiveRegistry.php index 17965b11bc..4f0c045a15 100644 --- a/src/Schema/DirectiveRegistry.php +++ b/src/Schema/DirectiveRegistry.php @@ -184,6 +184,34 @@ protected function associatedDirectivesOfType(Node $node, string $directiveClass }); } + /** + * Get a single directive of a type that belongs to an AST node. + * + * Use this for directives types that can only occur once, such as field resolvers. + * This throws if more than one such directive is found. + * + * @param Node $node + * @param string $directiveClass + * + * @throws DirectiveException + * + * @return Directive|null + */ + protected function singleDirectiveOfType(Node $node, string $directiveClass) + { + $directives = $this->associatedDirectivesOfType($node, $directiveClass); + + if ($directives->count() > 1) { + $directiveNames = $directives->implode(', '); + + throw new DirectiveException( + "Node [{$node->name->value}] can only have one directive of type [{$directiveClass}] but found [{$directiveNames}]" + ); + } + + return $directives->first(); + } + /** * @param Node $node * @@ -225,17 +253,10 @@ public function argManipulators(InputValueDefinitionNode $inputValueDefinition): */ public function nodeResolver(TypeDefinitionNode $node) { - $resolvers = $this->associatedDirectivesOfType($node, NodeResolver::class); - - if ($resolvers->count() > 1) { - $resolverNames = $resolvers->implode(', '); - - throw new DirectiveException("Type [{$node->name->value}] has more then one resolver directive: [{$resolverNames}]"); - } - - return $resolvers->first(); + /** @noinspection PhpIncompatibleReturnTypeInspection */ + return $this->singleDirectiveOfType($node, NodeResolver::class); } - + /** * Check if the given node has a type resolver directive handler assigned to it. * @@ -287,17 +308,7 @@ public function hasFieldMiddleware($field): bool */ public function fieldResolver($field) { - $resolvers = $this->associatedDirectivesOfType($field, FieldResolver::class); - - if ($resolvers->count() > 1) { - $resolverNames = $resolvers->implode(', '); - - throw new DirectiveException( - "Field [{$field->name->value}] has more then one resolver directive: [{$resolverNames}]" - ); - } - - return $resolvers->first(); + return $this->singleDirectiveOfType($field, FieldResolver::class); } /** diff --git a/src/Schema/Directives/BaseDirective.php b/src/Schema/Directives/BaseDirective.php index a411a415df..348f7c7df3 100644 --- a/src/Schema/Directives/BaseDirective.php +++ b/src/Schema/Directives/BaseDirective.php @@ -91,32 +91,13 @@ public function directiveHasArgument(string $name): bool * * @return \Closure */ - public function getMethodArgument(string $argumentName): \Closure + public function getResolverFromArgument(string $argumentName): \Closure { - // A method argument is expected to contain a class and a method name, seperated by an @ symbol - // e.g. App\My\Class@methodName - $argumentParts = explode('@', $this->directiveArgValue($argumentName)); + list($className, $methodName) = $this->getMethodArgumentParts($argumentName); - if ( - count($argumentParts) !== 2 - || empty($argumentParts[0]) - || empty($argumentParts[1]) - ){ - throw new DirectiveException("Directive '{$this->name()}' must have an argument '{$argumentName}' with 'ClassName@methodName'"); - } - - $className = $this->namespaceClassName($argumentParts[0]); - $methodName = $argumentParts[1]; - - if (! method_exists($className, $methodName)) { - throw new DirectiveException("Method '{$methodName}' does not exist on class '{$className}'"); - } + $namespacedClassName = $this->namespaceClassName($className); - // TODO convert this back once we require PHP 7.1 - // return \Closure::fromCallable([resolve($className), $methodName]); - return function() use ($className, $methodName){ - return resolve($className)->{$methodName}(...func_get_args()); - }; + return \construct_resolver($namespacedClassName, $methodName); } /** @@ -181,4 +162,37 @@ protected function namespaceClassName(string $classCandidate, array $namespacesT return $className; } + + /** + * Split a single method argument into its parts. + * + * A method argument is expected to contain a class and a method name, separated by an @ symbol. + * e.g. "App\My\Class@methodName" + * This validates that exactly two parts are given and are not empty. + * + * @param string $argumentName + * + * @throws DirectiveException + * + * @return array [string $className, string $methodName] + */ + protected function getMethodArgumentParts(string $argumentName): array + { + $argumentParts = explode( + '@', + $this->directiveArgValue($argumentName) + ); + + if ( + count($argumentParts) !== 2 + || empty($argumentParts[0]) + || empty($argumentParts[1]) + ){ + throw new DirectiveException( + "Directive '{$this->name()}' must have an argument '{$argumentName}' in the form 'ClassName@methodName'" + ); + } + + return $argumentParts; + } } diff --git a/src/Schema/Directives/Fields/CacheDirective.php b/src/Schema/Directives/Fields/CacheDirective.php index eb89fc41f2..50bb496344 100644 --- a/src/Schema/Directives/Fields/CacheDirective.php +++ b/src/Schema/Directives/Fields/CacheDirective.php @@ -35,7 +35,7 @@ public function name(): string public function handleField(FieldValue $value, \Closure $next) { $this->setNodeKey( - $value->getNode() + $value->getParent() ); $value = $next($value); diff --git a/src/Schema/Directives/Fields/ComplexityDirective.php b/src/Schema/Directives/Fields/ComplexityDirective.php index 22296ec4e6..bd2155e0b1 100644 --- a/src/Schema/Directives/Fields/ComplexityDirective.php +++ b/src/Schema/Directives/Fields/ComplexityDirective.php @@ -36,12 +36,12 @@ public function handleField(FieldValue $value, \Closure $next): FieldValue return $next( $value->setComplexity( $this->directiveHasArgument('resolver') - ? $this->getMethodArgument('resolver') - : function ($childrenComplexity, $args) { - $complexity = array_get($args, 'first', array_get($args, 'count', 1)); + ? $this->getResolverFromArgument('resolver') + : function ($childrenComplexity, $args) { + $complexity = array_get($args, 'first', array_get($args, 'count', 1)); - return $childrenComplexity * $complexity; - } + return $childrenComplexity * $complexity; + } ) ); } diff --git a/src/Schema/Directives/Fields/FieldDirective.php b/src/Schema/Directives/Fields/FieldDirective.php index e6e208eb08..4e4542e11c 100644 --- a/src/Schema/Directives/Fields/FieldDirective.php +++ b/src/Schema/Directives/Fields/FieldDirective.php @@ -3,9 +3,10 @@ namespace Nuwave\Lighthouse\Schema\Directives\Fields; use Nuwave\Lighthouse\Schema\Values\FieldValue; +use Nuwave\Lighthouse\Exceptions\DirectiveException; +use Nuwave\Lighthouse\Exceptions\DefinitionException; use Nuwave\Lighthouse\Schema\Directives\BaseDirective; use Nuwave\Lighthouse\Support\Contracts\FieldResolver; -use Nuwave\Lighthouse\Exceptions\DirectiveException; class FieldDirective extends BaseDirective implements FieldResolver { @@ -22,40 +23,38 @@ public function name(): string /** * Resolve the field directive. * - * @param FieldValue $value + * @param FieldValue $fieldValue * * @throws DirectiveException + * @throws DefinitionException * * @return FieldValue */ - public function resolveField(FieldValue $value): FieldValue + public function resolveField(FieldValue $fieldValue): FieldValue { if($this->directiveHasArgument('resolver')){ - $resolver = $this->getMethodArgument('resolver'); + list($className, $methodName) = $this->getMethodArgumentParts('resolver'); } else { /** * @deprecated This behaviour will be removed in v3 * * The only way to define methods will be via the resolver: "Class@method" style */ - $className = $this->namespaceClassName( - $this->directiveArgValue('class') - ); + $className = $this->directiveArgValue('class'); $methodName = $this->directiveArgValue('method'); - if (! method_exists($className, $methodName)) { - throw new DirectiveException("Method '{$methodName}' does not exist on class '{$className}'"); - } - - // TODO convert this back once we require PHP 7.1 - // $resolver = \Closure::fromCallable([resolve($className), $methodName]); - $resolver = function() use ($className, $methodName){ - return resolve($className)->{$methodName}(...func_get_args()); - }; } + if($parentNamespace = $fieldValue->getDefaultNamespaceForParent()){ + $namespacedClassName = $this->namespaceClassName($className, [$parentNamespace]); + } else { + $namespacedClassName = $this->namespaceClassName($className); + } + + $resolver = construct_resolver($namespacedClassName, $methodName); + $additionalData = $this->directiveArgValue('args'); - return $value->setResolver( + return $fieldValue->setResolver( function ($root, array $args, $context = null, $info = null) use ($resolver, $additionalData) { return $resolver( $root, diff --git a/src/Schema/Directives/Fields/GlobalIdDirective.php b/src/Schema/Directives/Fields/GlobalIdDirective.php index e93663b8ea..286e63d3ba 100644 --- a/src/Schema/Directives/Fields/GlobalIdDirective.php +++ b/src/Schema/Directives/Fields/GlobalIdDirective.php @@ -32,7 +32,7 @@ public function name(): string */ public function handleField(FieldValue $value, \Closure $next): FieldValue { - $type = $value->getNodeName(); + $type = $value->getParentName(); $resolver = $value->getResolver(); return $next( diff --git a/src/Schema/Directives/Fields/InjectDirective.php b/src/Schema/Directives/Fields/InjectDirective.php index c49abbe8e4..fd22f43e22 100644 --- a/src/Schema/Directives/Fields/InjectDirective.php +++ b/src/Schema/Directives/Fields/InjectDirective.php @@ -35,14 +35,14 @@ public function handleField(FieldValue $value, \Closure $next): FieldValue $contextAttributeName = $this->directiveArgValue('context'); if (!$contextAttributeName) { throw new DirectiveException( - "The `inject` directive on {$value->getNodeName()} [{$value->getFieldName()}] must have a `context` argument" + "The `inject` directive on {$value->getParentName()} [{$value->getFieldName()}] must have a `context` argument" ); } $argumentName = $this->directiveArgValue('name'); if (!$argumentName) { throw new DirectiveException( - "The `inject` directive on {$value->getNodeName()} [{$value->getFieldName()}] must have a `name` argument" + "The `inject` directive on {$value->getParentName()} [{$value->getFieldName()}] must have a `name` argument" ); } diff --git a/src/Schema/Directives/Fields/MiddlewareDirective.php b/src/Schema/Directives/Fields/MiddlewareDirective.php index 2367508a06..8b012d62a4 100644 --- a/src/Schema/Directives/Fields/MiddlewareDirective.php +++ b/src/Schema/Directives/Fields/MiddlewareDirective.php @@ -34,12 +34,12 @@ public function handleField(FieldValue $value, \Closure $next) if ($checks) { $middlewareRegistry = resolve(MiddlewareRegistry::class); - if ('Query' === $value->getNodeName()) { + if ('Query' === $value->getParentName()) { $middlewareRegistry->registerQuery( $value->getFieldName(), $checks ); - } elseif ('Mutation' === $value->getNodeName()) { + } elseif ('Mutation' === $value->getParentName()) { $middlewareRegistry->registerMutation( $value->getFieldName(), $checks @@ -59,7 +59,7 @@ public function handleField(FieldValue $value, \Closure $next) */ protected function getChecks(FieldValue $value) { - if (! in_array($value->getNodeName(), ['Mutation', 'Query'])) { + if (! in_array($value->getParentName(), ['Mutation', 'Query'])) { return null; } diff --git a/src/Schema/Directives/Fields/PaginateDirective.php b/src/Schema/Directives/Fields/PaginateDirective.php index 171c3657a9..927106bc48 100755 --- a/src/Schema/Directives/Fields/PaginateDirective.php +++ b/src/Schema/Directives/Fields/PaginateDirective.php @@ -138,7 +138,7 @@ protected function getPaginatedResults(array $resolveArgs, int $page, int $first { if ($this->directiveHasArgument('builder')) { $query = \call_user_func_array( - $this->getMethodArgument('builder'), + $this->getResolverFromArgument('builder'), $resolveArgs ); } else { diff --git a/src/Schema/Directives/Nodes/NodeDirective.php b/src/Schema/Directives/Nodes/NodeDirective.php index a3744eddc2..20ce3d9392 100644 --- a/src/Schema/Directives/Nodes/NodeDirective.php +++ b/src/Schema/Directives/Nodes/NodeDirective.php @@ -53,7 +53,7 @@ public function handleNode(NodeValue $value, \Closure $next): NodeValue $this->nodeRegistry->registerNode( $typeName, - $this->getMethodArgument('resolver') + $this->getResolverFromArgument('resolver') ); return $next($value); diff --git a/src/Schema/Factories/FieldFactory.php b/src/Schema/Factories/FieldFactory.php index 62dac359d6..f1cdfcf91d 100644 --- a/src/Schema/Factories/FieldFactory.php +++ b/src/Schema/Factories/FieldFactory.php @@ -11,7 +11,6 @@ use Nuwave\Lighthouse\Execution\GraphQLValidator; use GraphQL\Language\AST\InputValueDefinitionNode; use Nuwave\Lighthouse\Exceptions\DirectiveException; -use Nuwave\Lighthouse\Schema\Conversion\DefinitionNodeConverter; class FieldFactory { @@ -23,23 +22,19 @@ class FieldFactory protected $argumentFactory; /** @var Pipeline */ protected $pipeline; - /** @var DefinitionNodeConverter */ - protected $definitionNodeConverter; /** * @param DirectiveRegistry $directiveRegistry * @param ValueFactory $valueFactory * @param ArgumentFactory $argumentFactory * @param Pipeline $pipeline - * @param DefinitionNodeConverter $definitionNodeConverter */ - public function __construct(DirectiveRegistry $directiveRegistry, ValueFactory $valueFactory, ArgumentFactory $argumentFactory, Pipeline $pipeline, DefinitionNodeConverter $definitionNodeConverter) + public function __construct(DirectiveRegistry $directiveRegistry, ValueFactory $valueFactory, ArgumentFactory $argumentFactory, Pipeline $pipeline) { $this->directiveRegistry = $directiveRegistry; $this->valueFactory = $valueFactory; $this->argumentFactory = $argumentFactory; $this->pipeline = $pipeline; - $this->definitionNodeConverter = $definitionNodeConverter; } /** @@ -54,18 +49,18 @@ public function __construct(DirectiveRegistry $directiveRegistry, ValueFactory $ public function handle(FieldValue $fieldValue): array { $fieldDefinition = $fieldValue->getField(); - - $fieldType = $this->definitionNodeConverter->toType( - $fieldDefinition->type - ); - $fieldValue->setType($fieldType); - $initialResolver = $this->directiveRegistry->hasFieldResolver($fieldDefinition) - ? $this->useResolverDirective($fieldValue) - : $this->defaultResolver($fieldValue); + if($fieldResolver = $this->directiveRegistry->fieldResolver($fieldDefinition)){ + $fieldValue = $fieldResolver->resolveField($fieldValue); + } + + $initialResolver = $fieldValue->getResolver(); $inputValueDefinitions = $this->getInputValueDefinitions($fieldValue); - $resolverWithAdditionalArgs = $this->injectAdditionalArgs($initialResolver, $fieldValue->getAdditionalArgs()); + $resolverWithAdditionalArgs = $this->injectAdditionalArgs( + $initialResolver, + $fieldValue->getAdditionalArgs() + ); $resolverWithValidation = $this->wrapResolverWithValidation($resolverWithAdditionalArgs, $inputValueDefinitions); $fieldValue->setResolver($resolverWithValidation); @@ -76,16 +71,18 @@ public function handle(FieldValue $fieldValue): array $this->directiveRegistry->fieldMiddleware($fieldDefinition) ) ->via('handleField') - ->then(function (FieldValue $fieldValue) { - return $fieldValue; - }) + ->then( + function (FieldValue $fieldValue): FieldValue { + return $fieldValue; + } + ) ->getResolver(); // To see what is allowed here, look at the validation rules in // GraphQL\Type\Definition\FieldDefinition::getDefinition() return [ 'name' => $fieldDefinition->name->value, - 'type' => $fieldType, + 'type' => $fieldValue->getReturnType(), 'args' => $inputValueDefinitions->toArray(), 'resolve' => $resolverWithMiddleware, 'description' => data_get($fieldDefinition->description, 'value'), @@ -93,71 +90,6 @@ public function handle(FieldValue $fieldValue): array ]; } - /** - * Use directive resolver to transform field. - * - * @param FieldValue $value - * - * @throws DirectiveException - * - * @return \Closure - */ - protected function useResolverDirective(FieldValue $value): \Closure - { - return $this->directiveRegistry - ->fieldResolver($value->getField()) - ->resolveField($value) - ->getResolver(); - } - - /** - * Get default field resolver. - * - * @param FieldValue $fieldValue - * - * @return \Closure - */ - protected function defaultResolver(FieldValue $fieldValue): \Closure - { - switch ($fieldValue->getNodeName()) { - case 'Mutation': - return $this->rootOperationResolver( - $fieldValue->getFieldName(), - 'mutations' - ); - case 'Query': - return $this->rootOperationResolver( - $fieldValue->getFieldName(), - 'queries' - ); - default: - // TODO convert this back once we require PHP 7.1 -// return \Closure::fromCallable( -// [\GraphQL\Executor\Executor::class, 'defaultFieldResolver'] -// ); - return function() { - return \GraphQL\Executor\Executor::defaultFieldResolver(...func_get_args()); - }; - } - } - - /** - * Get the default resolver for a field of the root operation types. - * - * @param string $fieldName - * @param string $rootOperationType One of [queries|mutations] - * - * @return \Closure - */ - protected function rootOperationResolver(string $fieldName, string $rootOperationType): \Closure - { - return function ($obj, array $args, $context = null, $info = null) use ($fieldName, $rootOperationType) { - $class = config("lighthouse.namespaces.{$rootOperationType}").'\\'.studly_case($fieldName); - - return resolve($class)->resolve($obj, $args, $context, $info); - }; - } - /** * Wrap the resolver by injecting additional arg values. * diff --git a/src/Schema/Factories/NodeFactory.php b/src/Schema/Factories/NodeFactory.php index 85571017df..590c34bcb7 100644 --- a/src/Schema/Factories/NodeFactory.php +++ b/src/Schema/Factories/NodeFactory.php @@ -269,12 +269,12 @@ protected function resolveInterfaceType(InterfaceTypeDefinitionNode $interfaceDe $interfaceDirective = (new InterfaceDirective)->hydrate($interfaceDefinition); if($interfaceDirective->directiveHasArgument('resolveType')){ - $typeResolver = $interfaceDirective->getMethodArgument('resolveType'); + $typeResolver = $interfaceDirective->getResolverFromArgument('resolveType'); } else { /** * @deprecated in v3 this will only be available as the argument resolveType */ - $typeResolver = $interfaceDirective->getMethodArgument('resolver'); + $typeResolver = $interfaceDirective->getResolverFromArgument('resolver'); } } else { $interfaceClass = \namespace_classname($nodeName, [ @@ -310,12 +310,12 @@ protected function resolveUnionType(UnionTypeDefinitionNode $unionDefinition): U $unionDirective = (new UnionDirective)->hydrate($unionDefinition); if($unionDirective->directiveHasArgument('resolveType')){ - $typeResolver = $unionDirective->getMethodArgument('resolveType'); + $typeResolver = $unionDirective->getResolverFromArgument('resolveType'); } else { /** * @deprecated in v3 this will only be available as the argument resolveType */ - $typeResolver = $unionDirective->getMethodArgument('resolver'); + $typeResolver = $unionDirective->getResolverFromArgument('resolver'); } } else { $unionClass = \namespace_classname($nodeName, [ diff --git a/src/Schema/Values/CacheValue.php b/src/Schema/Values/CacheValue.php index 45f69ac1a7..da4bfc2331 100644 --- a/src/Schema/Values/CacheValue.php +++ b/src/Schema/Values/CacheValue.php @@ -91,13 +91,13 @@ public function getTags(): array { $typeTag = $this->implode([ 'graphql', - strtolower($this->fieldValue->getNodeName()), + strtolower($this->fieldValue->getParentName()), $this->fieldKey, ]); $fieldTag = $this->implode([ 'graphql', - strtolower($this->fieldValue->getNodeName()), + strtolower($this->fieldValue->getParentName()), $this->fieldKey, $this->resolveInfo->fieldName, ]); @@ -135,7 +135,7 @@ protected function setFieldKey() } $cacheFieldKey = $this->fieldValue - ->getNode() + ->getParent() ->getCacheKey(); if ($cacheFieldKey) { diff --git a/src/Schema/Values/FieldValue.php b/src/Schema/Values/FieldValue.php index fd4b9a5892..c030d0aef7 100644 --- a/src/Schema/Values/FieldValue.php +++ b/src/Schema/Values/FieldValue.php @@ -12,11 +12,11 @@ class FieldValue { /** - * Current type. + * An instance of the type that this field returns. * - * @var \Closure|Type + * @var Type|null */ - protected $type; + protected $returnType; /** * @todo remove InputValueDefinitionNode once it no longer reuses this class. @@ -30,7 +30,7 @@ class FieldValue * * @var NodeValue */ - protected $node; + protected $parent; /** * The actual field resolver. @@ -67,34 +67,20 @@ class FieldValue * @todo remove InputValueDefinitionNode once it no longer reuses this class. * @param FieldDefinitionNode|InputValueDefinitionNode $field */ - public function __construct(NodeValue $node, $field) + public function __construct(NodeValue $parent, $field) { - $this->node = $node; + $this->parent = $parent; $this->field = $field; } /** - * Set current type. - * - * @param \Closure|Type $type - * - * @return FieldValue - */ - public function setType($type): FieldValue - { - $this->type = $type; - - return $this; - } - - /** - * Set current resolver. + * Overwrite the current/default resolver. * - * @param \Closure|null $resolver + * @param \Closure $resolver * * @return FieldValue */ - public function setResolver(\Closure $resolver = null): FieldValue + public function setResolver(\Closure $resolver): FieldValue { $this->resolver = $resolver; @@ -142,23 +128,35 @@ public function getAdditionalArgs(): array } /** - * Get current type. + * Get an instance of the return type of the field. * - * @return \Closure|Type + * @return Type */ - public function getType() + public function getReturnType(): Type { - return $this->type; + if(! isset($this->returnType)){ + $this->returnType = resolve(DefinitionNodeConverter::class)->toType( + $this->field->type + ); + } + + return $this->returnType; } /** - * Get current node. - * * @return NodeValue */ - public function getNode(): NodeValue + public function getParent(): NodeValue { - return $this->node; + return $this->parent; + } + + /** + * @return string + */ + public function getParentName(): string + { + return $this->getParent()->getNodeName(); } /** @@ -178,12 +176,56 @@ public function getField() */ public function getResolver(): \Closure { + if(!isset($this->resolver)){ + $this->resolver = $this->defaultResolver(); + } + return $this->resolver; } /** - * Get current description. + * Get default field resolver. + * + * @throws DefinitionException + * + * @return \Closure + */ + protected function defaultResolver(): \Closure + { + if($namespace = $this->getDefaultNamespaceForParent()){ + return construct_resolver( + $namespace . '\\' . studly_case($this->getFieldName()), + 'resolve' + ); + } + + // TODO convert this back once we require PHP 7.1 + // return \Closure::fromCallable( + // [\GraphQL\Executor\Executor::class, 'defaultFieldResolver'] + // ); + return function() { + return \GraphQL\Executor\Executor::defaultFieldResolver(...func_get_args()); + }; + } + + /** + * If a default namespace exists for the parent type, return it. * + * @return string|null + */ + public function getDefaultNamespaceForParent() + { + switch ($this->getParentName()) { + case 'Mutation': + return config('lighthouse.namespaces.mutations'); + case 'Query': + return config('lighthouse.namespaces.queries'); + default: + return null; + } + } + + /** * @return StringValueNode|null */ public function getDescription() @@ -201,42 +243,46 @@ public function getComplexity() return $this->complexity; } - /** - * Get private cache flag. - * - * @param null $flag - * - * @return FieldValue|bool + * @return string */ - public function isPrivateCache($flag = null) + public function getFieldName(): string { - if (null === $flag) { - return $this->privateCache; - } - - $this->privateCache = $flag; - - return $this; + return $this->field->name->value; } /** - * Get field name. - * - * @return string + * @return NodeValue + * @deprecated */ - public function getFieldName(): string + public function getNode(): NodeValue { - return $this->field->name->value; + return $this->getParent(); } /** * Get field's node name. * * @return string + * @deprecated */ public function getNodeName(): string { - return $this->getNode()->getNodeName(); + return $this->getParentName(); + } + + /** + * Set current type. + * + * @param \Closure|Type $type + * + * @return FieldValue + * @deprecated Do this sort of manipulation in the DocumentAST in the future. + */ + public function setType($type): FieldValue + { + $this->returnType = $type; + + return $this; } } diff --git a/src/Support/Traits/CanParseResolvers.php b/src/Support/Traits/CanParseResolvers.php index b39f9504b4..56b2318328 100644 --- a/src/Support/Traits/CanParseResolvers.php +++ b/src/Support/Traits/CanParseResolvers.php @@ -32,9 +32,11 @@ protected function getResolver(FieldValue $value, DirectiveNode $directive, $thr return $namespace ? $namespace . '\\' . $className : $className; } - return $value->getNode()->getNamespace( - $this->getResolverClassName($directive, $throw) - ); + return $value + ->getParent() + ->getNamespace( + $this->getResolverClassName($directive, $throw) + ); } /** diff --git a/src/Support/Traits/CreatesPaginators.php b/src/Support/Traits/CreatesPaginators.php index e6682b8b47..2e1fa84013 100644 --- a/src/Support/Traits/CreatesPaginators.php +++ b/src/Support/Traits/CreatesPaginators.php @@ -107,7 +107,7 @@ protected function registerPaginator(FieldValue $value) */ protected function paginatorTypeName(FieldValue $value) { - $parent = $value->getNodeName(); + $parent = $value->getParentName(); $child = str_singular($value->getField()->name->value); return studly_case($parent . '_' . $child . '_Paginator'); @@ -122,7 +122,7 @@ protected function paginatorTypeName(FieldValue $value) */ protected function connectionTypeName(FieldValue $value) { - $parent = $value->getNodeName(); + $parent = $value->getParentName(); $child = str_singular($value->getField()->name->value); return studly_case($parent . '_' . $child . '_Connection'); @@ -137,7 +137,7 @@ protected function connectionTypeName(FieldValue $value) */ protected function connectionEdgeName(FieldValue $value) { - $parent = $value->getNodeName(); + $parent = $value->getParentName(); $child = str_singular($value->getField()->name->value); return studly_case($parent . '_' . $child . '_Edge'); diff --git a/src/Support/Traits/HandlesQueries.php b/src/Support/Traits/HandlesQueries.php index 4a14fc5dfe..f309a5bde3 100644 --- a/src/Support/Traits/HandlesQueries.php +++ b/src/Support/Traits/HandlesQueries.php @@ -33,7 +33,7 @@ public function getModelClass(FieldValue $value) $message = sprintf( 'A `model` argument must be assigned to the %s directive on the %s field [%s]', $this->name(), - $value->getNodeName(), + $value->getParentName(), $value->getFieldName() ); diff --git a/src/Support/helpers.php b/src/Support/helpers.php index bd4b084841..87acbc4ce3 100644 --- a/src/Support/helpers.php +++ b/src/Support/helpers.php @@ -2,6 +2,7 @@ use Nuwave\Lighthouse\Schema\TypeRegistry; use Nuwave\Lighthouse\Schema\DirectiveRegistry; +use Nuwave\Lighthouse\Exceptions\DefinitionException; if (! function_exists('graphql')) { /** @@ -126,3 +127,28 @@ function namespace_classname(string $classCandidate, array $namespacesToTry = [] return false; } } + +if (! function_exists('construct_resolver')) { + /** + * Construct a closure that passes through the arguments. + * + * @param string $className This class is resolved through the container. + * @param string $methodName The method that gets passed the arguments of the closure. + * + * @throws DefinitionException + * + * @return \Closure + */ + function construct_resolver(string $className, string $methodName): \Closure + { + if (!method_exists($className, $methodName)) { + throw new DefinitionException("Method '{$methodName}' does not exist on class '{$className}'"); + } + + // TODO convert this back once we require PHP 7.1 + // return \Closure::fromCallable([resolve($className), $methodName]); + return function () use ($className, $methodName) { + return resolve($className)->{$methodName}(...func_get_args()); + }; + } +} diff --git a/tests/Integration/Events/BuildingASTTest.php b/tests/Integration/Events/BuildingASTTest.php index 5010eda5fd..0503d7d593 100644 --- a/tests/Integration/Events/BuildingASTTest.php +++ b/tests/Integration/Events/BuildingASTTest.php @@ -12,11 +12,7 @@ class BuildingASTTest extends TestCase */ public function itInjectsSourceSchemaIntoEvent() { - $schema = ' - type Query { - foo: Int - } - '; + $schema = $this->placeholderQuery(); resolve('events')->listen( BuildingAST::class, @@ -25,7 +21,7 @@ function (BuildingAST $buildingAST) use ($schema){ } ); - $this->buildSchemaFromString($schema); + $this->buildSchema($schema); } /** diff --git a/tests/Integration/Schema/Directives/Fields/BelongsToManyDirectiveTest.php b/tests/Integration/Schema/Directives/Fields/BelongsToManyDirectiveTest.php index bb1e773f96..6cc2126e16 100644 --- a/tests/Integration/Schema/Directives/Fields/BelongsToManyDirectiveTest.php +++ b/tests/Integration/Schema/Directives/Fields/BelongsToManyDirectiveTest.php @@ -249,7 +249,7 @@ public function itThrowsErrorWithUnknownTypeArg() { $this->expectException(DirectiveException::class); - $schema = $this->buildSchemaWithDefaultQuery(' + $schema = $this->buildSchemaWithPlaceholderQuery(' type User { roles(first: Int! after: Int): [Role!]! @belongsToMany(type:"foo") } diff --git a/tests/Integration/Schema/Directives/Fields/CreateDirectiveTest.php b/tests/Integration/Schema/Directives/Fields/CreateDirectiveTest.php index e05ac0cde3..01363bbcb5 100644 --- a/tests/Integration/Schema/Directives/Fields/CreateDirectiveTest.php +++ b/tests/Integration/Schema/Directives/Fields/CreateDirectiveTest.php @@ -21,11 +21,7 @@ public function itCanCreateFromFieldArguments() type Mutation { createCompany(name: String): Company @create } - - type Query { - foo: Int - } - '; + ' . $this->placeholderQuery(); $query = ' mutation { createCompany(name: "foo") { @@ -58,11 +54,7 @@ public function itCanCreateFromInputObject() input CreateCompanyInput { name: String } - - type Query { - foo: Int - } - '; + ' . $this->placeholderQuery(); $query = ' mutation { createCompany(input: { @@ -105,11 +97,7 @@ public function itCanCreateWithBelongsTo() name: String user: ID } - - type Query { - foo: Int - } - '; + ' . $this->placeholderQuery(); $query = ' mutation { createTask(input: { diff --git a/tests/Integration/Schema/Directives/Fields/DeleteDirectiveTest.php b/tests/Integration/Schema/Directives/Fields/DeleteDirectiveTest.php index 3894a602b8..ff747d4511 100644 --- a/tests/Integration/Schema/Directives/Fields/DeleteDirectiveTest.php +++ b/tests/Integration/Schema/Directives/Fields/DeleteDirectiveTest.php @@ -21,11 +21,7 @@ public function itDeletesUserAndReturnsIt() type Mutation { deleteUser(id: ID!): User @delete } - - type Query { - dummy: Int - } - '; + ' . $this->placeholderQuery(); $query = " mutation { deleteUser(id: 1) { @@ -53,11 +49,7 @@ public function itDeletesMultipleUsersAndReturnsThem() type Mutation { deleteUsers(id: [ID!]!): [User!]! @delete } - - type Query { - dummy: Int - } - '; + ' . $this->placeholderQuery(); $query = " mutation { deleteUsers(id: [1, 2]) { @@ -84,11 +76,7 @@ public function itRejectsDefinitionWithNullableArgument() type Mutation { deleteUser(id: ID): User @delete } - - type Query { - dummy: Int - } - '; + ' . $this->placeholderQuery(); $query = " mutation { deleteUser(id: 1) { @@ -112,11 +100,7 @@ public function itRejectsDefinitionWithNoArgument() type Mutation { deleteUser: User @delete } - - type Query { - dummy: Int - } - '; + ' . $this->placeholderQuery(); $query = " mutation { deleteUser { @@ -140,11 +124,7 @@ public function itRejectsDefinitionWithMultipleArguments() type Mutation { deleteUser(foo: String, bar: Int): User @delete } - - type Query { - dummy: Int - } - '; + ' . $this->placeholderQuery(); $query = " mutation { deleteUser { diff --git a/tests/Integration/Schema/Directives/Fields/HasManyDirectiveTest.php b/tests/Integration/Schema/Directives/Fields/HasManyDirectiveTest.php index a74d179626..c0deb5bfb6 100644 --- a/tests/Integration/Schema/Directives/Fields/HasManyDirectiveTest.php +++ b/tests/Integration/Schema/Directives/Fields/HasManyDirectiveTest.php @@ -317,7 +317,7 @@ public function itThrowsErrorWithUnknownTypeArg() { $this->expectException(DirectiveException::class); - $schema = $this->buildSchemaWithDefaultQuery(' + $schema = $this->buildSchemaWithPlaceholderQuery(' type User { tasks(first: Int! after: Int): [Task!]! @hasMany(type:"foo") } diff --git a/tests/Integration/Schema/Directives/Fields/PaginateDirectiveTest.php b/tests/Integration/Schema/Directives/Fields/PaginateDirectiveTest.php index 5704ff5f87..379c8f6a80 100644 --- a/tests/Integration/Schema/Directives/Fields/PaginateDirectiveTest.php +++ b/tests/Integration/Schema/Directives/Fields/PaginateDirectiveTest.php @@ -320,14 +320,10 @@ public function itPaginatesWhenDefinedInTypeExtension() name: String! } - type Query { - dummy: Int - } - extend type Query @group { users: [User!]! @paginate(model: "User") } - '; + ' . $this->placeholderQuery(); $query = ' { diff --git a/tests/Integration/Schema/Directives/Fields/UpdateDirectiveTest.php b/tests/Integration/Schema/Directives/Fields/UpdateDirectiveTest.php index 332ce2ad1d..f978d58daa 100644 --- a/tests/Integration/Schema/Directives/Fields/UpdateDirectiveTest.php +++ b/tests/Integration/Schema/Directives/Fields/UpdateDirectiveTest.php @@ -28,11 +28,7 @@ public function itCanUpdateFromFieldArguments() name: String ): Company @update } - - type Query { - foo: Int - } - '; + ' . $this->placeholderQuery(); $query = ' mutation { updateCompany( @@ -73,11 +69,7 @@ public function itCanUpdateFromInputObject() id: ID! name: String } - - type Query { - foo: Int - } - '; + ' . $this->placeholderQuery(); $query = ' mutation { updateCompany(input: { @@ -127,11 +119,7 @@ public function itCanUpdateWithBelongsTo() name: String user: ID } - - type Query { - foo: Int - } - '; + ' . $this->placeholderQuery(); $query = ' mutation { updateTask(input: { diff --git a/tests/Integration/Schema/NodeInterfaceTest.php b/tests/Integration/Schema/NodeInterfaceTest.php index 9d9129825b..8577ef834d 100644 --- a/tests/Integration/Schema/NodeInterfaceTest.php +++ b/tests/Integration/Schema/NodeInterfaceTest.php @@ -31,11 +31,7 @@ public function itCanResolveNodes() type User @node(resolver: "Tests\\\Integration\\\Schema\\\NodeInterfaceTest@resolveNode") { name: String! } - - type Query { - dummy: Int - } - '; + ' . $this->placeholderQuery(); $firstGlobalId = GlobalId::encode('User', $this->testTuples[1]['id']); $secondGlobalId = GlobalId::encode('User', $this->testTuples[2]['id']); @@ -83,11 +79,7 @@ public function itCanResolveModelsNodes() type User @model { name: String! } - - type Query { - dummy: Int - } - '; + ' . $this->placeholderQuery(); $user = factory(User::class)->create( ['name' => 'Sepp'] diff --git a/tests/TestCase.php b/tests/TestCase.php index 4d87dd9200..f24d44e17d 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -117,13 +117,27 @@ protected function execute(string $schema, string $query, array $variables = []) * * @return \GraphQL\Type\Schema */ - protected function buildSchemaWithDefaultQuery(string $schema): Schema + protected function buildSchemaWithPlaceholderQuery(string $schema): Schema { - return $this->buildSchemaFromString($schema.' + return $this->buildSchema( + $schema + . $this->placeholderQuery() + ); + } + + /** + * Convenience method to get a default Query, sometimes needed + * because the Schema is invalid without it. + * + * @return \GraphQL\Type\Schema + */ + protected function placeholderQuery(): string + { + return ' type Query { - dummy: String + foo: Int } - '); + '; } /** @@ -131,7 +145,7 @@ protected function buildSchemaWithDefaultQuery(string $schema): Schema * * @return \GraphQL\Type\Schema */ - protected function buildSchemaFromString(string $schema): Schema + protected function buildSchema(string $schema): Schema { $this->schema = $schema; diff --git a/tests/Unit/Schema/DirectiveRegistryTest.php b/tests/Unit/Schema/DirectiveRegistryTest.php index 3a42db4db1..036c8eea1e 100644 --- a/tests/Unit/Schema/DirectiveRegistryTest.php +++ b/tests/Unit/Schema/DirectiveRegistryTest.php @@ -64,7 +64,7 @@ public function itThrowsErrorIfMultipleDirectivesAssignedToNode() { $this->expectException(DirectiveException::class); - $this->buildSchemaWithDefaultQuery(' + $this->buildSchemaWithPlaceholderQuery(' scalar DateTime @scalar @foo '); } diff --git a/tests/Unit/Schema/Directives/Fields/CanDirectiveTest.php b/tests/Unit/Schema/Directives/Fields/CanDirectiveTest.php index 135880bb12..4ef04446a8 100644 --- a/tests/Unit/Schema/Directives/Fields/CanDirectiveTest.php +++ b/tests/Unit/Schema/Directives/Fields/CanDirectiveTest.php @@ -16,7 +16,9 @@ public function itThrowsWhenNotAuthenticated() { $schema = ' type Query { - user: User! @can(if: "adminOnly") + user: User! + @can(if: "adminOnly") + @field(resolver: "'.addslashes(self::class).'@resolveUser") } type User { @@ -44,7 +46,9 @@ public function itThrowsIfNotAuthorized() $schema = ' type Query { - user: User! @can(if: "adminOnly") + user: User! + @can(if: "adminOnly") + @field(resolver: "'.addslashes(self::class).'@resolveUser") } type User { @@ -74,7 +78,9 @@ public function itPassesAuthIfAuthorized() $schema = ' type Query { - user: User! @can(if: "adminOnly") @field(resolver: "'.addslashes(self::class).'@resolveUser") + user: User! + @can(if: "adminOnly") + @field(resolver: "'.addslashes(self::class).'@resolveUser") } type User { @@ -104,7 +110,9 @@ public function itPassesMultiplePolicies() $schema = ' type Query { - user: User! @can(if: ["adminOnly", "alwaysTrue"]) @field(resolver: "'.addslashes(self::class).'@resolveUser") + user: User! + @can(if: ["adminOnly", "alwaysTrue"]) + @field(resolver: "'.addslashes(self::class).'@resolveUser") } type User { diff --git a/tests/Unit/Schema/Directives/Fields/ComplexityDirectiveTest.php b/tests/Unit/Schema/Directives/Fields/ComplexityDirectiveTest.php index c23d866ba8..062439492f 100644 --- a/tests/Unit/Schema/Directives/Fields/ComplexityDirectiveTest.php +++ b/tests/Unit/Schema/Directives/Fields/ComplexityDirectiveTest.php @@ -11,7 +11,7 @@ class ComplexityDirectiveTest extends TestCase */ public function itCanSetDefaultComplexityOnField() { - $schema = $this->buildSchemaWithDefaultQuery(' + $schema = $this->buildSchemaWithPlaceholderQuery(' type User { posts: [Post!]! @complexity @hasMany } @@ -35,7 +35,7 @@ public function itCanSetCustomComplexityResolver() { $resolver = addslashes(self::class); - $schema = $this->buildSchemaWithDefaultQuery(' + $schema = $this->buildSchemaWithPlaceholderQuery(' type User { posts: [Post!]! @complexity(resolver: "'.$resolver.'@complexity") diff --git a/tests/Unit/Schema/Directives/Fields/FieldDirectiveTest.php b/tests/Unit/Schema/Directives/Fields/FieldDirectiveTest.php index 606f55e072..6830140c18 100644 --- a/tests/Unit/Schema/Directives/Fields/FieldDirectiveTest.php +++ b/tests/Unit/Schema/Directives/Fields/FieldDirectiveTest.php @@ -3,6 +3,7 @@ namespace Tests\Unit\Schema\Directives\Fields; use Tests\TestCase; +use Tests\Utils\Queries\FooBar; use Nuwave\Lighthouse\Exceptions\DirectiveException; class FieldDirectiveTest extends TestCase @@ -25,7 +26,7 @@ public function itCanResolveFieldWithAssignedClass() '; $result = $this->execute($schema, $query); - $this->assertEquals('foo.bar', array_get($result, 'data.bar')); + $this->assertSame('foo.bar', array_get($result, 'data.bar')); } /** @@ -45,7 +46,7 @@ public function itAssignsResolverFromCombinedDefinition() '; $result = $this->execute($schema, $query); - $this->assertEquals('foo.bar', array_get($result, 'data.bar')); + $this->assertSame('foo.bar', array_get($result, 'data.bar')); } /** @@ -65,7 +66,27 @@ public function itCanResolveFieldWithMergedArgs() '; $result = $this->execute($schema, $query); - $this->assertEquals('foo.baz', array_get($result, 'data.bar')); + $this->assertSame('foo.baz', array_get($result, 'data.bar')); + } + + /** + * @test + */ + public function itUsesDefaultFieldNamespace() + { + $schema = ' + type Query { + bar: String! @field(resolver: "FooBar@customResolve") + } + '; + $query = ' + { + bar + } + '; + $result = $this->execute($schema, $query); + + $this->assertSame(FooBar::CUSTOM_RESOLVE_RESULT, array_get($result, 'data.bar')); } /** diff --git a/tests/Unit/Schema/Directives/Fields/MiddlewareDirectiveTest.php b/tests/Unit/Schema/Directives/Fields/MiddlewareDirectiveTest.php index 76e16e5717..c623fc9187 100644 --- a/tests/Unit/Schema/Directives/Fields/MiddlewareDirectiveTest.php +++ b/tests/Unit/Schema/Directives/Fields/MiddlewareDirectiveTest.php @@ -22,14 +22,12 @@ public function setUp() */ public function itCanRegisterMiddleware() { - $this->buildSchemaFromString(' + $this->buildSchema(' type Query { foo: String! @middleware(checks: ["auth:web", "auth:admin"]) - bar: String! } type Mutation { foo(bar: String!): String! @middleware(checks: ["auth:api"]) - bar(baz: String!): String! } '); $query = ' @@ -58,15 +56,13 @@ public function itCanRegisterMiddleware() */ public function itCanRegisterMiddlewareWithFragments() { - $this->buildSchemaFromString(' + $this->buildSchema(' type Query { foo: String! @middleware(checks: ["auth:web", "auth:admin"]) - bar: String! } type Mutation { foo(bar: String!): String! @middleware(checks: ["auth:api"]) - bar(baz: String!): String! } '); diff --git a/tests/Unit/Schema/Directives/Fields/PaginateDirectiveTest.php b/tests/Unit/Schema/Directives/Fields/PaginateDirectiveTest.php index 7eb9e721df..7d9d395151 100644 --- a/tests/Unit/Schema/Directives/Fields/PaginateDirectiveTest.php +++ b/tests/Unit/Schema/Directives/Fields/PaginateDirectiveTest.php @@ -21,7 +21,7 @@ public function itCanAliasRelayToConnection() protected function getConnectionQueryField(string $type): FieldDefinition { return $this - ->buildSchemaFromString(" + ->buildSchema(" type User { name: String } @@ -39,7 +39,7 @@ protected function getConnectionQueryField(string $type): FieldDefinition */ public function itOnlyRegistersOneTypeForMultiplePaginators() { - $schema = $this->buildSchemaFromString(' + $schema = $this->buildSchema(' type User { name: String users: [User!]! @paginate @@ -71,16 +71,12 @@ public function itOnlyRegistersOneTypeForMultiplePaginators() */ public function itRegistersPaginatorFromTypeExtensionField() { - $schema = $this->buildSchemaFromString(' + $schema = $this->buildSchemaWithPlaceholderQuery(' type User { id: ID! name: String! } - type Query { - dummy: Int - } - extend type Query @group { users: [User!]! @paginate } diff --git a/tests/Unit/Schema/Directives/Nodes/GroupDirectiveTest.php b/tests/Unit/Schema/Directives/Nodes/GroupDirectiveTest.php index 3eab634b29..ee976ddd49 100644 --- a/tests/Unit/Schema/Directives/Nodes/GroupDirectiveTest.php +++ b/tests/Unit/Schema/Directives/Nodes/GroupDirectiveTest.php @@ -14,10 +14,6 @@ class GroupDirectiveTest extends TestCase public function itCanSetNamespaces() { $schema = ' - type Query { - dummy: Int - } - extend type Query @group(namespace: "Tests\\\Utils\\\Resolvers") { me: String @field(resolver: "Foo@bar") } @@ -25,7 +21,7 @@ public function itCanSetNamespaces() extend type Query @group(namespace: "Tests\\\Utils\\\Resolvers") { you: String @field(resolver: "Foo@bar") } - '; + ' . $this->placeholderQuery(); $query = ' { @@ -50,14 +46,10 @@ public function itCanSetNamespaces() public function itCanSetMiddleware() { $schema = ' - type Query { - dummy: Int - } - extend type Query @group(middleware: ["foo", "bar"]) { me: String @field(resolver: "Tests\\\Utils\\\Resolvers\\\Foo@bar") } - '; + ' . $this->placeholderQuery(); $query = ' { me diff --git a/tests/Unit/Schema/Factories/ValueFactoryTest.php b/tests/Unit/Schema/Factories/ValueFactoryTest.php index 7615db058d..c19e0d211e 100644 --- a/tests/Unit/Schema/Factories/ValueFactoryTest.php +++ b/tests/Unit/Schema/Factories/ValueFactoryTest.php @@ -26,36 +26,33 @@ public function setUp() */ public function itCanSetNodeValueResolver() { - $this->valueFactory->nodeResolver(function ($node) { - return new class($node) extends NodeValue { - public function getType() - { - return new ObjectType([ - 'name' => $this->getNodeName(), - 'fields' => [ - 'foo' => [ - 'type' => Type::string(), - 'resolve' => function () { - return 'bar'; - }, + $this->valueFactory->nodeResolver( + function ($node) { + return new class($node) extends NodeValue { + public function getType() + { + return new ObjectType([ + 'name' => $this->getNodeName(), + 'fields' => [ + 'foo' => [ + 'type' => Type::string(), + 'resolve' => function () { + return 'bar'; + }, + ], ], - ], - ]); - } - }; - }); + ]); + } + }; + } + ); - $schema = ' - type Query { - foo: String - } - '; $query = ' { foo } '; - $result = $this->execute($schema, $query); + $result = $this->execute($this->placeholderQuery(), $query); $this->assertEquals(['foo' => 'bar'], $result['data']); } diff --git a/tests/Unit/Schema/SchemaBuilderTest.php b/tests/Unit/Schema/SchemaBuilderTest.php index 7280aa2e05..aeb456b724 100644 --- a/tests/Unit/Schema/SchemaBuilderTest.php +++ b/tests/Unit/Schema/SchemaBuilderTest.php @@ -11,41 +11,13 @@ class SchemaBuilderTest extends TestCase { - /** - * Get test environment setup. - * - * @param mixed $app - */ - public function getEnvironmentSetUp($app) - { - parent::getEnvironmentSetUp($app); - - $app['config']->set( - 'lighthouse.namespaces.queries', - 'Tests\\Utils\\Mutations' - ); - - $app['config']->set( - 'lighthouse.namespaces.mutations', - 'Tests\\Utils\\Mutations' - ); - } - /** * @test */ public function itGeneratesValidSchema() { - $schema = $this->buildSchemaFromString(' - type Query { - foo: String! - } + $schema = $this->buildSchemaWithPlaceholderQuery(''); - type Mutation { - foo: String! - } - '); - $this->assertInstanceOf(Schema::class, $schema); // This would throw if the schema were invalid $schema->assertValid(); @@ -56,7 +28,7 @@ public function itGeneratesValidSchema() */ public function itCanResolveEnumTypes() { - $schema = $this->buildSchemaWithDefaultQuery(' + $schema = $this->buildSchemaWithPlaceholderQuery(' "Role description" enum Role { "Company administrator." @@ -82,7 +54,7 @@ enum Role { */ public function itCanResolveInterfaceTypes() { - $schema = $this->buildSchemaWithDefaultQuery(' + $schema = $this->buildSchemaWithPlaceholderQuery(' """ int """ @@ -105,7 +77,7 @@ interface Foo { */ public function itCanResolveObjectTypes() { - $schema = $this->buildSchemaWithDefaultQuery(' + $schema = $this->buildSchemaWithPlaceholderQuery(' "asdf" type Foo { "bar attribute of Foo" @@ -132,7 +104,7 @@ public function itCanResolveObjectTypes() */ public function itCanResolveInputObjectTypes() { - $schema = $this->buildSchemaWithDefaultQuery(' + $schema = $this->buildSchemaWithPlaceholderQuery(' "bla" input CreateFoo { "xyz" @@ -155,7 +127,7 @@ public function itCanResolveInputObjectTypes() */ public function itCanResolveMutations() { - $schema = $this->buildSchemaWithDefaultQuery(' + $schema = $this->buildSchemaWithPlaceholderQuery(' type Mutation { foo(bar: String! baz: String): String } @@ -173,17 +145,13 @@ public function itCanResolveMutations() */ public function itCanResolveQueries() { - $schema = $this->buildSchemaFromString(' - type Query { - foo(bar: String! baz: String): String - } - '); + $schema = $this->buildSchemaWithPlaceholderQuery(''); /** @var ObjectType $type */ $type = $schema->getType('Query'); - $foo = $type->getField('foo'); + $field = $type->getField('foo'); - $this->assertSame('foo', $foo->name); + $this->assertSame('foo', $field->name); } /** @@ -191,7 +159,7 @@ public function itCanResolveQueries() */ public function itCanExtendObjectTypes() { - $schema = $this->buildSchemaWithDefaultQuery(' + $schema = $this->buildSchemaWithPlaceholderQuery(' type Foo { bar: String! } @@ -211,19 +179,19 @@ public function itCanExtendObjectTypes() */ public function itCanExtendTypes() { - $schema = $this->buildSchemaFromString(' - type Query { + $schema = $this->buildSchemaWithPlaceholderQuery(' + type Foo { foo: String! } - extend type Query { + extend type Foo { "yo?" bar: String! } '); /** @var ObjectType $type */ - $type = $schema->getType('Query'); + $type = $schema->getType('Foo'); $this->assertSame('yo?', $type->getField('bar')->description); } diff --git a/tests/Unit/Schema/SecurityTest.php b/tests/Unit/Schema/SecurityTest.php index 37a28d8a4b..f889119962 100644 --- a/tests/Unit/Schema/SecurityTest.php +++ b/tests/Unit/Schema/SecurityTest.php @@ -52,7 +52,7 @@ public function itCanDisableIntrospectionThroughSecurityDirective() { $this->assertIntrospectionIsDisabled(' type Query @security(introspection: false) { - foo: String + foo: Int } '); } @@ -101,11 +101,9 @@ public function itCanDisableIntrospectionThroughConfig() { config(['lighthouse.security.disable_introspection' => true]); - $this->assertIntrospectionIsDisabled(' - type Query { - foo: String - } - '); + $this->assertIntrospectionIsDisabled( + $this->placeholderQuery() + ); } protected function assertMaxQueryComplexityIs1(string $schema) diff --git a/tests/Utils/Mutations/Foo.php b/tests/Utils/Mutations/Foo.php new file mode 100644 index 0000000000..52b17eeeae --- /dev/null +++ b/tests/Utils/Mutations/Foo.php @@ -0,0 +1,35 @@ +