diff --git a/src/AggregateControllerQueryProvider.php b/src/AggregateControllerQueryProvider.php index 432e8623a..909e2a956 100644 --- a/src/AggregateControllerQueryProvider.php +++ b/src/AggregateControllerQueryProvider.php @@ -8,17 +8,12 @@ use Psr\Container\ContainerInterface; use TheCodingMachine\GraphQLite\Mappers\DuplicateMappingException; -use function array_filter; -use function array_intersect_key; -use function array_keys; use function array_map; use function array_merge; use function array_sum; use function array_values; use function assert; use function count; -use function reset; -use function sort; /** * A query provider that looks into all controllers of your application to fetch queries. @@ -94,18 +89,25 @@ private function flattenList(array $list): array } // We have an issue, let's detect the duplicate - $duplicates = array_intersect_key(...array_values($list)); - // Let's display an error from the first one. - $firstDuplicate = reset($duplicates); - assert($firstDuplicate instanceof FieldDefinition); + $queriesByName = []; + $duplicate = null; - $duplicateName = $firstDuplicate->name; + foreach ($list as $class => $queries) { + foreach ($queries as $query => $field) { + $duplicatedClass = $queriesByName[$query] ?? null; - $classes = array_keys(array_filter($list, static function (array $fields) use ($duplicateName) { - return isset($fields[$duplicateName]); - })); - sort($classes); + if (! $duplicatedClass) { + $queriesByName[$query] = $class; - throw DuplicateMappingException::createForQueryInTwoControllers($classes[0], $classes[1], $duplicateName); + continue; + } + + $duplicate = [$duplicatedClass, $class, $query]; + } + } + + assert($duplicate !== null); + + throw DuplicateMappingException::createForQueryInTwoControllers($duplicate[0], $duplicate[1], $duplicate[2]); } } diff --git a/tests/FieldsBuilderTest.php b/tests/FieldsBuilderTest.php index a64095610..d080a8f0d 100644 --- a/tests/FieldsBuilderTest.php +++ b/tests/FieldsBuilderTest.php @@ -22,6 +22,7 @@ use Symfony\Component\Cache\Psr16Cache; use TheCodingMachine\GraphQLite\Annotations\Exceptions\InvalidParameterException; use TheCodingMachine\GraphQLite\Annotations\Query; +use TheCodingMachine\GraphQLite\Cache\HardClassBoundCache; use TheCodingMachine\GraphQLite\Fixtures\PropertyPromotionInputType; use TheCodingMachine\GraphQLite\Fixtures\PropertyPromotionInputTypeWithoutGenericDoc; use TheCodingMachine\GraphQLite\Fixtures\TestController; @@ -353,7 +354,8 @@ public function getUser(): object|null $this->getTypeMapper(), $this->getArgumentResolver(), $this->getTypeResolver(), - new CachedDocBlockFactory(new Psr16Cache(new ArrayAdapter())), + $this->getDocBlockFactory(), + $this->getDocBlockContextFactory(), new NamingStrategy(), $this->getRootTypeMapper(), $this->getParameterMiddlewarePipe(), @@ -384,7 +386,8 @@ public function isAllowed(string $right, $subject = null): bool $this->getTypeMapper(), $this->getArgumentResolver(), $this->getTypeResolver(), - new CachedDocBlockFactory(new Psr16Cache(new ArrayAdapter())), + $this->getDocBlockFactory(), + $this->getDocBlockContextFactory(), new NamingStrategy(), $this->getRootTypeMapper(), $this->getParameterMiddlewarePipe(), @@ -445,7 +448,8 @@ public function testFromSourceFieldsInterface(): void $this->getTypeMapper(), $this->getArgumentResolver(), $this->getTypeResolver(), - new CachedDocBlockFactory(new Psr16Cache(new ArrayAdapter())), + $this->getDocBlockFactory(), + $this->getDocBlockContextFactory(), new NamingStrategy(), $this->getRootTypeMapper(), $this->getParameterMiddlewarePipe(), diff --git a/tests/Mappers/ClassFinderTypeMapperTest.php b/tests/Mappers/ClassFinderTypeMapperTest.php index cb5831e50..1657c271c 100644 --- a/tests/Mappers/ClassFinderTypeMapperTest.php +++ b/tests/Mappers/ClassFinderTypeMapperTest.php @@ -74,7 +74,7 @@ public function testClassFinderTypeMapperDuplicateTypesException(): void $typeGenerator = $this->getTypeGenerator(); - $mapper = new ClassFinderTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\DuplicateTypes'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), new Psr16Cache(new NullAdapter())); + $mapper = new ClassFinderTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\DuplicateTypes'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), $this->getClassFinderComputedCache()); $this->expectException(DuplicateMappingException::class); $mapper->canMapClassToType(TestType::class); @@ -90,7 +90,7 @@ public function testClassFinderTypeMapperDuplicateInputsException(): void $typeGenerator = $this->getTypeGenerator(); - $mapper = new ClassFinderTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\DuplicateInputs'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), new Psr16Cache(new NullAdapter())); + $mapper = new ClassFinderTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\DuplicateInputs'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), $this->getClassFinderComputedCache()); $this->expectException(DuplicateMappingException::class); $mapper->canMapClassToInputType(TestInput::class); @@ -106,7 +106,7 @@ public function testClassFinderTypeMapperDuplicateInputTypesException(): void $typeGenerator = $this->getTypeGenerator(); - $mapper = new ClassFinderTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\DuplicateInputTypes'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), new Psr16Cache(new NullAdapter())); + $mapper = new ClassFinderTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\DuplicateInputTypes'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), $this->getClassFinderComputedCache()); $caught = false; try { @@ -135,7 +135,7 @@ public function testClassFinderTypeMapperInheritedInputTypesException(): void $typeGenerator = $this->getTypeGenerator(); - $mapper = new ClassFinderTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\InheritedInputTypes'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), new Psr16Cache(new NullAdapter())); + $mapper = new ClassFinderTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\InheritedInputTypes'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), $this->getClassFinderComputedCache()); //$this->expectException(DuplicateMappingException::class); //$this->expectExceptionMessage('The class \'TheCodingMachine\GraphQLite\Fixtures\TestObject\' should be mapped to only one GraphQL Input type. Two methods are pointing via the @Factory annotation to this class: \'TheCodingMachine\GraphQLite\Fixtures\DuplicateInputTypes\TestFactory::myFactory\' and \'TheCodingMachine\GraphQLite\Fixtures\DuplicateInputTypes\TestFactory2::myFactory\''); @@ -153,7 +153,7 @@ public function testClassFinderTypeMapperClassNotFoundException(): void $typeGenerator = $this->getTypeGenerator(); - $mapper = new ClassFinderTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\BadClassType'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), new Psr16Cache(new NullAdapter())); + $mapper = new ClassFinderTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\BadClassType'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), $this->getClassFinderComputedCache()); $this->expectException(ClassNotFoundException::class); $this->expectExceptionMessage("Could not autoload class 'Foobar' defined in #[Type] attribute of class 'TheCodingMachine\\GraphQLite\\Fixtures\\BadClassType\\TestType'"); @@ -170,7 +170,7 @@ public function testClassFinderTypeMapperNameNotFoundException(): void $typeGenerator = $this->getTypeGenerator(); - $mapper = new ClassFinderTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\Types'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), new Psr16Cache(new NullAdapter())); + $mapper = new ClassFinderTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\Types'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), $this->getClassFinderComputedCache()); $this->expectException(CannotMapTypeException::class); $mapper->mapNameToType('NotExists', $this->getTypeMapper()); @@ -294,7 +294,7 @@ public function testInvalidName(): void $typeGenerator = $this->getTypeGenerator(); - $mapper = new ClassFinderTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\Types'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), new Psr16Cache(new ArrayAdapter())); + $mapper = new ClassFinderTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\Types'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), $this->getClassFinderComputedCache()); $this->assertFalse($mapper->canExtendTypeForName('{}()/\\@:', new MutableObjectType(['name' => 'foo']))); $this->assertFalse($mapper->canDecorateInputTypeForName('{}()/\\@:', new MockResolvableInputObjectType(['name' => 'foo']))); diff --git a/tests/Mappers/Parameters/TypeMapperTest.php b/tests/Mappers/Parameters/TypeMapperTest.php index f3f4fd374..00ce2aef1 100644 --- a/tests/Mappers/Parameters/TypeMapperTest.php +++ b/tests/Mappers/Parameters/TypeMapperTest.php @@ -11,6 +11,7 @@ use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\Cache\Psr16Cache; use TheCodingMachine\GraphQLite\AbstractQueryProvider; +use TheCodingMachine\GraphQLite\Annotations\HideParameter; use TheCodingMachine\GraphQLite\Fixtures\UnionOutputType; use TheCodingMachine\GraphQLite\Mappers\CannotMapTypeException; use TheCodingMachine\GraphQLite\Parameters\DefaultValueParameter; diff --git a/tests/Mappers/Root/MyCLabsEnumTypeMapperTest.php b/tests/Mappers/Root/MyCLabsEnumTypeMapperTest.php index ecd711c94..8663d4f8a 100644 --- a/tests/Mappers/Root/MyCLabsEnumTypeMapperTest.php +++ b/tests/Mappers/Root/MyCLabsEnumTypeMapperTest.php @@ -15,7 +15,7 @@ class MyCLabsEnumTypeMapperTest extends AbstractQueryProvider { public function testObjectTypeHint(): void { - $mapper = new MyCLabsEnumTypeMapper(new FinalRootTypeMapper($this->getTypeMapper()), $this->getAnnotationReader(), new ArrayAdapter(), []); + $mapper = new MyCLabsEnumTypeMapper(new FinalRootTypeMapper($this->getTypeMapper()), $this->getAnnotationReader(), $this->getClassFinder([]), $this->getClassFinderComputedCache()); $this->expectException(CannotMapTypeException::class); $this->expectExceptionMessage("don't know how to handle type object"); diff --git a/tests/Mappers/StaticClassListTypeMapperTest.php b/tests/Mappers/StaticClassListTypeMapperTest.php deleted file mode 100644 index c77beb392..000000000 --- a/tests/Mappers/StaticClassListTypeMapperTest.php +++ /dev/null @@ -1,31 +0,0 @@ -getTypeGenerator(); - $inputTypeGenerator = $this->getInputTypeGenerator(); - - $cache = new Psr16Cache(new ArrayAdapter()); - - $mapper = new StaticClassListTypeMapper(['NotExistsClass'], $typeGenerator, $inputTypeGenerator, $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), $cache); - - $this->expectException(GraphQLRuntimeException::class); - $this->expectExceptionMessage('Could not find class "NotExistsClass"'); - - $mapper->getSupportedClasses(); - } -} diff --git a/tests/Reflection/CachedDocBlockFactoryTest.php b/tests/Reflection/CachedDocBlockFactoryTest.php index 47168df8a..d58f57593 100644 --- a/tests/Reflection/CachedDocBlockFactoryTest.php +++ b/tests/Reflection/CachedDocBlockFactoryTest.php @@ -2,31 +2,47 @@ namespace TheCodingMachine\GraphQLite\Reflection; +use phpDocumentor\Reflection\DocBlockFactory; +use phpDocumentor\Reflection\Types\ContextFactory; use PHPUnit\Framework\TestCase; -use ReflectionClass; use ReflectionMethod; use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\Cache\Psr16Cache; -use Symfony\Component\Cache\Simple\ArrayCache; +use TheCodingMachine\GraphQLite\AnnotationReader; +use TheCodingMachine\GraphQLite\Cache\HardClassBoundCache; use TheCodingMachine\GraphQLite\Reflection\DocBlock\CachedDocBlockFactory; +use TheCodingMachine\GraphQLite\Reflection\DocBlock\PhpDocumentorDocBlockContextFactory; +use TheCodingMachine\GraphQLite\Reflection\DocBlock\PhpDocumentorDocBlockFactory; class CachedDocBlockFactoryTest extends TestCase { public function testGetDocBlock(): void { - $arrayCache = new Psr16Cache(new ArrayAdapter()); - $cachedDocBlockFactory = new CachedDocBlockFactory($arrayCache); + $arrayCache = new Psr16Cache(new ArrayAdapter(storeSerialized: false)); + $cachedDocBlockFactory = new CachedDocBlockFactory( + new HardClassBoundCache($arrayCache), + new PhpDocumentorDocBlockFactory( + DocBlockFactory::createInstance(), + new PhpDocumentorDocBlockContextFactory(new ContextFactory()), + ) + ); - $refMethod = new ReflectionMethod(CachedDocBlockFactory::class, 'getDocBlock'); + $refMethod = new ReflectionMethod(AnnotationReader::class, 'getMethodAnnotation'); - $docBlock = $cachedDocBlockFactory->getDocBlock($refMethod); - $this->assertSame('Fetches a DocBlock object from a ReflectionMethod', $docBlock->getSummary()); - $docBlock2 = $cachedDocBlockFactory->getDocBlock($refMethod); + $docBlock = $cachedDocBlockFactory->createFromReflector($refMethod); + $this->assertSame('Returns a method annotation and handles correctly errors.', $docBlock->getSummary()); + $docBlock2 = $cachedDocBlockFactory->createFromReflector($refMethod); $this->assertSame($docBlock2, $docBlock); - $newCachedDocBlockFactory = new CachedDocBlockFactory($arrayCache); - $docBlock3 = $newCachedDocBlockFactory->getDocBlock($refMethod); + $newCachedDocBlockFactory = new CachedDocBlockFactory( + new HardClassBoundCache($arrayCache), + new PhpDocumentorDocBlockFactory( + DocBlockFactory::createInstance(), + new PhpDocumentorDocBlockContextFactory(new ContextFactory()), + ) + ); + $docBlock3 = $newCachedDocBlockFactory->createFromReflector($refMethod); $this->assertEquals($docBlock3, $docBlock); } }