diff --git a/src/GraphQL/DecoratableTypeResolver.php b/src/GraphQL/DecoratableTypeResolver.php new file mode 100644 index 000000000..c8834da93 --- /dev/null +++ b/src/GraphQL/DecoratableTypeResolver.php @@ -0,0 +1,73 @@ +decorated = $resolver; + } + + /** + * Resolve the type for the provided object. + * + * @param mixed $object + * The object to resolve to a concrete type. + * + * @return string|null + * The GraphQL type name or NULL if this resolver could not determine it. + */ + abstract protected function resolve($object) : ?string; + + /** + * Allows this type resolver to be called by the GraphQL library. + * + * Takes care of chaining the various type resolvers together and invokes the + * `resolve` method for each concrete implementation in the chain. + * + * @param mixed $object + * The object to resolve to a concrete type. + * + * @return string + * The resolved GraphQL type name. + * + * @throws \RuntimeException + * When a type was passed for which no type resolver exists in the chain. + */ + public function __invoke($object) : string { + $type = $this->resolve($object); + if ($type !== NULL) { + return $type; + } + + if ($this->decorated !== NULL) { + $type = $this->decorated->__invoke($object); + if ($type !== NULL) { + return $type; + } + } + + $klass = get_class($object); + throw new \RuntimeException("Can not map instance of '${klass}' to concrete GraphQL Type."); + } + +} diff --git a/src/GraphQL/DecoratableTypeResolverInterface.php b/src/GraphQL/DecoratableTypeResolverInterface.php new file mode 100644 index 000000000..32b410427 --- /dev/null +++ b/src/GraphQL/DecoratableTypeResolverInterface.php @@ -0,0 +1,62 @@ +addTypeResolver( + * 'InterfaceType', + * new ConcreteTypeResolver($registry->getTypeResolver('InterfaceType')) + * ); + * ``` + * + * TypeResolvers should not extend other type resolvers but always extend this + * class directly. Classes will be called in the reverse order of being added + * (classes added last will be called first). + * + * @package Drupal\social_graphql\GraphQL + */ +interface DecoratableTypeResolverInterface { + + /** + * Create a new decoratable type resolver. + * + * @param \Drupal\graphql\GraphQL\DecoratableTypeResolverInterface|null $resolver + * The previous type resolver if any. + */ + public function __construct(?DecoratableTypeResolverInterface $resolver); + + /** + * Allows this type resolver to be called by the GraphQL library. + * + * Takes care of chaining the various type resolvers together and invokes the + * `resolve` method for each concrete implementation in the chain. + * + * @param mixed $object + * The object to resolve to a concrete type. + * + * @return string + * The resolved GraphQL type name. + * + * @throws \RuntimeException + * When a type was passed for which no type resolver exists in the chain. + */ + public function __invoke($object) : string; + +} diff --git a/tests/src/Kernel/TypeResolver/DecoratableTypeResolverTest.php b/tests/src/Kernel/TypeResolver/DecoratableTypeResolverTest.php new file mode 100644 index 000000000..d6d433692 --- /dev/null +++ b/tests/src/Kernel/TypeResolver/DecoratableTypeResolverTest.php @@ -0,0 +1,70 @@ +resolver = $this->getMockForAbstractClass(DecoratableTypeResolver::class, [NULL]); + $this->resolver->method('resolve') + ->willReturnCallback(function ($object) { + return ucfirst($object->bundle()); + }); + + $this->decoratedResolver = $this->getMockForAbstractClass(DecoratableTypeResolver::class, [$this->resolver]); + $this->decoratedResolver->method('resolve') + ->willReturnCallback(function ($object) { + if ($object->bundle( + ) === 'article') { + return 'DecoratedArticle'; + } + return NULL; + }); + + } + + /** + * Test the decoration. + */ + public function testDecoration(): void { + $newsNode = $this->createMock(NodeInterface::class); + $newsNode->method('bundle') + ->willReturn('news'); + + $articleNode = $this->createMock(NodeInterface::class); + $articleNode->method('bundle') + ->willReturn('article'); + + $this->assertEquals('News', $this->resolver->__invoke($newsNode)); + $this->assertEquals('Article', $this->resolver->__invoke($articleNode)); + + $this->assertEquals('News', $this->decoratedResolver->__invoke($newsNode)); + $this->assertEquals('DecoratedArticle', $this->decoratedResolver->__invoke($articleNode)); + } + +}