From fc6b0e1ff537c10a14bc9bf5794739f9556dcc9d Mon Sep 17 00:00:00 2001 From: Oleksandr Prypkhan Date: Thu, 28 Mar 2024 16:55:34 +0200 Subject: [PATCH 01/28] Combine addTypeNamespace() and addControllerNamespace() into addNamespace() --- src/SchemaFactory.php | 44 +++++++++++++------ .../Psr15GraphQLMiddlewareBuilderTest.php | 4 +- tests/Integration/AnnotatedInterfaceTest.php | 3 +- tests/Integration/EndToEndTest.php | 10 ++--- tests/Mappers/StaticTypeMapperTest.php | 2 +- tests/SchemaFactoryTest.php | 32 ++++---------- website/docs/other-frameworks.mdx | 12 ++--- website/docs/validation.mdx | 3 +- 8 files changed, 49 insertions(+), 61 deletions(-) diff --git a/src/SchemaFactory.php b/src/SchemaFactory.php index 04e4bfa6ba..24222b1f3c 100644 --- a/src/SchemaFactory.php +++ b/src/SchemaFactory.php @@ -75,12 +75,8 @@ class SchemaFactory { public const GLOB_CACHE_SECONDS = 2; - - /** @var array */ - private array $controllerNamespaces = []; - /** @var array */ - private array $typeNamespaces = []; + private array $namespaces = []; /** @var QueryProviderInterface[] */ private array $queryProviders = []; @@ -133,20 +129,40 @@ public function __construct(private CacheInterface $cache, private ContainerInte /** * Registers a namespace that can contain GraphQL controllers. + * + * @deprecated Using SchemaFactory::addControllerNamespace() is deprecated in favor of SchemaFactory::addNamespace() */ public function addControllerNamespace(string $namespace): self { - $this->controllerNamespaces[] = $namespace; + trigger_error( + "Using SchemaFactory::addControllerNamespace() is deprecated in favor of SchemaFactory::addNamespace().", + E_USER_DEPRECATED, + ); - return $this; + return $this->addNamespace($namespace); } /** * Registers a namespace that can contain GraphQL types. + * + * @deprecated Using SchemaFactory::addTypeNamespace() is deprecated in favor of SchemaFactory::addNamespace() */ public function addTypeNamespace(string $namespace): self { - $this->typeNamespaces[] = $namespace; + trigger_error( + "Using SchemaFactory::addTypeNamespace() is deprecated in favor of SchemaFactory::addNamespace().", + E_USER_DEPRECATED, + ); + + return $this->addNamespace($namespace); + } + + /** + * Registers a namespace that can contain GraphQL types or controllers. + */ + public function addNamespace(string $namespace): self + { + $this->namespaces[] = $namespace; return $this; } @@ -349,7 +365,7 @@ public function createSchema(): Schema $namespaceFactory = new NamespaceFactory($namespacedCache, $finder, $this->globTTL); $nsList = array_map( static fn (string $namespace) => $namespaceFactory->createNamespace($namespace), - $this->typeNamespaces, + $this->namespaces, ); $expressionLanguage = $this->expressionLanguage ?: new ExpressionLanguage($symfonyCache); @@ -477,8 +493,8 @@ public function createSchema(): Schema $this->typeMappers[] = $typeMapperFactory->create($context); } - if (empty($this->typeNamespaces) && empty($this->typeMappers)) { - throw new GraphQLRuntimeException('Cannot create schema: no namespace for types found (You must call the SchemaFactory::addTypeNamespace() at least once).'); + if (empty($this->namespaces) && empty($this->typeMappers)) { + throw new GraphQLRuntimeException('Cannot create schema: no namespace for types found (You must call the SchemaFactory::addNamespace() at least once).'); } foreach ($this->typeMappers as $typeMapper) { @@ -488,9 +504,9 @@ public function createSchema(): Schema $compositeTypeMapper->addTypeMapper(new PorpaginasTypeMapper($recursiveTypeMapper)); $queryProviders = []; - foreach ($this->controllerNamespaces as $controllerNamespace) { + foreach ($this->namespaces as $namespace) { $queryProviders[] = new GlobControllerQueryProvider( - $controllerNamespace, + $namespace, $fieldsBuilder, $this->container, $annotationReader, @@ -509,7 +525,7 @@ public function createSchema(): Schema } if ($queryProviders === []) { - throw new GraphQLRuntimeException('Cannot create schema: no namespace for controllers found (You must call the SchemaFactory::addControllerNamespace() at least once).'); + throw new GraphQLRuntimeException('Cannot create schema: no namespace for controllers found (You must call the SchemaFactory::addNamespace() at least once).'); } $aggregateQueryProvider = new AggregateQueryProvider($queryProviders); diff --git a/tests/Http/Psr15GraphQLMiddlewareBuilderTest.php b/tests/Http/Psr15GraphQLMiddlewareBuilderTest.php index 885b64f891..d72062ac30 100644 --- a/tests/Http/Psr15GraphQLMiddlewareBuilderTest.php +++ b/tests/Http/Psr15GraphQLMiddlewareBuilderTest.php @@ -39,9 +39,7 @@ public function testCreateMiddleware() $factory = new SchemaFactory($cache, $container); $factory->setAuthenticationService(new VoidAuthenticationService()); $factory->setAuthorizationService(new VoidAuthorizationService()); - - $factory->addControllerNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration\\Controllers'); - $factory->addTypeNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration'); + $factory->addNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration'); $schema = $factory->createSchema(); diff --git a/tests/Integration/AnnotatedInterfaceTest.php b/tests/Integration/AnnotatedInterfaceTest.php index d3c81b64ce..b24666de2b 100644 --- a/tests/Integration/AnnotatedInterfaceTest.php +++ b/tests/Integration/AnnotatedInterfaceTest.php @@ -24,8 +24,7 @@ public function setUp(): void $container = new BasicAutoWiringContainer(new EmptyContainer()); $schemaFactory = new SchemaFactory(new Psr16Cache(new ArrayAdapter()), $container); - $schemaFactory->addControllerNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\AnnotatedInterfaces\\Controllers'); - $schemaFactory->addTypeNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\AnnotatedInterfaces\\Types'); + $schemaFactory->addNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\AnnotatedInterfaces'); $this->schema = $schemaFactory->createSchema(); } diff --git a/tests/Integration/EndToEndTest.php b/tests/Integration/EndToEndTest.php index d8522cc275..8205ff2ba8 100644 --- a/tests/Integration/EndToEndTest.php +++ b/tests/Integration/EndToEndTest.php @@ -1549,8 +1549,7 @@ public function testInputOutputNameConflict(): void $arrayAdapter = new ArrayAdapter(); $arrayAdapter->setLogger(new ExceptionLogger()); $schemaFactory = new SchemaFactory(new Psr16Cache($arrayAdapter), new BasicAutoWiringContainer(new EmptyContainer())); - $schemaFactory->addControllerNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\InputOutputNameConflict\\Controllers'); - $schemaFactory->addTypeNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\InputOutputNameConflict\\Types'); + $schemaFactory->addNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\InputOutputNameConflict'); $schema = $schemaFactory->createSchema(); @@ -1873,9 +1872,7 @@ public function testEndToEndInputTypeValidation(): void $arrayAdapter = new ArrayAdapter(); $arrayAdapter->setLogger(new ExceptionLogger()); $schemaFactory = new SchemaFactory(new Psr16Cache($arrayAdapter), new BasicAutoWiringContainer(new EmptyContainer())); - $schemaFactory->addControllerNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration\\Controllers'); - $schemaFactory->addTypeNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration\\Models'); - $schemaFactory->addTypeNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration\\Types'); + $schemaFactory->addNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration'); $schemaFactory->setAuthenticationService($container->get(AuthenticationServiceInterface::class)); $schemaFactory->setAuthorizationService($container->get(AuthorizationServiceInterface::class)); $schemaFactory->setInputTypeValidator($validator); @@ -2249,8 +2246,7 @@ public function testCircularInput(): void $arrayAdapter = new ArrayAdapter(); $arrayAdapter->setLogger(new ExceptionLogger()); $schemaFactory = new SchemaFactory(new Psr16Cache($arrayAdapter), new BasicAutoWiringContainer(new EmptyContainer())); - $schemaFactory->addControllerNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\CircularInputReference\\Controllers'); - $schemaFactory->addTypeNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\CircularInputReference\\Types'); + $schemaFactory->addNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\CircularInputReference'); $schema = $schemaFactory->createSchema(); diff --git a/tests/Mappers/StaticTypeMapperTest.php b/tests/Mappers/StaticTypeMapperTest.php index b340d83c20..7b7ece2a13 100644 --- a/tests/Mappers/StaticTypeMapperTest.php +++ b/tests/Mappers/StaticTypeMapperTest.php @@ -137,7 +137,7 @@ public function testEndToEnd(): void $arrayAdapter = new ArrayAdapter(); $arrayAdapter->setLogger(new ExceptionLogger()); $schemaFactory = new SchemaFactory(new Psr16Cache($arrayAdapter), new BasicAutoWiringContainer(new EmptyContainer())); - $schemaFactory->addControllerNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\StaticTypeMapper\\Controllers'); + $schemaFactory->addNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\StaticTypeMapper\\Controllers'); // Let's register a type that maps by default to the "MyClass" PHP class $staticTypeMapper = new StaticTypeMapper( diff --git a/tests/SchemaFactoryTest.php b/tests/SchemaFactoryTest.php index d57dc78a11..b880698693 100644 --- a/tests/SchemaFactoryTest.php +++ b/tests/SchemaFactoryTest.php @@ -51,8 +51,7 @@ public function testCreateSchema(): void $factory->setAuthenticationService(new VoidAuthenticationService()); $factory->setAuthorizationService(new VoidAuthorizationService()); - $factory->addControllerNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration\\Controllers'); - $factory->addTypeNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration'); + $factory->addNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration'); $factory->addQueryProvider(new AggregateQueryProvider([])); $factory->addFieldMiddleware(new FieldMiddlewarePipe()); $factory->addInputFieldMiddleware(new InputFieldMiddlewarePipe()); @@ -69,8 +68,7 @@ public function testSetters(): void $factory = new SchemaFactory($cache, $container); - $factory->addControllerNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration\\Controllers'); - $factory->addTypeNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration'); + $factory->addNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration'); $factory->setDoctrineAnnotationReader(new AnnotationReader()) ->setAuthenticationService(new VoidAuthenticationService()) ->setAuthorizationService(new VoidAuthorizationService()) @@ -101,8 +99,7 @@ public function testFinderInjectionWithValidMapper(): void $factory->setAuthenticationService(new VoidAuthenticationService()) ->setAuthorizationService(new VoidAuthorizationService()) ->setFinder(new ComposerFinder()) - ->addControllerNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration\\Controllers') - ->addTypeNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration'); + ->addNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration'); $schema = $factory->createSchema(); @@ -148,8 +145,7 @@ public function testFinderInjectionWithInvalidMapper(): void $factory->setAuthenticationService(new VoidAuthenticationService()) ->setAuthorizationService(new VoidAuthorizationService()) ->setFinder(new RecursiveFinder(__DIR__ . '/Annotations')) - ->addControllerNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration\\Controllers') - ->addTypeNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration'); + ->addNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration'); $this->doTestSchemaWithError($factory->createSchema()); } @@ -165,18 +161,6 @@ public function testException(): void $factory->createSchema(); } - public function testException2(): void - { - $container = new BasicAutoWiringContainer(new EmptyContainer()); - $cache = new Psr16Cache(new ArrayAdapter()); - - $factory = new SchemaFactory($cache, $container); - $factory->addTypeNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration'); - - $this->expectException(GraphQLRuntimeException::class); - $factory->createSchema(); - } - private function execTestQuery(Schema $schema): ExecutionResult { $schema->assertValid(); @@ -240,8 +224,8 @@ public function testDuplicateQueryException(): void ); $factory->setAuthenticationService(new VoidAuthenticationService()) ->setAuthorizationService(new VoidAuthorizationService()) - ->addControllerNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\DuplicateQueries') - ->addTypeNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration'); + ->addNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\DuplicateQueries') + ->addNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration'); $this->expectException(DuplicateMappingException::class); $this->expectExceptionMessage("The query/mutation/field 'duplicateQuery' is declared twice in class 'TheCodingMachine\GraphQLite\Fixtures\DuplicateQueries\TestControllerWithDuplicateQuery'. First in 'TheCodingMachine\GraphQLite\Fixtures\DuplicateQueries\TestControllerWithDuplicateQuery::testDuplicateQuery1()', second in 'TheCodingMachine\GraphQLite\Fixtures\DuplicateQueries\TestControllerWithDuplicateQuery::testDuplicateQuery2()'"); @@ -267,8 +251,8 @@ public function testDuplicateQueryInTwoControllersException(): void ); $factory->setAuthenticationService(new VoidAuthenticationService()) ->setAuthorizationService(new VoidAuthorizationService()) - ->addControllerNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\DuplicateQueriesInTwoControllers') - ->addTypeNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration'); + ->addNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\DuplicateQueriesInTwoControllers') + ->addNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration'); $this->expectException(DuplicateMappingException::class); $this->expectExceptionMessage("The query/mutation 'duplicateQuery' is declared twice: in class 'TheCodingMachine\\GraphQLite\\Fixtures\\DuplicateQueriesInTwoControllers\\TestControllerWithDuplicateQuery1' and in class 'TheCodingMachine\\GraphQLite\\Fixtures\\DuplicateQueriesInTwoControllers\\TestControllerWithDuplicateQuery2"); diff --git a/website/docs/other-frameworks.mdx b/website/docs/other-frameworks.mdx index 9402451116..d1ae5dca86 100644 --- a/website/docs/other-frameworks.mdx +++ b/website/docs/other-frameworks.mdx @@ -38,8 +38,7 @@ use TheCodingMachine\GraphQLite\SchemaFactory; // $cache is a PSR-16 compatible cache // $container is a PSR-11 compatible container $factory = new SchemaFactory($cache, $container); -$factory->addControllerNamespace('App\\Controllers\\') - ->addTypeNamespace('App\\'); +$factory->addNamespace('App'); $schema = $factory->createSchema(); ``` @@ -109,8 +108,7 @@ use Kcs\ClassFinder\Finder\ComposerFinder; use TheCodingMachine\GraphQLite\SchemaFactory; $factory = new SchemaFactory($cache, $container); -$factory->addControllerNamespace('App\\Controllers\\') - ->addTypeNamespace('App\\') +$factory->addNamespace('App') ->setFinder( (new ComposerFinder())->useAutoloading(false) ); @@ -132,8 +130,7 @@ use TheCodingMachine\GraphQLite\Context\Context; // $cache is a PSR-16 compatible cache. // $container is a PSR-11 compatible container. $factory = new SchemaFactory($cache, $container); -$factory->addControllerNamespace('App\\Controllers\\') - ->addTypeNamespace('App\\'); +$factory->addNamespace('App'); $schema = $factory->createSchema(); @@ -290,8 +287,7 @@ return new Picotainer([ Schema::class => function(ContainerInterface $container) { // The magic happens here. We create a schema using GraphQLite SchemaFactory. $factory = new SchemaFactory($container->get(CacheInterface::class), $container); - $factory->addControllerNamespace('App\\Controllers\\'); - $factory->addTypeNamespace('App\\'); + $factory->addNamespace('App'); return $factory->createSchema(); } ]); diff --git a/website/docs/validation.mdx b/website/docs/validation.mdx index dfb2c925dd..6de6bbf569 100644 --- a/website/docs/validation.mdx +++ b/website/docs/validation.mdx @@ -252,8 +252,7 @@ To get started with validation on input types defined by an `@Input` annotation, ```php $factory = new SchemaFactory($cache, $this->container); -$factory->addControllerNamespace('App\\Controllers'); -$factory->addTypeNamespace('App'); +$factory->addNamespace('App'); // Register your validator $factory->setInputTypeValidator($this->container->get('your_validator')); $factory->createSchema(); From 5326b6092c05a88c7deadecc25cd1021658b8e24 Mon Sep 17 00:00:00 2001 From: Oleksandr Prypkhan Date: Thu, 28 Mar 2024 21:14:19 +0200 Subject: [PATCH 02/28] Refactor CachedDocBlockFactory to use existing caching infrastructure --- src/FieldsBuilder.php | 42 +++--- src/Mappers/Parameters/TypeHandler.php | 10 +- src/Reflection/CachedDocBlockFactory.php | 132 ------------------ .../DocBlock/CachedDocBlockContextFactory.php | 29 ++++ .../DocBlock/CachedDocBlockFactory.php | 33 +++++ .../DocBlock/DocBlockContextFactory.php | 11 ++ src/Reflection/DocBlock/DocBlockFactory.php | 12 ++ .../PhpDocumentorDocBlockContextFactory.php | 21 +++ .../DocBlock/PhpDocumentorDocBlockFactory.php | 25 ++++ src/SchemaFactory.php | 34 ++++- tests/AbstractQueryProviderTest.php | 49 ++++++- tests/FieldsBuilderTest.php | 27 ++-- tests/Integration/EndToEndTest.php | 56 -------- tests/Integration/IntegrationTestCase.php | 45 +++++- tests/Mappers/Parameters/TypeMapperTest.php | 42 +++--- .../Reflection/CachedDocBlockFactoryTest.php | 18 +-- 16 files changed, 303 insertions(+), 283 deletions(-) delete mode 100644 src/Reflection/CachedDocBlockFactory.php create mode 100644 src/Reflection/DocBlock/CachedDocBlockContextFactory.php create mode 100644 src/Reflection/DocBlock/CachedDocBlockFactory.php create mode 100644 src/Reflection/DocBlock/DocBlockContextFactory.php create mode 100644 src/Reflection/DocBlock/DocBlockFactory.php create mode 100644 src/Reflection/DocBlock/PhpDocumentorDocBlockContextFactory.php create mode 100644 src/Reflection/DocBlock/PhpDocumentorDocBlockFactory.php diff --git a/src/FieldsBuilder.php b/src/FieldsBuilder.php index 878f837c9c..e73204e389 100644 --- a/src/FieldsBuilder.php +++ b/src/FieldsBuilder.php @@ -47,12 +47,12 @@ use TheCodingMachine\GraphQLite\Parameters\InputTypeParameterInterface; use TheCodingMachine\GraphQLite\Parameters\ParameterInterface; use TheCodingMachine\GraphQLite\Parameters\PrefetchDataParameter; -use TheCodingMachine\GraphQLite\Reflection\CachedDocBlockFactory; +use TheCodingMachine\GraphQLite\Reflection\DocBlock\DocBlockContextFactory; +use TheCodingMachine\GraphQLite\Reflection\DocBlock\DocBlockFactory; use TheCodingMachine\GraphQLite\Types\ArgumentResolver; use TheCodingMachine\GraphQLite\Types\MutableObjectType; use TheCodingMachine\GraphQLite\Types\TypeResolver; use TheCodingMachine\GraphQLite\Utils\PropertyAccessor; - use function array_diff_key; use function array_fill_keys; use function array_intersect_key; @@ -70,7 +70,6 @@ use function rtrim; use function str_starts_with; use function trim; - use const PHP_EOL; /** @@ -81,15 +80,16 @@ class FieldsBuilder private TypeHandler $typeMapper; public function __construct( - private readonly AnnotationReader $annotationReader, - private readonly RecursiveTypeMapperInterface $recursiveTypeMapper, - private readonly ArgumentResolver $argumentResolver, - private readonly TypeResolver $typeResolver, - private readonly CachedDocBlockFactory $cachedDocBlockFactory, - private readonly NamingStrategyInterface $namingStrategy, - private readonly RootTypeMapperInterface $rootTypeMapper, - private readonly ParameterMiddlewareInterface $parameterMapper, - private readonly FieldMiddlewareInterface $fieldMiddleware, + private readonly AnnotationReader $annotationReader, + private readonly RecursiveTypeMapperInterface $recursiveTypeMapper, + private readonly ArgumentResolver $argumentResolver, + private readonly TypeResolver $typeResolver, + private readonly DocBlockFactory $docBlockFactory, + private readonly DocBlockContextFactory $docBlockContextFactory, + private readonly NamingStrategyInterface $namingStrategy, + private readonly RootTypeMapperInterface $rootTypeMapper, + private readonly ParameterMiddlewareInterface $parameterMapper, + private readonly FieldMiddlewareInterface $fieldMiddleware, private readonly InputFieldMiddlewareInterface $inputFieldMiddleware, ) { @@ -97,7 +97,7 @@ public function __construct( $this->argumentResolver, $this->rootTypeMapper, $this->typeResolver, - $this->cachedDocBlockFactory, + $this->docBlockFactory, ); } @@ -280,7 +280,7 @@ public function getSelfFields(string $className, string|null $typeName = null): */ public function getParameters(ReflectionMethod $refMethod, int $skip = 0): array { - $docBlockObj = $this->cachedDocBlockFactory->getDocBlock($refMethod); + $docBlockObj = $this->docBlockFactory->createFromReflector($refMethod); //$docBlockComment = $docBlockObj->getSummary()."\n".$docBlockObj->getDescription()->render(); $parameters = array_slice($refMethod->getParameters(), $skip); @@ -417,7 +417,7 @@ private function getFieldsByMethodAnnotations( $description = $queryAnnotation->getDescription(); } - $docBlockObj = $this->cachedDocBlockFactory->getDocBlock($refMethod); + $docBlockObj = $this->docBlockFactory->createFromReflector($refMethod); $name = $queryAnnotation->getName() ?: $this->namingStrategy->getFieldNameFromMethodName($methodName); @@ -524,7 +524,7 @@ private function getFieldsByPropertyAnnotations( $description = $queryAnnotation->getDescription(); } - $docBlock = $this->cachedDocBlockFactory->getDocBlock($refProperty); + $docBlock = $this->docBlockFactory->createFromReflector($refProperty); $name = $queryAnnotation->getName() ?: $refProperty->getName(); @@ -640,7 +640,7 @@ private function getQueryFieldsFromSourceFields( throw FieldNotFoundException::wrapWithCallerInfo($e, $refClass->getName()); } - $docBlockObj = $this->cachedDocBlockFactory->getDocBlock($refMethod); + $docBlockObj = $this->docBlockFactory->createFromReflector($refMethod); $docBlockComment = rtrim($docBlockObj->getSummary() . "\n" . $docBlockObj->getDescription()->render()); $deprecated = $docBlockObj->getTagsByName('deprecated'); @@ -768,7 +768,7 @@ private function resolvePhpType( { $typeResolver = new \phpDocumentor\Reflection\TypeResolver(); - $context = $this->cachedDocBlockFactory->getContextFromClass($refClass); + $context = $this->docBlockContextFactory->createFromReflector($refClass); $phpdocType = $typeResolver->resolve($phpTypeStr, $context); assert($phpdocType !== null); @@ -911,7 +911,7 @@ private function getPrefetchParameter( $prefetchParameters = $prefetchRefMethod->getParameters(); array_shift($prefetchParameters); - $prefetchDocBlockObj = $this->cachedDocBlockFactory->getDocBlock($prefetchRefMethod); + $prefetchDocBlockObj = $this->docBlockFactory->createFromReflector($prefetchRefMethod); $prefetchArgs = $this->mapParameters($prefetchParameters, $prefetchDocBlockObj); return new PrefetchDataParameter( @@ -964,7 +964,7 @@ private function getInputFieldsByMethodAnnotations( continue; } - $docBlockObj = $this->cachedDocBlockFactory->getDocBlock($refMethod); + $docBlockObj = $this->docBlockFactory->createFromReflector($refMethod); $methodName = $refMethod->getName(); if (! str_starts_with($methodName, 'set')) { continue; @@ -1058,7 +1058,7 @@ private function getInputFieldsByPropertyAnnotations( $fields = []; $annotations = $this->annotationReader->getPropertyAnnotations($refProperty, $annotationName); - $docBlock = $this->cachedDocBlockFactory->getDocBlock($refProperty); + $docBlock = $this->docBlockFactory->createFromReflector($refProperty); foreach ($annotations as $annotation) { $description = null; diff --git a/src/Mappers/Parameters/TypeHandler.php b/src/Mappers/Parameters/TypeHandler.php index 8a8eadfe1d..27ae6a4675 100644 --- a/src/Mappers/Parameters/TypeHandler.php +++ b/src/Mappers/Parameters/TypeHandler.php @@ -42,7 +42,7 @@ use TheCodingMachine\GraphQLite\Parameters\InputTypeParameter; use TheCodingMachine\GraphQLite\Parameters\InputTypeProperty; use TheCodingMachine\GraphQLite\Parameters\ParameterInterface; -use TheCodingMachine\GraphQLite\Reflection\CachedDocBlockFactory; +use TheCodingMachine\GraphQLite\Reflection\DocBlock\DocBlockFactory; use TheCodingMachine\GraphQLite\Types\ArgumentResolver; use TheCodingMachine\GraphQLite\Types\TypeResolver; @@ -65,10 +65,10 @@ class TypeHandler implements ParameterHandlerInterface private PhpDocumentorTypeResolver $phpDocumentorTypeResolver; public function __construct( - private readonly ArgumentResolver $argumentResolver, + private readonly ArgumentResolver $argumentResolver, private readonly RootTypeMapperInterface $rootTypeMapper, - private readonly TypeResolver $typeResolver, - private readonly CachedDocBlockFactory $cachedDocBlockFactory, + private readonly TypeResolver $typeResolver, + private readonly DocBlockFactory $docBlockFactory, ) { $this->phpDocumentorTypeResolver = new PhpDocumentorTypeResolver(); @@ -135,7 +135,7 @@ private function getDocBlockPropertyType(DocBlock $docBlock, ReflectionProperty return null; } - $docBlock = $this->cachedDocBlockFactory->getDocBlock($refConstructor); + $docBlock = $this->docBlockFactory->createFromReflector($refConstructor); $paramTags = $docBlock->getTagsByName('param'); foreach ($paramTags as $paramTag) { if (! $paramTag instanceof Param) { diff --git a/src/Reflection/CachedDocBlockFactory.php b/src/Reflection/CachedDocBlockFactory.php deleted file mode 100644 index 70b5c6699c..0000000000 --- a/src/Reflection/CachedDocBlockFactory.php +++ /dev/null @@ -1,132 +0,0 @@ - */ - private array $docBlockArrayCache = []; - /** @var array */ - private array $contextArrayCache = []; - private ContextFactory $contextFactory; - - /** @param CacheInterface $cache The cache we fetch data from. Note this is a SAFE cache. It does not need to be purged. */ - public function __construct(private readonly CacheInterface $cache, DocBlockFactory|null $docBlockFactory = null) - { - $this->docBlockFactory = $docBlockFactory ?: DocBlockFactory::createInstance(); - $this->contextFactory = new ContextFactory(); - } - - /** - * Fetches a DocBlock object from a ReflectionMethod - * - * @throws InvalidArgumentException - */ - public function getDocBlock(ReflectionMethod|ReflectionProperty $reflector): DocBlock - { - $key = 'docblock_' . md5($reflector->getDeclaringClass()->getName() . '::' . $reflector->getName() . '::' . $reflector::class); - if (isset($this->docBlockArrayCache[$key])) { - return $this->docBlockArrayCache[$key]; - } - - $fileName = $reflector->getDeclaringClass()->getFileName(); - assert(is_string($fileName)); - - $cacheItem = $this->cache->get($key); - if ($cacheItem !== null) { - [ - 'time' => $time, - 'docblock' => $docBlock, - ] = $cacheItem; - - if (filemtime($fileName) === $time) { - $this->docBlockArrayCache[$key] = $docBlock; - - return $docBlock; - } - } - - $docBlock = $this->doGetDocBlock($reflector); - - $this->cache->set($key, [ - 'time' => filemtime($fileName), - 'docblock' => $docBlock, - ]); - $this->docBlockArrayCache[$key] = $docBlock; - - return $docBlock; - } - - private function doGetDocBlock(ReflectionMethod|ReflectionProperty $reflector): DocBlock - { - $docComment = $reflector->getDocComment() ?: '/** */'; - - $refClass = $reflector->getDeclaringClass(); - $refClassName = $refClass->getName(); - - if (! isset($this->contextArrayCache[$refClassName])) { - $this->contextArrayCache[$refClassName] = $this->contextFactory->createFromReflector($reflector); - } - - return $this->docBlockFactory->create($docComment, $this->contextArrayCache[$refClassName]); - } - - /** @param ReflectionClass $reflectionClass */ - public function getContextFromClass(ReflectionClass $reflectionClass): Context - { - $className = $reflectionClass->getName(); - if (isset($this->contextArrayCache[$className])) { - return $this->contextArrayCache[$className]; - } - - $key = 'docblockcontext_' . md5($className); - - $fileName = $reflectionClass->getFileName(); - assert(is_string($fileName)); - - $cacheItem = $this->cache->get($key); - if ($cacheItem !== null) { - [ - 'time' => $time, - 'context' => $context, - ] = $cacheItem; - - if (filemtime($fileName) === $time) { - $this->contextArrayCache[$className] = $context; - - return $context; - } - } - - $context = $this->contextFactory->createFromReflector($reflectionClass); - - $this->cache->set($key, [ - 'time' => filemtime($fileName), - 'context' => $context, - ]); - - $this->contextArrayCache[$className] = $context; - return $context; - } -} diff --git a/src/Reflection/DocBlock/CachedDocBlockContextFactory.php b/src/Reflection/DocBlock/CachedDocBlockContextFactory.php new file mode 100644 index 0000000000..46ac64a7b6 --- /dev/null +++ b/src/Reflection/DocBlock/CachedDocBlockContextFactory.php @@ -0,0 +1,29 @@ +getDeclaringClass(); + + return $this->classBoundCacheContract->get( + $reflector, + fn () => $this->contextFactory->createFromReflector($reflector), + 'reflection.docBlockContext', + ); + } +} \ No newline at end of file diff --git a/src/Reflection/DocBlock/CachedDocBlockFactory.php b/src/Reflection/DocBlock/CachedDocBlockFactory.php new file mode 100644 index 0000000000..e9ef90c78a --- /dev/null +++ b/src/Reflection/DocBlock/CachedDocBlockFactory.php @@ -0,0 +1,33 @@ +getDeclaringClass(); + + return $this->classBoundCacheContract->get( + $class, + fn () => $this->docBlockFactory->createFromReflector($reflector), + 'reflection.docBlock.' . md5($reflector::class . '.' . $reflector->getName()), + ); + } +} diff --git a/src/Reflection/DocBlock/DocBlockContextFactory.php b/src/Reflection/DocBlock/DocBlockContextFactory.php new file mode 100644 index 0000000000..9e6c65c68d --- /dev/null +++ b/src/Reflection/DocBlock/DocBlockContextFactory.php @@ -0,0 +1,11 @@ +contextFactory->createFromReflector($reflector); + } +} \ No newline at end of file diff --git a/src/Reflection/DocBlock/PhpDocumentorDocBlockFactory.php b/src/Reflection/DocBlock/PhpDocumentorDocBlockFactory.php new file mode 100644 index 0000000000..92899af5c2 --- /dev/null +++ b/src/Reflection/DocBlock/PhpDocumentorDocBlockFactory.php @@ -0,0 +1,25 @@ +getDocComment() ?: '/** */'; + $context = $this->docBlockContextFactory->createFromReflector($reflector); + + return $this->docBlockFactory->create($docblock, $context); + } +} \ No newline at end of file diff --git a/src/SchemaFactory.php b/src/SchemaFactory.php index 24222b1f3c..911dd36878 100644 --- a/src/SchemaFactory.php +++ b/src/SchemaFactory.php @@ -12,11 +12,17 @@ use Kcs\ClassFinder\Finder\FinderInterface; use MyCLabs\Enum\Enum; use PackageVersions\Versions; +use phpDocumentor\Reflection\DocBlockFactory; +use phpDocumentor\Reflection\Types\ContextFactory; use Psr\Cache\CacheItemPoolInterface; use Psr\Container\ContainerInterface; use Psr\SimpleCache\CacheInterface; use Symfony\Component\Cache\Adapter\Psr16Adapter; use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use TheCodingMachine\CacheUtils\ClassBoundCache; +use TheCodingMachine\CacheUtils\ClassBoundCacheContract; +use TheCodingMachine\CacheUtils\ClassBoundMemoryAdapter; +use TheCodingMachine\CacheUtils\FileBoundCache; use TheCodingMachine\GraphQLite\Mappers\CompositeTypeMapper; use TheCodingMachine\GraphQLite\Mappers\GlobTypeMapper; use TheCodingMachine\GraphQLite\Mappers\Parameters\ContainerParameterHandler; @@ -49,7 +55,10 @@ use TheCodingMachine\GraphQLite\Middlewares\InputFieldMiddlewarePipe; use TheCodingMachine\GraphQLite\Middlewares\SecurityFieldMiddleware; use TheCodingMachine\GraphQLite\Middlewares\SecurityInputFieldMiddleware; -use TheCodingMachine\GraphQLite\Reflection\CachedDocBlockFactory; +use TheCodingMachine\GraphQLite\Reflection\DocBlock\CachedDocBlockContextFactory; +use TheCodingMachine\GraphQLite\Reflection\DocBlock\CachedDocBlockFactory; +use TheCodingMachine\GraphQLite\Reflection\DocBlock\PhpDocumentorDocBlockContextFactory; +use TheCodingMachine\GraphQLite\Reflection\DocBlock\PhpDocumentorDocBlockFactory; use TheCodingMachine\GraphQLite\Security\AuthenticationServiceInterface; use TheCodingMachine\GraphQLite\Security\AuthorizationServiceInterface; use TheCodingMachine\GraphQLite\Security\FailAuthenticationService; @@ -60,7 +69,6 @@ use TheCodingMachine\GraphQLite\Types\TypeResolver; use TheCodingMachine\GraphQLite\Utils\NamespacedCache; use TheCodingMachine\GraphQLite\Utils\Namespaces\NamespaceFactory; - use function array_map; use function array_reverse; use function class_exists; @@ -357,7 +365,24 @@ public function createSchema(): Schema $authorizationService = $this->authorizationService ?: new FailAuthorizationService(); $typeResolver = new TypeResolver(); $namespacedCache = new NamespacedCache($this->cache); - $cachedDocBlockFactory = new CachedDocBlockFactory($namespacedCache); + $fileBoundCache = new FileBoundCache($this->cache); + $nonInheritedClassBoundCache = new ClassBoundCache( + fileBoundCache: $fileBoundCache, + analyzeParentClasses: false, + analyzeTraits: false, + analyzeInterfaces: false, + ); + $docBlockContextFactory = new CachedDocBlockContextFactory( + new ClassBoundCacheContract(new ClassBoundMemoryAdapter($nonInheritedClassBoundCache)), + new PhpDocumentorDocBlockContextFactory(new ContextFactory()) + ); + $docBlockFactory = new CachedDocBlockFactory( + new ClassBoundCacheContract(new ClassBoundMemoryAdapter($nonInheritedClassBoundCache)), + new PhpDocumentorDocBlockFactory( + DocBlockFactory::createInstance(), + $docBlockContextFactory, + ), + ); $namingStrategy = $this->namingStrategy ?: new NamingStrategy(); $typeRegistry = new TypeRegistry(); $finder = $this->finder ?? new ComposerFinder(); @@ -436,7 +461,8 @@ public function createSchema(): Schema $recursiveTypeMapper, $argumentResolver, $typeResolver, - $cachedDocBlockFactory, + $docBlockFactory, + $docBlockContextFactory, $namingStrategy, $topRootTypeMapper, $parameterMiddlewarePipe, diff --git a/tests/AbstractQueryProviderTest.php b/tests/AbstractQueryProviderTest.php index 04c98fd4c8..a0a7f124a3 100644 --- a/tests/AbstractQueryProviderTest.php +++ b/tests/AbstractQueryProviderTest.php @@ -11,12 +11,17 @@ use GraphQL\Type\Definition\Type; use Kcs\ClassFinder\Finder\ComposerFinder; use phpDocumentor\Reflection\TypeResolver as PhpDocumentorTypeResolver; +use phpDocumentor\Reflection\Types\ContextFactory; use PHPUnit\Framework\TestCase; use Psr\Container\ContainerInterface; use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\Cache\Adapter\Psr16Adapter; use Symfony\Component\Cache\Psr16Cache; use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use TheCodingMachine\CacheUtils\ClassBoundCache; +use TheCodingMachine\CacheUtils\ClassBoundCacheContract; +use TheCodingMachine\CacheUtils\FileBoundCache; +use TheCodingMachine\GraphQLite\Containers\BasicAutoWiringContainer; use TheCodingMachine\GraphQLite\Containers\EmptyContainer; use TheCodingMachine\GraphQLite\Containers\LazyContainer; use TheCodingMachine\GraphQLite\Fixtures\Mocks\MockResolvableInputObjectType; @@ -24,6 +29,7 @@ use TheCodingMachine\GraphQLite\Fixtures\TestObject2; use TheCodingMachine\GraphQLite\Loggers\ExceptionLogger; use TheCodingMachine\GraphQLite\Mappers\CannotMapTypeException; +use TheCodingMachine\GraphQLite\Mappers\Parameters\ParameterMiddlewarePipe; use TheCodingMachine\GraphQLite\Mappers\Parameters\PrefetchParameterMiddleware; use TheCodingMachine\GraphQLite\Mappers\Parameters\ResolveInfoParameterHandler; use TheCodingMachine\GraphQLite\Mappers\RecursiveTypeMapper; @@ -38,14 +44,17 @@ use TheCodingMachine\GraphQLite\Mappers\Root\RootTypeMapperInterface; use TheCodingMachine\GraphQLite\Mappers\Root\VoidTypeMapper; use TheCodingMachine\GraphQLite\Mappers\TypeMapperInterface; -use TheCodingMachine\GraphQLite\Containers\BasicAutoWiringContainer; use TheCodingMachine\GraphQLite\Middlewares\AuthorizationFieldMiddleware; use TheCodingMachine\GraphQLite\Middlewares\FieldMiddlewarePipe; -use TheCodingMachine\GraphQLite\Mappers\Parameters\ParameterMiddlewarePipe; use TheCodingMachine\GraphQLite\Middlewares\InputFieldMiddlewarePipe; use TheCodingMachine\GraphQLite\Middlewares\PrefetchFieldMiddleware; use TheCodingMachine\GraphQLite\Middlewares\SecurityFieldMiddleware; -use TheCodingMachine\GraphQLite\Reflection\CachedDocBlockFactory; +use TheCodingMachine\GraphQLite\Reflection\DocBlock\CachedDocBlockContextFactory; +use TheCodingMachine\GraphQLite\Reflection\DocBlock\CachedDocBlockFactory; +use TheCodingMachine\GraphQLite\Reflection\DocBlock\DocBlockContextFactory; +use TheCodingMachine\GraphQLite\Reflection\DocBlock\DocBlockFactory; +use TheCodingMachine\GraphQLite\Reflection\DocBlock\PhpDocumentorDocBlockContextFactory; +use TheCodingMachine\GraphQLite\Reflection\DocBlock\PhpDocumentorDocBlockFactory; use TheCodingMachine\GraphQLite\Security\SecurityExpressionLanguageProvider; use TheCodingMachine\GraphQLite\Security\VoidAuthenticationService; use TheCodingMachine\GraphQLite\Security\VoidAuthorizationService; @@ -55,7 +64,6 @@ use TheCodingMachine\GraphQLite\Types\ResolvableMutableInputInterface; use TheCodingMachine\GraphQLite\Types\TypeResolver; use TheCodingMachine\GraphQLite\Utils\Namespaces\NamespaceFactory; -use UnitEnum; abstract class AbstractQueryProviderTest extends TestCase { @@ -278,13 +286,39 @@ protected function getParameterMiddlewarePipe(): ParameterMiddlewarePipe return $this->parameterMiddlewarePipe; } - protected function getCachedDocBlockFactory(): CachedDocBlockFactory + protected function getDocBlockFactory(): DocBlockFactory + { + return new CachedDocBlockFactory( + $this->getClassBoundCacheContract(false), + new PhpDocumentorDocBlockFactory( + \phpDocumentor\Reflection\DocBlockFactory::createInstance(), + $this->getDocBlockContextFactory(), + ) + ); + } + + protected function getDocBlockContextFactory(): DocBlockContextFactory + { + return new CachedDocBlockContextFactory( + $this->getClassBoundCacheContract(false), + new PhpDocumentorDocBlockContextFactory(new ContextFactory()) + ); + } + + private function getClassBoundCacheContract(bool $analyzeParents): ClassBoundCacheContract { $arrayAdapter = new ArrayAdapter(); $arrayAdapter->setLogger(new ExceptionLogger()); $psr16Cache = new Psr16Cache($arrayAdapter); - return new CachedDocBlockFactory($psr16Cache); + return new ClassBoundCacheContract( + new ClassBoundCache( + fileBoundCache: new FileBoundCache($psr16Cache), + analyzeParentClasses: $analyzeParents, + analyzeTraits: $analyzeParents, + analyzeInterfaces: $analyzeParents, + ), + ); } protected function buildFieldsBuilder(): FieldsBuilder @@ -318,7 +352,8 @@ protected function buildFieldsBuilder(): FieldsBuilder $this->getTypeMapper(), $this->getArgumentResolver(), $this->getTypeResolver(), - $this->getCachedDocBlockFactory(), + $this->getDocBlockFactory(), + $this->getDocBlockContextFactory(), new NamingStrategy(), $this->buildRootTypeMapper(), $parameterMiddlewarePipe, diff --git a/tests/FieldsBuilderTest.php b/tests/FieldsBuilderTest.php index 0b3bcf2c00..778dfeea38 100644 --- a/tests/FieldsBuilderTest.php +++ b/tests/FieldsBuilderTest.php @@ -10,52 +10,54 @@ use GraphQL\Type\Definition\IntType; use GraphQL\Type\Definition\ListOfType; use GraphQL\Type\Definition\NonNull; +use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ResolveInfo; use GraphQL\Type\Definition\StringType; -use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\UnionType; use ReflectionMethod; use stdClass; use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\Cache\Psr16Cache; use TheCodingMachine\GraphQLite\Annotations\Exceptions\InvalidParameterException; +use TheCodingMachine\GraphQLite\Fixtures\PropertyPromotionInputType; +use TheCodingMachine\GraphQLite\Fixtures\PropertyPromotionInputTypeWithoutGenericDoc; use TheCodingMachine\GraphQLite\Fixtures\TestController; use TheCodingMachine\GraphQLite\Fixtures\TestControllerNoReturnType; use TheCodingMachine\GraphQLite\Fixtures\TestControllerWithArrayParam; use TheCodingMachine\GraphQLite\Fixtures\TestControllerWithArrayReturnType; use TheCodingMachine\GraphQLite\Fixtures\TestControllerWithBadSecurity; -use TheCodingMachine\GraphQLite\Fixtures\TestControllerWithInvalidParameterAnnotation; -use TheCodingMachine\GraphQLite\Fixtures\TestControllerWithParamDateTime; use TheCodingMachine\GraphQLite\Fixtures\TestControllerWithFailWith; use TheCodingMachine\GraphQLite\Fixtures\TestControllerWithInvalidInputType; +use TheCodingMachine\GraphQLite\Fixtures\TestControllerWithInvalidParameterAnnotation; +use TheCodingMachine\GraphQLite\Fixtures\TestControllerWithInvalidReturnType; +use TheCodingMachine\GraphQLite\Fixtures\TestControllerWithIterableReturnType; use TheCodingMachine\GraphQLite\Fixtures\TestControllerWithNullableArray; +use TheCodingMachine\GraphQLite\Fixtures\TestControllerWithParamDateTime; use TheCodingMachine\GraphQLite\Fixtures\TestControllerWithReturnDateTime; use TheCodingMachine\GraphQLite\Fixtures\TestControllerWithUnionInputParam; -use TheCodingMachine\GraphQLite\Fixtures\TestEnum; -use TheCodingMachine\GraphQLite\Fixtures\TestTypeWithDescriptions; -use TheCodingMachine\GraphQLite\Fixtures\TestTypeWithInvalidPrefetchMethod; -use TheCodingMachine\GraphQLite\Fixtures\TestControllerWithInvalidReturnType; -use TheCodingMachine\GraphQLite\Fixtures\TestControllerWithIterableReturnType; use TheCodingMachine\GraphQLite\Fixtures\TestDoubleReturnTag; +use TheCodingMachine\GraphQLite\Fixtures\TestEnum; use TheCodingMachine\GraphQLite\Fixtures\TestFieldBadInputType; use TheCodingMachine\GraphQLite\Fixtures\TestFieldBadOutputType; use TheCodingMachine\GraphQLite\Fixtures\TestObject; use TheCodingMachine\GraphQLite\Fixtures\TestSelfType; use TheCodingMachine\GraphQLite\Fixtures\TestSourceFieldBadOutputType; use TheCodingMachine\GraphQLite\Fixtures\TestSourceFieldBadOutputType2; +use TheCodingMachine\GraphQLite\Fixtures\TestSourceName; +use TheCodingMachine\GraphQLite\Fixtures\TestSourceNameType; use TheCodingMachine\GraphQLite\Fixtures\TestType; use TheCodingMachine\GraphQLite\Fixtures\TestTypeId; use TheCodingMachine\GraphQLite\Fixtures\TestTypeMissingAnnotation; use TheCodingMachine\GraphQLite\Fixtures\TestTypeMissingField; use TheCodingMachine\GraphQLite\Fixtures\TestTypeMissingReturnType; +use TheCodingMachine\GraphQLite\Fixtures\TestTypeWithDescriptions; use TheCodingMachine\GraphQLite\Fixtures\TestTypeWithFailWith; +use TheCodingMachine\GraphQLite\Fixtures\TestTypeWithInvalidPrefetchMethod; use TheCodingMachine\GraphQLite\Fixtures\TestTypeWithMagicProperty; use TheCodingMachine\GraphQLite\Fixtures\TestTypeWithMagicPropertyType; use TheCodingMachine\GraphQLite\Fixtures\TestTypeWithPrefetchMethods; use TheCodingMachine\GraphQLite\Fixtures\TestTypeWithSourceFieldInterface; use TheCodingMachine\GraphQLite\Fixtures\TestTypeWithSourceFieldInvalidParameterAnnotation; -use TheCodingMachine\GraphQLite\Fixtures\TestSourceName; -use TheCodingMachine\GraphQLite\Fixtures\TestSourceNameType; use TheCodingMachine\GraphQLite\Mappers\CannotMapTypeException; use TheCodingMachine\GraphQLite\Mappers\CannotMapTypeExceptionInterface; use TheCodingMachine\GraphQLite\Middlewares\AuthorizationFieldMiddleware; @@ -63,14 +65,11 @@ use TheCodingMachine\GraphQLite\Middlewares\InputFieldMiddlewarePipe; use TheCodingMachine\GraphQLite\Middlewares\MissingMagicGetException; use TheCodingMachine\GraphQLite\Parameters\MissingArgumentException; -use TheCodingMachine\GraphQLite\Reflection\CachedDocBlockFactory; +use TheCodingMachine\GraphQLite\Reflection\DocBlock\CachedDocBlockFactory; use TheCodingMachine\GraphQLite\Security\AuthenticationServiceInterface; use TheCodingMachine\GraphQLite\Security\AuthorizationServiceInterface; use TheCodingMachine\GraphQLite\Security\VoidAuthenticationService; use TheCodingMachine\GraphQLite\Security\VoidAuthorizationService; -use TheCodingMachine\GraphQLite\Annotations\Query; -use TheCodingMachine\GraphQLite\Fixtures\PropertyPromotionInputType; -use TheCodingMachine\GraphQLite\Fixtures\PropertyPromotionInputTypeWithoutGenericDoc; use TheCodingMachine\GraphQLite\Types\DateTimeType; use TheCodingMachine\GraphQLite\Types\VoidType; diff --git a/tests/Integration/EndToEndTest.php b/tests/Integration/EndToEndTest.php index 8205ff2ba8..dc15d477f7 100644 --- a/tests/Integration/EndToEndTest.php +++ b/tests/Integration/EndToEndTest.php @@ -4,25 +4,17 @@ namespace TheCodingMachine\GraphQLite\Integration; -use Doctrine\Common\Annotations\AnnotationReader as DoctrineAnnotationReader; use GraphQL\Error\DebugFlag; -use GraphQL\Executor\ExecutionResult; use GraphQL\GraphQL; use GraphQL\Server\Helper; use GraphQL\Server\OperationParams; use GraphQL\Server\ServerConfig; -use PHPUnit\Framework\TestCase; use Psr\Container\ContainerInterface; use stdClass; use Symfony\Component\Cache\Adapter\ArrayAdapter; -use Symfony\Component\Cache\Adapter\Psr16Adapter; use Symfony\Component\Cache\Psr16Cache; -use Symfony\Component\ExpressionLanguage\ExpressionLanguage; -use TheCodingMachine\GraphQLite\AggregateQueryProvider; -use TheCodingMachine\GraphQLite\AnnotationReader; use TheCodingMachine\GraphQLite\Containers\BasicAutoWiringContainer; use TheCodingMachine\GraphQLite\Containers\EmptyContainer; -use TheCodingMachine\GraphQLite\Containers\LazyContainer; use TheCodingMachine\GraphQLite\Context\Context; use TheCodingMachine\GraphQLite\Exceptions\WebonyxErrorHandler; use TheCodingMachine\GraphQLite\FieldsBuilder; @@ -31,73 +23,25 @@ use TheCodingMachine\GraphQLite\Fixtures\Integration\Models\Color; use TheCodingMachine\GraphQLite\Fixtures\Integration\Models\Position; use TheCodingMachine\GraphQLite\Fixtures\Integration\Models\Size; -use TheCodingMachine\GraphQLite\GlobControllerQueryProvider; use TheCodingMachine\GraphQLite\GraphQLRuntimeException; use TheCodingMachine\GraphQLite\InputTypeGenerator; use TheCodingMachine\GraphQLite\InputTypeUtils; use TheCodingMachine\GraphQLite\Loggers\ExceptionLogger; use TheCodingMachine\GraphQLite\Mappers\CannotMapTypeException; -use TheCodingMachine\GraphQLite\Mappers\CompositeTypeMapper; -use TheCodingMachine\GraphQLite\Mappers\GlobTypeMapper; -use TheCodingMachine\GraphQLite\Mappers\Parameters\ContainerParameterHandler; -use TheCodingMachine\GraphQLite\Mappers\Parameters\InjectUserParameterHandler; -use TheCodingMachine\GraphQLite\Mappers\Parameters\ParameterMiddlewareInterface; -use TheCodingMachine\GraphQLite\Mappers\Parameters\ParameterMiddlewarePipe; -use TheCodingMachine\GraphQLite\Mappers\Parameters\PrefetchParameterMiddleware; -use TheCodingMachine\GraphQLite\Mappers\Parameters\ResolveInfoParameterHandler; -use TheCodingMachine\GraphQLite\Mappers\PorpaginasTypeMapper; -use TheCodingMachine\GraphQLite\Mappers\RecursiveTypeMapper; -use TheCodingMachine\GraphQLite\Mappers\RecursiveTypeMapperInterface; -use TheCodingMachine\GraphQLite\Mappers\Root\BaseTypeMapper; -use TheCodingMachine\GraphQLite\Mappers\Root\CompoundTypeMapper; -use TheCodingMachine\GraphQLite\Mappers\Root\EnumTypeMapper; -use TheCodingMachine\GraphQLite\Mappers\Root\FinalRootTypeMapper; -use TheCodingMachine\GraphQLite\Mappers\Root\IteratorTypeMapper; -use TheCodingMachine\GraphQLite\Mappers\Root\LastDelegatingTypeMapper; -use TheCodingMachine\GraphQLite\Mappers\Root\MyCLabsEnumTypeMapper; -use TheCodingMachine\GraphQLite\Mappers\Root\NullableTypeMapperAdapter; -use TheCodingMachine\GraphQLite\Mappers\Root\RootTypeMapperInterface; -use TheCodingMachine\GraphQLite\Mappers\Root\VoidTypeMapper; -use TheCodingMachine\GraphQLite\Mappers\TypeMapperInterface; -use TheCodingMachine\GraphQLite\Middlewares\AuthorizationFieldMiddleware; -use TheCodingMachine\GraphQLite\Middlewares\AuthorizationInputFieldMiddleware; -use TheCodingMachine\GraphQLite\Middlewares\CostFieldMiddleware; -use TheCodingMachine\GraphQLite\Middlewares\FieldMiddlewareInterface; -use TheCodingMachine\GraphQLite\Middlewares\FieldMiddlewarePipe; -use TheCodingMachine\GraphQLite\Middlewares\InputFieldMiddlewareInterface; -use TheCodingMachine\GraphQLite\Middlewares\InputFieldMiddlewarePipe; use TheCodingMachine\GraphQLite\Middlewares\MissingAuthorizationException; use TheCodingMachine\GraphQLite\Middlewares\PrefetchFieldMiddleware; -use TheCodingMachine\GraphQLite\Middlewares\SecurityFieldMiddleware; -use TheCodingMachine\GraphQLite\Middlewares\SecurityInputFieldMiddleware; -use TheCodingMachine\GraphQLite\NamingStrategy; -use TheCodingMachine\GraphQLite\NamingStrategyInterface; -use TheCodingMachine\GraphQLite\ParameterizedCallableResolver; -use TheCodingMachine\GraphQLite\QueryProviderInterface; -use TheCodingMachine\GraphQLite\Reflection\CachedDocBlockFactory; use TheCodingMachine\GraphQLite\Schema; use TheCodingMachine\GraphQLite\SchemaFactory; use TheCodingMachine\GraphQLite\Security\AuthenticationServiceInterface; use TheCodingMachine\GraphQLite\Security\AuthorizationServiceInterface; -use TheCodingMachine\GraphQLite\Security\SecurityExpressionLanguageProvider; use TheCodingMachine\GraphQLite\Security\VoidAuthenticationService; -use TheCodingMachine\GraphQLite\Security\VoidAuthorizationService; -use TheCodingMachine\GraphQLite\TypeGenerator; use TheCodingMachine\GraphQLite\TypeMismatchRuntimeException; -use TheCodingMachine\GraphQLite\TypeRegistry; -use TheCodingMachine\GraphQLite\Types\ArgumentResolver; -use TheCodingMachine\GraphQLite\Types\TypeResolver; use TheCodingMachine\GraphQLite\Utils\AccessPropertyException; -use TheCodingMachine\GraphQLite\Utils\Namespaces\NamespaceFactory; -use UnitEnum; - use function array_filter; use function assert; use function count; use function in_array; -use function interface_exists; use function json_encode; - use const JSON_PRETTY_PRINT; class EndToEndTest extends IntegrationTestCase diff --git a/tests/Integration/IntegrationTestCase.php b/tests/Integration/IntegrationTestCase.php index d363393d5f..16e90ceb91 100644 --- a/tests/Integration/IntegrationTestCase.php +++ b/tests/Integration/IntegrationTestCase.php @@ -7,6 +7,7 @@ use GraphQL\Executor\ExecutionResult; use Kcs\ClassFinder\Finder\ComposerFinder; use Kcs\ClassFinder\Finder\FinderInterface; +use phpDocumentor\Reflection\Types\ContextFactory; use PHPUnit\Framework\TestCase; use Psr\Container\ContainerInterface; use stdClass; @@ -14,6 +15,9 @@ use Symfony\Component\Cache\Adapter\Psr16Adapter; use Symfony\Component\Cache\Psr16Cache; use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use TheCodingMachine\CacheUtils\ClassBoundCache; +use TheCodingMachine\CacheUtils\ClassBoundCacheContract; +use TheCodingMachine\CacheUtils\FileBoundCache; use TheCodingMachine\GraphQLite\AggregateQueryProvider; use TheCodingMachine\GraphQLite\AnnotationReader; use TheCodingMachine\GraphQLite\Containers\BasicAutoWiringContainer; @@ -59,7 +63,12 @@ use TheCodingMachine\GraphQLite\NamingStrategyInterface; use TheCodingMachine\GraphQLite\ParameterizedCallableResolver; use TheCodingMachine\GraphQLite\QueryProviderInterface; -use TheCodingMachine\GraphQLite\Reflection\CachedDocBlockFactory; +use TheCodingMachine\GraphQLite\Reflection\DocBlock\CachedDocBlockContextFactory; +use TheCodingMachine\GraphQLite\Reflection\DocBlock\CachedDocBlockFactory; +use TheCodingMachine\GraphQLite\Reflection\DocBlock\DocBlockContextFactory; +use TheCodingMachine\GraphQLite\Reflection\DocBlock\DocBlockFactory; +use TheCodingMachine\GraphQLite\Reflection\DocBlock\PhpDocumentorDocBlockContextFactory; +use TheCodingMachine\GraphQLite\Reflection\DocBlock\PhpDocumentorDocBlockFactory; use TheCodingMachine\GraphQLite\Schema; use TheCodingMachine\GraphQLite\Security\AuthenticationServiceInterface; use TheCodingMachine\GraphQLite\Security\AuthorizationServiceInterface; @@ -71,7 +80,6 @@ use TheCodingMachine\GraphQLite\Types\ArgumentResolver; use TheCodingMachine\GraphQLite\Types\TypeResolver; use TheCodingMachine\GraphQLite\Utils\Namespaces\NamespaceFactory; -use UnitEnum; class IntegrationTestCase extends TestCase { @@ -121,7 +129,8 @@ public function createContainer(array $overloadedServices = []): ContainerInterf $container->get(RecursiveTypeMapperInterface::class), $container->get(ArgumentResolver::class), $container->get(TypeResolver::class), - $container->get(CachedDocBlockFactory::class), + $container->get(DocBlockFactory::class), + $container->get(DocBlockContextFactory::class), $container->get(NamingStrategyInterface::class), $container->get(RootTypeMapperInterface::class), $parameterMiddlewarePipe, @@ -287,10 +296,36 @@ public function createContainer(array $overloadedServices = []): ContainerInterf NamingStrategyInterface::class => static function () { return new NamingStrategy(); }, - CachedDocBlockFactory::class => static function () { + 'nonInheritedClassBoundCacheContract' => static function () { $arrayAdapter = new ArrayAdapter(); $arrayAdapter->setLogger(new ExceptionLogger()); - return new CachedDocBlockFactory(new Psr16Cache($arrayAdapter)); + $psr16Cache = new Psr16Cache($arrayAdapter); + + return new ClassBoundCacheContract( + new ClassBoundCache( + fileBoundCache: new FileBoundCache($psr16Cache), + analyzeParentClasses: false, + analyzeTraits: false, + analyzeInterfaces: false, + ), + ); + }, + DocBlockFactory::class => static function (ContainerInterface $container) { + return new CachedDocBlockFactory( + $container->get('nonInheritedClassBoundCacheContract'), + new PhpDocumentorDocBlockFactory( + \phpDocumentor\Reflection\DocBlockFactory::createInstance(), + $container->get(DocBlockContextFactory::class), + ) + ); + }, + DocBlockContextFactory::class => static function (ContainerInterface $container) { + return new CachedDocBlockContextFactory( + $container->get('nonInheritedClassBoundCacheContract'), + new PhpDocumentorDocBlockContextFactory( + new ContextFactory(), + ) + ); }, RootTypeMapperInterface::class => static function (ContainerInterface $container) { return new VoidTypeMapper( diff --git a/tests/Mappers/Parameters/TypeMapperTest.php b/tests/Mappers/Parameters/TypeMapperTest.php index 36d66746bc..71deee9bc9 100644 --- a/tests/Mappers/Parameters/TypeMapperTest.php +++ b/tests/Mappers/Parameters/TypeMapperTest.php @@ -2,7 +2,6 @@ namespace TheCodingMachine\GraphQLite\Mappers\Parameters; -use DateTimeImmutable; use GraphQL\Type\Definition\NonNull; use GraphQL\Type\Definition\ResolveInfo; use GraphQL\Type\Definition\UnionType; @@ -10,29 +9,28 @@ use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\Cache\Psr16Cache; use TheCodingMachine\GraphQLite\AbstractQueryProviderTest; -use TheCodingMachine\GraphQLite\Annotations\HideParameter; use TheCodingMachine\GraphQLite\Fixtures\UnionOutputType; use TheCodingMachine\GraphQLite\Mappers\CannotMapTypeException; use TheCodingMachine\GraphQLite\Parameters\DefaultValueParameter; use TheCodingMachine\GraphQLite\Parameters\InputTypeParameter; -use TheCodingMachine\GraphQLite\Reflection\CachedDocBlockFactory; +use TheCodingMachine\GraphQLite\Reflection\DocBlock\CachedDocBlockFactory; class TypeMapperTest extends AbstractQueryProviderTest { public function testMapScalarUnionException(): void { + $docBlockFactory = $this->getDocBlockFactory(); + $typeMapper = new TypeHandler( $this->getArgumentResolver(), $this->getRootTypeMapper(), $this->getTypeResolver(), - $this->getCachedDocBlockFactory(), + $docBlockFactory, ); - $cachedDocBlockFactory = new CachedDocBlockFactory(new Psr16Cache(new ArrayAdapter())); - $refMethod = new ReflectionMethod($this, 'dummy'); - $docBlockObj = $cachedDocBlockFactory->getDocBlock($refMethod); + $docBlockObj = $docBlockFactory->createFromReflector($refMethod); $this->expectException(CannotMapTypeException::class); $this->expectExceptionMessage('For return type of TheCodingMachine\GraphQLite\Mappers\Parameters\TypeMapperTest::dummy, in GraphQL, you can only use union types between objects. These types cannot be used in union types: Int!, String!'); @@ -41,17 +39,17 @@ public function testMapScalarUnionException(): void public function testMapObjectUnionWorks(): void { - $cachedDocBlockFactory = $this->getCachedDocBlockFactory(); + $docBlockFactory = $this->getDocBlockFactory(); $typeMapper = new TypeHandler( $this->getArgumentResolver(), $this->getRootTypeMapper(), $this->getTypeResolver(), - $cachedDocBlockFactory, + $docBlockFactory, ); $refMethod = new ReflectionMethod(UnionOutputType::class, 'objectUnion'); - $docBlockObj = $cachedDocBlockFactory->getDocBlock($refMethod); + $docBlockObj = $docBlockFactory->createFromReflector($refMethod); $gqType = $typeMapper->mapReturnType($refMethod, $docBlockObj); $this->assertInstanceOf(NonNull::class, $gqType); @@ -66,17 +64,17 @@ public function testMapObjectUnionWorks(): void public function testMapObjectNullableUnionWorks(): void { - $cachedDocBlockFactory = $this->getCachedDocBlockFactory(); + $docBlockFactory = $this->getDocBlockFactory(); $typeMapper = new TypeHandler( $this->getArgumentResolver(), $this->getRootTypeMapper(), $this->getTypeResolver(), - $cachedDocBlockFactory, + $docBlockFactory, ); $refMethod = new ReflectionMethod(UnionOutputType::class, 'nullableObjectUnion'); - $docBlockObj = $cachedDocBlockFactory->getDocBlock($refMethod); + $docBlockObj = $docBlockFactory->createFromReflector($refMethod); $gqType = $typeMapper->mapReturnType($refMethod, $docBlockObj); $this->assertNotInstanceOf(NonNull::class, $gqType); @@ -92,18 +90,18 @@ public function testMapObjectNullableUnionWorks(): void public function testHideParameter(): void { - $cachedDocBlockFactory = $this->getCachedDocBlockFactory(); + $docBlockFactory = $this->getDocBlockFactory(); $typeMapper = new TypeHandler( $this->getArgumentResolver(), $this->getRootTypeMapper(), $this->getTypeResolver(), - $cachedDocBlockFactory, + $docBlockFactory, ); $refMethod = new ReflectionMethod($this, 'withDefaultValue'); $refParameter = $refMethod->getParameters()[0]; - $docBlockObj = $cachedDocBlockFactory->getDocBlock($refMethod); + $docBlockObj = $docBlockFactory->createFromReflector($refMethod); $annotations = $this->getAnnotationReader()->getParameterAnnotations($refParameter); $param = $typeMapper->mapParameter($refParameter, $docBlockObj, null, $annotations); @@ -116,17 +114,17 @@ public function testHideParameter(): void public function testParameterWithDescription(): void { - $cachedDocBlockFactory = $this->getCachedDocBlockFactory(); + $docBlockFactory = $this->getDocBlockFactory(); $typeMapper = new TypeHandler( $this->getArgumentResolver(), $this->getRootTypeMapper(), $this->getTypeResolver(), - $cachedDocBlockFactory, + $docBlockFactory, ); $refMethod = new ReflectionMethod($this, 'withParamDescription'); - $docBlockObj = $cachedDocBlockFactory->getDocBlock($refMethod); + $docBlockObj = $docBlockFactory->createFromReflector($refMethod); $refParameter = $refMethod->getParameters()[0]; $parameter = $typeMapper->mapParameter($refParameter, $docBlockObj, null, $this->getAnnotationReader()->getParameterAnnotations($refParameter)); @@ -137,18 +135,18 @@ public function testParameterWithDescription(): void public function testHideParameterException(): void { - $cachedDocBlockFactory = $this->getCachedDocBlockFactory(); + $docBlockFactory = $this->getDocBlockFactory(); $typeMapper = new TypeHandler( $this->getArgumentResolver(), $this->getRootTypeMapper(), $this->getTypeResolver(), - $cachedDocBlockFactory, + $docBlockFactory, ); $refMethod = new ReflectionMethod($this, 'withoutDefaultValue'); $refParameter = $refMethod->getParameters()[0]; - $docBlockObj = $cachedDocBlockFactory->getDocBlock($refMethod); + $docBlockObj = $docBlockFactory->createFromReflector($refMethod); $annotations = $this->getAnnotationReader()->getParameterAnnotations($refParameter); $this->expectException(CannotHideParameterRuntimeException::class); diff --git a/tests/Reflection/CachedDocBlockFactoryTest.php b/tests/Reflection/CachedDocBlockFactoryTest.php index 1f3a5fbed8..47168df8a8 100644 --- a/tests/Reflection/CachedDocBlockFactoryTest.php +++ b/tests/Reflection/CachedDocBlockFactoryTest.php @@ -8,6 +8,7 @@ use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\Cache\Psr16Cache; use Symfony\Component\Cache\Simple\ArrayCache; +use TheCodingMachine\GraphQLite\Reflection\DocBlock\CachedDocBlockFactory; class CachedDocBlockFactoryTest extends TestCase { @@ -28,21 +29,4 @@ public function testGetDocBlock(): void $docBlock3 = $newCachedDocBlockFactory->getDocBlock($refMethod); $this->assertEquals($docBlock3, $docBlock); } - - public function testGetContext(): void - { - $arrayCache = new Psr16Cache(new ArrayAdapter()); - $cachedDocBlockFactory = new CachedDocBlockFactory($arrayCache); - - $refClass = new ReflectionClass(CachedDocBlockFactory::class); - - $context = $cachedDocBlockFactory->getContextFromClass($refClass); - - $context2 = $cachedDocBlockFactory->getContextFromClass($refClass); - $this->assertSame($context2, $context); - - $newCachedDocBlockFactory = new CachedDocBlockFactory($arrayCache); - $context3 = $newCachedDocBlockFactory->getContextFromClass($refClass); - $this->assertEquals($context3, $context); - } } From c6be5aba0829c5f298405159900c4f3801ebc9b0 Mon Sep 17 00:00:00 2001 From: Oleksandr Prypkhan Date: Fri, 29 Mar 2024 18:51:40 +0200 Subject: [PATCH 03/28] Refactor field name --- src/Mappers/Root/EnumTypeMapper.php | 16 ++++++++-------- src/Mappers/Root/MyCLabsEnumTypeMapper.php | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Mappers/Root/EnumTypeMapper.php b/src/Mappers/Root/EnumTypeMapper.php index 89a7e041e9..c01ec784be 100644 --- a/src/Mappers/Root/EnumTypeMapper.php +++ b/src/Mappers/Root/EnumTypeMapper.php @@ -33,7 +33,7 @@ class EnumTypeMapper implements RootTypeMapperInterface { /** @var array, EnumType> */ - private array $cache = []; + private array $cacheByClass = []; /** @var array */ private array $cacheByName = []; /** @var array> */ @@ -42,9 +42,9 @@ class EnumTypeMapper implements RootTypeMapperInterface /** @param NS[] $namespaces List of namespaces containing enums. Used when searching an enum by name. */ public function __construct( private readonly RootTypeMapperInterface $next, - private readonly AnnotationReader $annotationReader, - private readonly CacheInterface $cacheService, - private readonly array $namespaces, + private readonly AnnotationReader $annotationReader, + private readonly CacheInterface $cache, + private readonly array $namespaces, ) { } @@ -102,8 +102,8 @@ private function mapByClassName(string $enumClass): EnumType|null } /** @var class-string $enumClass */ $enumClass = ltrim($enumClass, '\\'); - if (isset($this->cache[$enumClass])) { - return $this->cache[$enumClass]; + if (isset($this->cacheByClass[$enumClass])) { + return $this->cacheByClass[$enumClass]; } // phpcs:disable SlevomatCodingStandard.Commenting.InlineDocCommentDeclaration.MissingVariable @@ -155,7 +155,7 @@ private function mapByClassName(string $enumClass): EnumType|null $type = new EnumType($enumClass, $typeName, $enumDescription, $enumCaseDescriptions, $enumCaseDeprecationReasons, $useValues); - return $this->cacheByName[$type->name] = $this->cache[$enumClass] = $type; + return $this->cacheByName[$type->name] = $this->cacheByClass[$enumClass] = $type; } private function getTypeName(ReflectionClass $reflectionClass): string @@ -200,7 +200,7 @@ public function mapNameToType(string $typeName): NamedType&GraphQLType private function getNameToClassMapping(): array { if ($this->nameToClassMapping === null) { - $this->nameToClassMapping = $this->cacheService->get('enum_name_to_class', function () { + $this->nameToClassMapping = $this->cache->get('enum_name_to_class', function () { $nameToClassMapping = []; foreach ($this->namespaces as $ns) { foreach ($ns->getClassList() as $className => $classRef) { diff --git a/src/Mappers/Root/MyCLabsEnumTypeMapper.php b/src/Mappers/Root/MyCLabsEnumTypeMapper.php index 7a2d7eae2c..abe5d5a6a4 100644 --- a/src/Mappers/Root/MyCLabsEnumTypeMapper.php +++ b/src/Mappers/Root/MyCLabsEnumTypeMapper.php @@ -30,7 +30,7 @@ class MyCLabsEnumTypeMapper implements RootTypeMapperInterface { /** @var array, EnumType> */ - private array $cache = []; + private array $cacheByClass = []; /** @var array */ private array $cacheByName = []; @@ -40,9 +40,9 @@ class MyCLabsEnumTypeMapper implements RootTypeMapperInterface /** @param NS[] $namespaces List of namespaces containing enums. Used when searching an enum by name. */ public function __construct( private readonly RootTypeMapperInterface $next, - private readonly AnnotationReader $annotationReader, - private readonly CacheInterface $cacheService, - private readonly array $namespaces, + private readonly AnnotationReader $annotationReader, + private readonly CacheInterface $cache, + private readonly array $namespaces, ) { } @@ -98,13 +98,13 @@ private function mapByClassName(string $enumClass): EnumType|null } /** @var class-string $enumClass */ $enumClass = ltrim($enumClass, '\\'); - if (isset($this->cache[$enumClass])) { - return $this->cache[$enumClass]; + if (isset($this->cacheByClass[$enumClass])) { + return $this->cacheByClass[$enumClass]; } $refClass = new ReflectionClass($enumClass); $type = new MyCLabsEnumType($enumClass, $this->getTypeName($refClass)); - return $this->cacheByName[$type->name] = $this->cache[$enumClass] = $type; + return $this->cacheByName[$type->name] = $this->cacheByClass[$enumClass] = $type; } private function getTypeName(ReflectionClass $refClass): string @@ -155,7 +155,7 @@ public function mapNameToType(string $typeName): NamedType&\GraphQL\Type\Definit private function getNameToClassMapping(): array { if ($this->nameToClassMapping === null) { - $this->nameToClassMapping = $this->cacheService->get('myclabsenum_name_to_class', function () { + $this->nameToClassMapping = $this->cache->get('myclabsenum_name_to_class', function () { $nameToClassMapping = []; foreach ($this->namespaces as $ns) { /** @var class-string $className */ From bb8e81bd3367d118790623a9c5c086b3c6f6271c Mon Sep 17 00:00:00 2001 From: Oleksandr Prypkhan Date: Sun, 31 Mar 2024 00:30:22 +0200 Subject: [PATCH 04/28] Refactor code to use ClassFinder instead of separating namespaces --- src/Discovery/ClassFinder.php | 11 +++ src/Discovery/EmptyClassFinder.php | 13 +++ src/Discovery/KcsClassFinder.php | 20 +++++ .../OldCachedClassFinder.php} | 53 +++--------- src/GlobControllerQueryProvider.php | 16 ++-- src/Mappers/GlobTypeMapper.php | 20 +---- src/Mappers/Root/EnumTypeMapper.php | 17 ++-- src/Mappers/Root/MyCLabsEnumTypeMapper.php | 19 ++-- .../Root/RootTypeMapperFactoryContext.php | 14 +-- src/SchemaFactory.php | 86 +++++++++++++------ src/Utils/Namespaces/NamespaceFactory.php | 29 ------- tests/AbstractQueryProviderTest.php | 22 +++-- tests/GlobControllerQueryProviderTest.php | 4 +- tests/Integration/IntegrationTestCase.php | 57 +++++------- tests/Mappers/GlobTypeMapperTest.php | 38 ++++---- tests/Mappers/RecursiveTypeMapperTest.php | 4 +- tests/RootTypeMapperFactoryContextTest.php | 7 +- 17 files changed, 206 insertions(+), 224 deletions(-) create mode 100644 src/Discovery/ClassFinder.php create mode 100644 src/Discovery/EmptyClassFinder.php create mode 100644 src/Discovery/KcsClassFinder.php rename src/{Utils/Namespaces/NS.php => Discovery/OldCachedClassFinder.php} (55%) delete mode 100644 src/Utils/Namespaces/NamespaceFactory.php diff --git a/src/Discovery/ClassFinder.php b/src/Discovery/ClassFinder.php new file mode 100644 index 0000000000..b4e352b538 --- /dev/null +++ b/src/Discovery/ClassFinder.php @@ -0,0 +1,11 @@ +> + */ +interface ClassFinder extends \IteratorAggregate +{ + +} \ No newline at end of file diff --git a/src/Discovery/EmptyClassFinder.php b/src/Discovery/EmptyClassFinder.php new file mode 100644 index 0000000000..ebbc7389ba --- /dev/null +++ b/src/Discovery/EmptyClassFinder.php @@ -0,0 +1,13 @@ +finder->getIterator(); + } +} \ No newline at end of file diff --git a/src/Utils/Namespaces/NS.php b/src/Discovery/OldCachedClassFinder.php similarity index 55% rename from src/Utils/Namespaces/NS.php rename to src/Discovery/OldCachedClassFinder.php index f1c51f3a18..2e34c4fb33 100644 --- a/src/Utils/Namespaces/NS.php +++ b/src/Discovery/OldCachedClassFinder.php @@ -1,59 +1,33 @@ > + * @var array> */ private array|null $classes = null; - /** @param string $namespace The namespace that contains the GraphQL types (they must have a `@Type` annotation) */ public function __construct( - private readonly string $namespace, + private readonly ClassFinder $finder, private readonly CacheInterface $cache, - private readonly FinderInterface $finder, - private readonly int|null $globTTL, + private readonly int|null $globTTL = null, ) { } - /** - * Returns the array of globbed classes. - * Only instantiable classes are returned. - * - * @return array> Key: fully qualified class name - */ - public function getClassList(): array + public function getIterator(): \Traversable { if ($this->classes === null) { - $cacheKey = 'GraphQLite_NS_' . preg_replace('/[\/{}()\\\\@:]/', '', $this->namespace); + $cacheKey = 'GraphQLite_NS_'; try { $classes = $this->cache->get($cacheKey); if ($classes !== null) { @@ -78,8 +52,8 @@ public function getClassList(): array if ($this->classes === null) { $this->classes = []; /** @var class-string $className */ - /** @var ReflectionClass $reflector */ - foreach ((clone $this->finder)->inNamespace($this->namespace) as $className => $reflector) { + /** @var \ReflectionClass $reflector */ + foreach ($this->finder as $className => $reflector) { if (! ($reflector instanceof ReflectionClass)) { continue; } @@ -94,11 +68,6 @@ public function getClassList(): array } } - return $this->classes; - } - - public function getNamespace(): string - { - return $this->namespace; + return new \ArrayIterator($this->classes); } -} +} \ No newline at end of file diff --git a/src/GlobControllerQueryProvider.php b/src/GlobControllerQueryProvider.php index f7ed17170e..bb5d5cbd37 100644 --- a/src/GlobControllerQueryProvider.php +++ b/src/GlobControllerQueryProvider.php @@ -6,7 +6,6 @@ use GraphQL\Type\Definition\FieldDefinition; use InvalidArgumentException; -use Kcs\ClassFinder\Finder\FinderInterface; use Psr\Container\ContainerInterface; use Psr\SimpleCache\CacheInterface; use ReflectionClass; @@ -17,6 +16,7 @@ use TheCodingMachine\GraphQLite\Annotations\Query; use TheCodingMachine\GraphQLite\Annotations\Subscription; +use TheCodingMachine\GraphQLite\Discovery\ClassFinder; use function class_exists; use function interface_exists; use function is_array; @@ -40,13 +40,13 @@ final class GlobControllerQueryProvider implements QueryProviderInterface * @param ContainerInterface $container The container we will fetch controllers from. */ public function __construct( - private readonly string $namespace, - private readonly FieldsBuilder $fieldsBuilder, + private readonly string $namespace, + private readonly FieldsBuilder $fieldsBuilder, private readonly ContainerInterface $container, - private readonly AnnotationReader $annotationReader, - private readonly CacheInterface $cache, - private readonly FinderInterface $finder, - int|null $cacheTtl = null, + private readonly AnnotationReader $annotationReader, + private readonly CacheInterface $cache, + private readonly ClassFinder $classFinder, + int|null $cacheTtl = null, ) { $this->cacheContract = new Psr16Adapter( @@ -92,7 +92,7 @@ private function getInstancesList(): array private function buildInstancesList(): array { $instances = []; - foreach ((clone $this->finder)->inNamespace($this->namespace) as $className => $refClass) { + foreach ($this->classFinder as $className => $refClass) { if (! class_exists($className) && ! interface_exists($className)) { continue; } diff --git a/src/Mappers/GlobTypeMapper.php b/src/Mappers/GlobTypeMapper.php index 132673b898..64df14b696 100644 --- a/src/Mappers/GlobTypeMapper.php +++ b/src/Mappers/GlobTypeMapper.php @@ -8,11 +8,11 @@ use Psr\SimpleCache\CacheInterface; use ReflectionClass; use TheCodingMachine\GraphQLite\AnnotationReader; +use TheCodingMachine\GraphQLite\Discovery\ClassFinder; use TheCodingMachine\GraphQLite\InputTypeGenerator; use TheCodingMachine\GraphQLite\InputTypeUtils; use TheCodingMachine\GraphQLite\NamingStrategyInterface; use TheCodingMachine\GraphQLite\TypeGenerator; -use TheCodingMachine\GraphQLite\Utils\Namespaces\NS; use function str_replace; @@ -26,14 +26,8 @@ */ final class GlobTypeMapper extends AbstractTypeMapper { - /** - * Constructor - * - * @param NS $namespace The namespace that contains the GraphQL types - * (they must have a `@Type` annotation) - */ public function __construct( - private NS $namespace, + private ClassFinder $classFinder, TypeGenerator $typeGenerator, InputTypeGenerator $inputTypeGenerator, InputTypeUtils $inputTypeUtils, @@ -45,14 +39,8 @@ public function __construct( int|null $globTTL = 2, int|null $mapTTL = null, ) { - $cachePrefix = str_replace( - ['\\', '{', '}', '(', ')', '/', '@', ':'], - '_', - $namespace->getNamespace(), - ); - parent::__construct( - $cachePrefix, + '', $typeGenerator, $inputTypeGenerator, $inputTypeUtils, @@ -74,6 +62,6 @@ public function __construct( */ protected function getClassList(): array { - return $this->namespace->getClassList(); + return iterator_to_array($this->classFinder); } } diff --git a/src/Mappers/Root/EnumTypeMapper.php b/src/Mappers/Root/EnumTypeMapper.php index c01ec784be..327306e681 100644 --- a/src/Mappers/Root/EnumTypeMapper.php +++ b/src/Mappers/Root/EnumTypeMapper.php @@ -19,8 +19,8 @@ use ReflectionProperty; use Symfony\Contracts\Cache\CacheInterface; use TheCodingMachine\GraphQLite\AnnotationReader; +use TheCodingMachine\GraphQLite\Discovery\ClassFinder; use TheCodingMachine\GraphQLite\Types\EnumType; -use TheCodingMachine\GraphQLite\Utils\Namespaces\NS; use UnitEnum; use function assert; @@ -39,12 +39,11 @@ class EnumTypeMapper implements RootTypeMapperInterface /** @var array> */ private array|null $nameToClassMapping = null; - /** @param NS[] $namespaces List of namespaces containing enums. Used when searching an enum by name. */ public function __construct( private readonly RootTypeMapperInterface $next, private readonly AnnotationReader $annotationReader, private readonly CacheInterface $cache, - private readonly array $namespaces, + private readonly ClassFinder $classFinder, ) { } @@ -202,14 +201,12 @@ private function getNameToClassMapping(): array if ($this->nameToClassMapping === null) { $this->nameToClassMapping = $this->cache->get('enum_name_to_class', function () { $nameToClassMapping = []; - foreach ($this->namespaces as $ns) { - foreach ($ns->getClassList() as $className => $classRef) { - if (! enum_exists($className)) { - continue; - } - - $nameToClassMapping[$this->getTypeName($classRef)] = $className; + foreach ($this->classFinder as $className => $classRef) { + if (! enum_exists($className)) { + continue; } + + $nameToClassMapping[$this->getTypeName($classRef)] = $className; } return $nameToClassMapping; }); diff --git a/src/Mappers/Root/MyCLabsEnumTypeMapper.php b/src/Mappers/Root/MyCLabsEnumTypeMapper.php index abe5d5a6a4..a2f35c9075 100644 --- a/src/Mappers/Root/MyCLabsEnumTypeMapper.php +++ b/src/Mappers/Root/MyCLabsEnumTypeMapper.php @@ -17,8 +17,8 @@ use ReflectionProperty; use Symfony\Contracts\Cache\CacheInterface; use TheCodingMachine\GraphQLite\AnnotationReader; +use TheCodingMachine\GraphQLite\Discovery\ClassFinder; use TheCodingMachine\GraphQLite\Types\MyCLabsEnumType; -use TheCodingMachine\GraphQLite\Utils\Namespaces\NS; use function assert; use function is_a; @@ -37,12 +37,11 @@ class MyCLabsEnumTypeMapper implements RootTypeMapperInterface /** @var array> */ private array|null $nameToClassMapping = null; - /** @param NS[] $namespaces List of namespaces containing enums. Used when searching an enum by name. */ public function __construct( private readonly RootTypeMapperInterface $next, private readonly AnnotationReader $annotationReader, private readonly CacheInterface $cache, - private readonly array $namespaces, + private readonly ClassFinder $classFinder, ) { } @@ -157,15 +156,13 @@ private function getNameToClassMapping(): array if ($this->nameToClassMapping === null) { $this->nameToClassMapping = $this->cache->get('myclabsenum_name_to_class', function () { $nameToClassMapping = []; - foreach ($this->namespaces as $ns) { - /** @var class-string $className */ - foreach ($ns->getClassList() as $className => $classRef) { - if (! $classRef->isSubclassOf(Enum::class)) { - continue; - } - - $nameToClassMapping[$this->getTypeName($classRef)] = $className; + /** @var class-string $className */ + foreach ($this->classFinder as $className => $classRef) { + if (! $classRef->isSubclassOf(Enum::class)) { + continue; } + + $nameToClassMapping[$this->getTypeName($classRef)] = $className; } return $nameToClassMapping; diff --git a/src/Mappers/Root/RootTypeMapperFactoryContext.php b/src/Mappers/Root/RootTypeMapperFactoryContext.php index a5e1c1cb11..3f85e206d4 100644 --- a/src/Mappers/Root/RootTypeMapperFactoryContext.php +++ b/src/Mappers/Root/RootTypeMapperFactoryContext.php @@ -7,11 +7,11 @@ use Psr\Container\ContainerInterface; use Psr\SimpleCache\CacheInterface; use TheCodingMachine\GraphQLite\AnnotationReader; +use TheCodingMachine\GraphQLite\Discovery\ClassFinder; use TheCodingMachine\GraphQLite\Mappers\RecursiveTypeMapperInterface; use TheCodingMachine\GraphQLite\NamingStrategyInterface; use TheCodingMachine\GraphQLite\TypeRegistry; use TheCodingMachine\GraphQLite\Types\TypeResolver; -use TheCodingMachine\GraphQLite\Utils\Namespaces\NS; /** * A context class containing a number of classes created on the fly by SchemaFactory. @@ -19,11 +19,6 @@ */ final class RootTypeMapperFactoryContext { - /** - * Constructor - * - * @param iterable $typeNamespaces - */ public function __construct( private readonly AnnotationReader $annotationReader, private readonly TypeResolver $typeResolver, @@ -32,7 +27,7 @@ public function __construct( private readonly RecursiveTypeMapperInterface $recursiveTypeMapper, private readonly ContainerInterface $container, private readonly CacheInterface $cache, - private readonly iterable $typeNamespaces, + private readonly ClassFinder $classFinder, private readonly int|null $globTTL, private readonly int|null $mapTTL = null, ) { @@ -73,10 +68,9 @@ public function getCache(): CacheInterface return $this->cache; } - /** @return iterable */ - public function getTypeNamespaces(): iterable + public function getClassFinder(): ClassFinder { - return $this->typeNamespaces; + return $this->classFinder; } public function getGlobTTL(): int|null diff --git a/src/SchemaFactory.php b/src/SchemaFactory.php index 911dd36878..d326ed3cf5 100644 --- a/src/SchemaFactory.php +++ b/src/SchemaFactory.php @@ -21,8 +21,13 @@ use Symfony\Component\ExpressionLanguage\ExpressionLanguage; use TheCodingMachine\CacheUtils\ClassBoundCache; use TheCodingMachine\CacheUtils\ClassBoundCacheContract; +use TheCodingMachine\CacheUtils\ClassBoundCacheInterface; use TheCodingMachine\CacheUtils\ClassBoundMemoryAdapter; use TheCodingMachine\CacheUtils\FileBoundCache; +use TheCodingMachine\GraphQLite\Discovery\ClassFinder; +use TheCodingMachine\GraphQLite\Discovery\EmptyClassFinder; +use TheCodingMachine\GraphQLite\Discovery\KcsClassFinder; +use TheCodingMachine\GraphQLite\Discovery\OldCachedClassFinder; use TheCodingMachine\GraphQLite\Mappers\CompositeTypeMapper; use TheCodingMachine\GraphQLite\Mappers\GlobTypeMapper; use TheCodingMachine\GraphQLite\Mappers\Parameters\ContainerParameterHandler; @@ -68,7 +73,6 @@ use TheCodingMachine\GraphQLite\Types\InputTypeValidatorInterface; use TheCodingMachine\GraphQLite\Types\TypeResolver; use TheCodingMachine\GraphQLite\Utils\NamespacedCache; -use TheCodingMachine\GraphQLite\Utils\Namespaces\NamespaceFactory; use function array_map; use function array_reverse; use function class_exists; @@ -372,26 +376,10 @@ public function createSchema(): Schema analyzeTraits: false, analyzeInterfaces: false, ); - $docBlockContextFactory = new CachedDocBlockContextFactory( - new ClassBoundCacheContract(new ClassBoundMemoryAdapter($nonInheritedClassBoundCache)), - new PhpDocumentorDocBlockContextFactory(new ContextFactory()) - ); - $docBlockFactory = new CachedDocBlockFactory( - new ClassBoundCacheContract(new ClassBoundMemoryAdapter($nonInheritedClassBoundCache)), - new PhpDocumentorDocBlockFactory( - DocBlockFactory::createInstance(), - $docBlockContextFactory, - ), - ); + [$docBlockFactory, $docBlockContextFactory] = $this->createDocBlockFactory($nonInheritedClassBoundCache); $namingStrategy = $this->namingStrategy ?: new NamingStrategy(); $typeRegistry = new TypeRegistry(); - $finder = $this->finder ?? new ComposerFinder(); - - $namespaceFactory = new NamespaceFactory($namespacedCache, $finder, $this->globTTL); - $nsList = array_map( - static fn (string $namespace) => $namespaceFactory->createNamespace($namespace), - $this->namespaces, - ); + $classFinder = $this->createClassFinder($namespacedCache); $expressionLanguage = $this->expressionLanguage ?: new ExpressionLanguage($symfonyCache); $expressionLanguage->registerProvider(new SecurityExpressionLanguageProvider()); @@ -422,14 +410,14 @@ public function createSchema(): Schema $errorRootTypeMapper = new FinalRootTypeMapper($recursiveTypeMapper); $rootTypeMapper = new BaseTypeMapper($errorRootTypeMapper, $recursiveTypeMapper, $topRootTypeMapper); - $rootTypeMapper = new EnumTypeMapper($rootTypeMapper, $annotationReader, $symfonyCache, $nsList); + $rootTypeMapper = new EnumTypeMapper($rootTypeMapper, $annotationReader, $symfonyCache, $classFinder); if (class_exists(Enum::class)) { // Annotation support - deprecated - $rootTypeMapper = new MyCLabsEnumTypeMapper($rootTypeMapper, $annotationReader, $symfonyCache, $nsList); + $rootTypeMapper = new MyCLabsEnumTypeMapper($rootTypeMapper, $annotationReader, $symfonyCache, $classFinder); } - if (! empty($this->rootTypeMapperFactories)) { + if (!empty($this->rootTypeMapperFactories)) { $rootSchemaFactoryContext = new RootTypeMapperFactoryContext( $annotationReader, $typeResolver, @@ -438,7 +426,7 @@ public function createSchema(): Schema $recursiveTypeMapper, $this->container, $namespacedCache, - $nsList, + $classFinder, $this->globTTL, ); @@ -483,9 +471,9 @@ public function createSchema(): Schema $inputTypeUtils = new InputTypeUtils($annotationReader, $namingStrategy); $inputTypeGenerator = new InputTypeGenerator($inputTypeUtils, $fieldsBuilder, $this->inputTypeValidator); - foreach ($nsList as $ns) { + if ($this->namespaces) { $compositeTypeMapper->addTypeMapper(new GlobTypeMapper( - $ns, + $classFinder, $typeGenerator, $inputTypeGenerator, $inputTypeUtils, @@ -537,7 +525,7 @@ public function createSchema(): Schema $this->container, $annotationReader, $namespacedCache, - $finder, + $classFinder, $this->globTTL, ); } @@ -558,4 +546,50 @@ public function createSchema(): Schema return new Schema($aggregateQueryProvider, $recursiveTypeMapper, $typeResolver, $topRootTypeMapper, $this->schemaConfig); } + + private function createClassFinder(CacheInterface $cache): ClassFinder + { + // When no namespaces are specified, class finder uses all available namespaces to discover classes. + // While this is technically okay, it doesn't follow SchemaFactory's semantics that allow it's + // users to manually specify classes (see SchemaFactory::testCreateSchemaOnlyWithFactories()), + // without having to specify namespaces to glob. This solves it by providing an empty iterator. + if (!$this->namespaces) { + return new EmptyClassFinder(); + } + + $finder = (clone ($this->finder ?? new ComposerFinder())); + + foreach ($this->namespaces as $namespace) { + $finder->inNamespace($namespace); + } + + return new OldCachedClassFinder( + new KcsClassFinder($finder), + $cache, + $this->globTTL + ); + +// if ($this->devMode) { +// $finder = new FileCachedFinder($finder); +// } + + return $finder; + } + + private function createDocBlockFactory(ClassBoundCacheInterface $nonInheritedClassBoundCache): array + { + $docBlockContextFactory = new CachedDocBlockContextFactory( + new ClassBoundCacheContract(new ClassBoundMemoryAdapter($nonInheritedClassBoundCache)), + new PhpDocumentorDocBlockContextFactory(new ContextFactory()) + ); + $docBlockFactory = new CachedDocBlockFactory( + new ClassBoundCacheContract(new ClassBoundMemoryAdapter($nonInheritedClassBoundCache)), + new PhpDocumentorDocBlockFactory( + DocBlockFactory::createInstance(), + $docBlockContextFactory, + ), + ); + + return [$docBlockFactory, $docBlockContextFactory]; + } } diff --git a/src/Utils/Namespaces/NamespaceFactory.php b/src/Utils/Namespaces/NamespaceFactory.php deleted file mode 100644 index 719c80bb22..0000000000 --- a/src/Utils/Namespaces/NamespaceFactory.php +++ /dev/null @@ -1,29 +0,0 @@ -cache, clone $this->finder, $this->globTTL); - } -} diff --git a/tests/AbstractQueryProviderTest.php b/tests/AbstractQueryProviderTest.php index a0a7f124a3..6c5d7218b7 100644 --- a/tests/AbstractQueryProviderTest.php +++ b/tests/AbstractQueryProviderTest.php @@ -24,6 +24,10 @@ use TheCodingMachine\GraphQLite\Containers\BasicAutoWiringContainer; use TheCodingMachine\GraphQLite\Containers\EmptyContainer; use TheCodingMachine\GraphQLite\Containers\LazyContainer; +use TheCodingMachine\GraphQLite\Discovery\ClassFinder; +use TheCodingMachine\GraphQLite\Discovery\EmptyClassFinder; +use TheCodingMachine\GraphQLite\Discovery\KcsClassFinder; +use TheCodingMachine\GraphQLite\Discovery\OldCachedClassFinder; use TheCodingMachine\GraphQLite\Fixtures\Mocks\MockResolvableInputObjectType; use TheCodingMachine\GraphQLite\Fixtures\TestObject; use TheCodingMachine\GraphQLite\Fixtures\TestObject2; @@ -64,6 +68,7 @@ use TheCodingMachine\GraphQLite\Types\ResolvableMutableInputInterface; use TheCodingMachine\GraphQLite\Types\TypeResolver; use TheCodingMachine\GraphQLite\Utils\Namespaces\NamespaceFactory; +use Traversable; abstract class AbstractQueryProviderTest extends TestCase { @@ -397,14 +402,14 @@ protected function buildRootTypeMapper(): RootTypeMapperInterface $rootTypeMapper, $this->getAnnotationReader(), $arrayAdapter, - [] + new EmptyClassFinder(), ); $rootTypeMapper = new EnumTypeMapper( $rootTypeMapper, $this->getAnnotationReader(), $arrayAdapter, - [] + new EmptyClassFinder(), ); $rootTypeMapper = new CompoundTypeMapper( @@ -495,15 +500,14 @@ protected function resolveType(string $type): \phpDocumentor\Reflection\Type return (new PhpDocumentorTypeResolver())->resolve($type); } - protected function getNamespaceFactory(): NamespaceFactory + protected function getClassFinder(array|string $namespaces): ClassFinder { - if ($this->namespaceFactory === null) { - $arrayAdapter = new ArrayAdapter(); - $arrayAdapter->setLogger(new ExceptionLogger()); - $psr16Cache = new Psr16Cache($arrayAdapter); + $finder = new ComposerFinder(); - $this->namespaceFactory = new NamespaceFactory($psr16Cache, new ComposerFinder()); + foreach ((array) $namespaces as $namespace) { + $finder->inNamespace($namespace); } - return $this->namespaceFactory; + + return new KcsClassFinder($finder); } } diff --git a/tests/GlobControllerQueryProviderTest.php b/tests/GlobControllerQueryProviderTest.php index dedc7d36ab..201b9c7ff4 100644 --- a/tests/GlobControllerQueryProviderTest.php +++ b/tests/GlobControllerQueryProviderTest.php @@ -9,6 +9,7 @@ use ReflectionClass; use Symfony\Component\Cache\Adapter\NullAdapter; use Symfony\Component\Cache\Psr16Cache; +use TheCodingMachine\GraphQLite\Discovery\KcsClassFinder; use TheCodingMachine\GraphQLite\Fixtures\TestController; class GlobControllerQueryProviderTest extends AbstractQueryProviderTest @@ -38,6 +39,7 @@ public function has($id): bool }; $finder = new ComposerFinder(); + $finder->inNamespace('TheCodingMachine\\GraphQLite\\Fixtures'); $finder->filter(static fn (ReflectionClass $class) => $class->getNamespaceName() === 'TheCodingMachine\\GraphQLite\\Fixtures'); // Fix for recursive:false $globControllerQueryProvider = new GlobControllerQueryProvider( 'TheCodingMachine\\GraphQLite\\Fixtures', @@ -45,7 +47,7 @@ public function has($id): bool $container, $this->getAnnotationReader(), new Psr16Cache(new NullAdapter()), - $finder, + new KcsClassFinder($finder), 0, ); diff --git a/tests/Integration/IntegrationTestCase.php b/tests/Integration/IntegrationTestCase.php index 16e90ceb91..436dbc1d9f 100644 --- a/tests/Integration/IntegrationTestCase.php +++ b/tests/Integration/IntegrationTestCase.php @@ -6,7 +6,6 @@ use GraphQL\Error\DebugFlag; use GraphQL\Executor\ExecutionResult; use Kcs\ClassFinder\Finder\ComposerFinder; -use Kcs\ClassFinder\Finder\FinderInterface; use phpDocumentor\Reflection\Types\ContextFactory; use PHPUnit\Framework\TestCase; use Psr\Container\ContainerInterface; @@ -23,6 +22,9 @@ use TheCodingMachine\GraphQLite\Containers\BasicAutoWiringContainer; use TheCodingMachine\GraphQLite\Containers\EmptyContainer; use TheCodingMachine\GraphQLite\Containers\LazyContainer; +use TheCodingMachine\GraphQLite\Discovery\ClassFinder; +use TheCodingMachine\GraphQLite\Discovery\KcsClassFinder; +use TheCodingMachine\GraphQLite\Discovery\OldCachedClassFinder; use TheCodingMachine\GraphQLite\FieldsBuilder; use TheCodingMachine\GraphQLite\GlobControllerQueryProvider; use TheCodingMachine\GraphQLite\InputTypeGenerator; @@ -79,7 +81,6 @@ use TheCodingMachine\GraphQLite\TypeRegistry; use TheCodingMachine\GraphQLite\Types\ArgumentResolver; use TheCodingMachine\GraphQLite\Types\TypeResolver; -use TheCodingMachine\GraphQLite\Utils\Namespaces\NamespaceFactory; class IntegrationTestCase extends TestCase { @@ -97,7 +98,17 @@ public function createContainer(array $overloadedServices = []): ContainerInterf Schema::class => static function (ContainerInterface $container) { return new Schema($container->get(QueryProviderInterface::class), $container->get(RecursiveTypeMapperInterface::class), $container->get(TypeResolver::class), $container->get(RootTypeMapperInterface::class)); }, - FinderInterface::class => fn () => new ComposerFinder(), + ClassFinder::class => function () { + $composerFinder = new ComposerFinder(); + $composerFinder->inNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration\\Types'); + $composerFinder->inNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration\\Models'); + $composerFinder->inNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration\\Controllers'); + + return new OldCachedClassFinder( + new KcsClassFinder($composerFinder), + new Psr16Cache(new ArrayAdapter()), + ); + }, QueryProviderInterface::class => static function (ContainerInterface $container) { $queryProvider = new GlobControllerQueryProvider( 'TheCodingMachine\\GraphQLite\\Fixtures\\Integration\\Controllers', @@ -105,7 +116,7 @@ public function createContainer(array $overloadedServices = []): ContainerInterf $container->get(BasicAutoWiringContainer::class), $container->get(AnnotationReader::class), new Psr16Cache(new ArrayAdapter()), - $container->get(FinderInterface::class), + $container->get(ClassFinder::class), ); $queryProvider = new AggregateQueryProvider([ @@ -116,7 +127,7 @@ public function createContainer(array $overloadedServices = []): ContainerInterf $container->get(BasicAutoWiringContainer::class), $container->get(AnnotationReader::class), new Psr16Cache(new ArrayAdapter()), - $container->get(FinderInterface::class), + $container->get(ClassFinder::class), ), ]); @@ -212,35 +223,11 @@ public function createContainer(array $overloadedServices = []): ContainerInterf TypeMapperInterface::class => static function (ContainerInterface $container) { return new CompositeTypeMapper(); }, - NamespaceFactory::class => static function (ContainerInterface $container) { - $arrayAdapter = new ArrayAdapter(); - $arrayAdapter->setLogger(new ExceptionLogger()); - return new NamespaceFactory( - new Psr16Cache($arrayAdapter), - $container->get(FinderInterface::class), - ); - }, GlobTypeMapper::class => static function (ContainerInterface $container) { $arrayAdapter = new ArrayAdapter(); $arrayAdapter->setLogger(new ExceptionLogger()); return new GlobTypeMapper( - $container->get(NamespaceFactory::class)->createNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration\\Types'), - $container->get(TypeGenerator::class), - $container->get(InputTypeGenerator::class), - $container->get(InputTypeUtils::class), - $container->get(BasicAutoWiringContainer::class), - $container->get(AnnotationReader::class), - $container->get(NamingStrategyInterface::class), - $container->get(RecursiveTypeMapperInterface::class), - new Psr16Cache($arrayAdapter), - ); - }, - // We use a second type mapper here so we can target the Models dir - GlobTypeMapper::class . '2' => static function (ContainerInterface $container) { - $arrayAdapter = new ArrayAdapter(); - $arrayAdapter->setLogger(new ExceptionLogger()); - return new GlobTypeMapper( - $container->get(NamespaceFactory::class)->createNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration\\Models'), + $container->get(ClassFinder::class), $container->get(TypeGenerator::class), $container->get(InputTypeGenerator::class), $container->get(InputTypeUtils::class), @@ -259,10 +246,7 @@ public function createContainer(array $overloadedServices = []): ContainerInterf $container->get(RootTypeMapperInterface::class), $container->get(AnnotationReader::class), new ArrayAdapter(), - [ - $container->get(NamespaceFactory::class) - ->createNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration\\Models'), - ], + $container->get(ClassFinder::class), ); }, TypeGenerator::class => static function (ContainerInterface $container) { @@ -341,8 +325,8 @@ public function createContainer(array $overloadedServices = []): ContainerInterf // These are in reverse order of execution $errorRootTypeMapper = new FinalRootTypeMapper($container->get(RecursiveTypeMapperInterface::class)); $rootTypeMapper = new BaseTypeMapper($errorRootTypeMapper, $container->get(RecursiveTypeMapperInterface::class), $container->get(RootTypeMapperInterface::class)); - $rootTypeMapper = new MyCLabsEnumTypeMapper($rootTypeMapper, $container->get(AnnotationReader::class), new ArrayAdapter(), [ $container->get(NamespaceFactory::class)->createNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration\\Models') ]); - $rootTypeMapper = new EnumTypeMapper($rootTypeMapper, $container->get(AnnotationReader::class), new ArrayAdapter(), [ $container->get(NamespaceFactory::class)->createNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration\\Models') ]); + $rootTypeMapper = new MyCLabsEnumTypeMapper($rootTypeMapper, $container->get(AnnotationReader::class), new ArrayAdapter(), $container->get(ClassFinder::class)); + $rootTypeMapper = new EnumTypeMapper($rootTypeMapper, $container->get(AnnotationReader::class), new ArrayAdapter(), $container->get(ClassFinder::class)); $rootTypeMapper = new CompoundTypeMapper($rootTypeMapper, $container->get(RootTypeMapperInterface::class), $container->get(NamingStrategyInterface::class), $container->get(TypeRegistry::class), $container->get(RecursiveTypeMapperInterface::class)); $rootTypeMapper = new IteratorTypeMapper($rootTypeMapper, $container->get(RootTypeMapperInterface::class)); return $rootTypeMapper; @@ -373,7 +357,6 @@ public function createContainer(array $overloadedServices = []): ContainerInterf $container = new LazyContainer($overloadedServices + $services); $container->get(TypeResolver::class)->registerSchema($container->get(Schema::class)); $container->get(TypeMapperInterface::class)->addTypeMapper($container->get(GlobTypeMapper::class)); - $container->get(TypeMapperInterface::class)->addTypeMapper($container->get(GlobTypeMapper::class . '2')); $container->get(TypeMapperInterface::class)->addTypeMapper($container->get(PorpaginasTypeMapper::class)); $container->get('topRootTypeMapper')->setNext($container->get('rootTypeMapper')); diff --git a/tests/Mappers/GlobTypeMapperTest.php b/tests/Mappers/GlobTypeMapperTest.php index 937f71004e..beda4da1cc 100644 --- a/tests/Mappers/GlobTypeMapperTest.php +++ b/tests/Mappers/GlobTypeMapperTest.php @@ -43,7 +43,7 @@ public function testGlobTypeMapper(): void $cache = new Psr16Cache(new ArrayAdapter()); - $mapper = new GlobTypeMapper($this->getNamespaceFactory()->createNamespace('TheCodingMachine\GraphQLite\Fixtures\Types'), $typeGenerator, $inputTypeGenerator, $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), $cache); + $mapper = new GlobTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\Types'), $typeGenerator, $inputTypeGenerator, $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), $cache); $this->assertSame([TestObject::class], $mapper->getSupportedClasses()); $this->assertTrue($mapper->canMapClassToType(TestObject::class)); @@ -53,7 +53,7 @@ public function testGlobTypeMapper(): void $this->assertFalse($mapper->canMapNameToType('NotExists')); // Again to test cache - $anotherMapperSameCache = new GlobTypeMapper($this->getNamespaceFactory()->createNamespace('TheCodingMachine\GraphQLite\Fixtures\Types'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), $cache); + $anotherMapperSameCache = new GlobTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\Types'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), $cache); $this->assertTrue($anotherMapperSameCache->canMapClassToType(TestObject::class)); $this->assertTrue($anotherMapperSameCache->canMapNameToType('Foo')); @@ -71,7 +71,7 @@ public function testGlobTypeMapperDuplicateTypesException(): void $typeGenerator = $this->getTypeGenerator(); - $mapper = new GlobTypeMapper($this->getNamespaceFactory()->createNamespace('TheCodingMachine\GraphQLite\Fixtures\DuplicateTypes'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), new Psr16Cache(new NullAdapter())); + $mapper = new GlobTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\DuplicateTypes'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), new Psr16Cache(new NullAdapter())); $this->expectException(DuplicateMappingException::class); $mapper->canMapClassToType(TestType::class); @@ -87,7 +87,7 @@ public function testGlobTypeMapperDuplicateInputsException(): void $typeGenerator = $this->getTypeGenerator(); - $mapper = new GlobTypeMapper($this->getNamespaceFactory()->createNamespace('TheCodingMachine\GraphQLite\Fixtures\DuplicateInputs'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), new Psr16Cache(new NullAdapter())); + $mapper = new GlobTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\DuplicateInputs'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), new Psr16Cache(new NullAdapter())); $this->expectException(DuplicateMappingException::class); $mapper->canMapClassToInputType(TestInput::class); @@ -103,7 +103,7 @@ public function testGlobTypeMapperDuplicateInputTypesException(): void $typeGenerator = $this->getTypeGenerator(); - $mapper = new GlobTypeMapper($this->getNamespaceFactory()->createNamespace('TheCodingMachine\GraphQLite\Fixtures\DuplicateInputTypes'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), new Psr16Cache(new NullAdapter())); + $mapper = new GlobTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\DuplicateInputTypes'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), new Psr16Cache(new NullAdapter())); $caught = false; try { @@ -130,7 +130,7 @@ public function testGlobTypeMapperInheritedInputTypesException(): void $typeGenerator = $this->getTypeGenerator(); - $mapper = new GlobTypeMapper($this->getNamespaceFactory()->createNamespace('TheCodingMachine\GraphQLite\Fixtures\InheritedInputTypes'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), new Psr16Cache(new NullAdapter())); + $mapper = new GlobTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\InheritedInputTypes'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), new Psr16Cache(new NullAdapter())); //$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\''); @@ -148,7 +148,7 @@ public function testGlobTypeMapperClassNotFoundException(): void $typeGenerator = $this->getTypeGenerator(); - $mapper = new GlobTypeMapper($this->getNamespaceFactory()->createNamespace('TheCodingMachine\GraphQLite\Fixtures\BadClassType'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), new Psr16Cache(new NullAdapter())); + $mapper = new GlobTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\BadClassType'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), new Psr16Cache(new NullAdapter())); $this->expectException(ClassNotFoundException::class); $this->expectExceptionMessage("Could not autoload class 'Foobar' defined in @Type annotation of class 'TheCodingMachine\\GraphQLite\\Fixtures\\BadClassType\\TestType'"); @@ -165,7 +165,7 @@ public function testGlobTypeMapperNameNotFoundException(): void $typeGenerator = $this->getTypeGenerator(); - $mapper = new GlobTypeMapper($this->getNamespaceFactory()->createNamespace('TheCodingMachine\GraphQLite\Fixtures\Types'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), new Psr16Cache(new NullAdapter())); + $mapper = new GlobTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\Types'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), new Psr16Cache(new NullAdapter())); $this->expectException(CannotMapTypeException::class); $mapper->mapNameToType('NotExists', $this->getTypeMapper()); @@ -186,7 +186,7 @@ public function testGlobTypeMapperInputType(): void $cache = new Psr16Cache(new ArrayAdapter()); - $mapper = new GlobTypeMapper($this->getNamespaceFactory()->createNamespace('TheCodingMachine\GraphQLite\Fixtures\Types'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), $cache); + $mapper = new GlobTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\Types'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), $cache); $this->assertTrue($mapper->canMapClassToInputType(TestObject::class)); @@ -195,7 +195,7 @@ public function testGlobTypeMapperInputType(): void $this->assertSame('TestObjectInput', $inputType->name); // Again to test cache - $anotherMapperSameCache = new GlobTypeMapper($this->getNamespaceFactory()->createNamespace('TheCodingMachine\GraphQLite\Fixtures\Types'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), $cache); + $anotherMapperSameCache = new GlobTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\Types'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), $cache); $this->assertTrue($anotherMapperSameCache->canMapClassToInputType(TestObject::class)); $this->assertSame('TestObjectInput', $anotherMapperSameCache->mapClassToInputType(TestObject::class, $this->getTypeMapper())->name); @@ -221,7 +221,7 @@ public function testGlobTypeMapperExtend(): void $cache = new Psr16Cache(new ArrayAdapter()); - $mapper = new GlobTypeMapper($this->getNamespaceFactory()->createNamespace('TheCodingMachine\GraphQLite\Fixtures\Types'), $typeGenerator, $inputTypeGenerator, $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), $cache); + $mapper = new GlobTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\Types'), $typeGenerator, $inputTypeGenerator, $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), $cache); $type = $mapper->mapClassToType(TestObject::class, null); @@ -232,7 +232,7 @@ public function testGlobTypeMapperExtend(): void $this->assertFalse($mapper->canExtendTypeForName('NotExists', $type)); // Again to test cache - $anotherMapperSameCache = new GlobTypeMapper($this->getNamespaceFactory()->createNamespace('TheCodingMachine\GraphQLite\Fixtures\Types'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), $cache); + $anotherMapperSameCache = new GlobTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\Types'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), $cache); $this->assertTrue($anotherMapperSameCache->canExtendTypeForClass(TestObject::class, $type)); $this->assertTrue($anotherMapperSameCache->canExtendTypeForName('TestObject', $type)); @@ -249,7 +249,7 @@ public function testEmptyGlobTypeMapper(): void $cache = new Psr16Cache(new ArrayAdapter()); - $mapper = new GlobTypeMapper($this->getNamespaceFactory()->createNamespace('TheCodingMachine\GraphQLite\Fixtures\Integration\Controllers'), $typeGenerator, $inputTypeGenerator, $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), $cache); + $mapper = new GlobTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\Integration\Controllers'), $typeGenerator, $inputTypeGenerator, $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), $cache); $this->assertSame([], $mapper->getSupportedClasses()); } @@ -267,7 +267,7 @@ public function testGlobTypeMapperDecorate(): void $cache = new Psr16Cache(new ArrayAdapter()); - $mapper = new GlobTypeMapper($this->getNamespaceFactory()->createNamespace('TheCodingMachine\GraphQLite\Fixtures\Integration\Types'), $typeGenerator, $inputTypeGenerator, $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), $cache); + $mapper = new GlobTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\Integration\Types'), $typeGenerator, $inputTypeGenerator, $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), $cache); $inputType = new MockResolvableInputObjectType(['name'=>'FilterInput']); @@ -290,7 +290,7 @@ public function testInvalidName(): void $typeGenerator = $this->getTypeGenerator(); - $mapper = new GlobTypeMapper($this->getNamespaceFactory()->createNamespace('TheCodingMachine\GraphQLite\Fixtures\Types'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), new Psr16Cache(new ArrayAdapter())); + $mapper = new GlobTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\Types'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), new Psr16Cache(new ArrayAdapter())); $this->assertFalse($mapper->canExtendTypeForName('{}()/\\@:', new MutableObjectType(['name' => 'foo']))); $this->assertFalse($mapper->canDecorateInputTypeForName('{}()/\\@:', new MockResolvableInputObjectType(['name' => 'foo']))); @@ -316,7 +316,7 @@ public function testGlobTypeMapperExtendBadName(): void $cache = new Psr16Cache(new ArrayAdapter()); - $mapper = new GlobTypeMapper($this->getNamespaceFactory()->createNamespace('TheCodingMachine\GraphQLite\Fixtures\BadExtendType'), $typeGenerator, $inputTypeGenerator, $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), $cache); + $mapper = new GlobTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\BadExtendType'), $typeGenerator, $inputTypeGenerator, $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), $cache); $testObjectType = new MutableObjectType([ 'name' => 'TestObject', @@ -349,7 +349,7 @@ public function testGlobTypeMapperExtendBadClass(): void $cache = new Psr16Cache(new ArrayAdapter()); - $mapper = new GlobTypeMapper($this->getNamespaceFactory()->createNamespace('TheCodingMachine\GraphQLite\Fixtures\BadExtendType2'), $typeGenerator, $inputTypeGenerator, $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), $cache); + $mapper = new GlobTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\BadExtendType2'), $typeGenerator, $inputTypeGenerator, $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), $cache); $testObjectType = new MutableObjectType([ 'name' => 'TestObject', @@ -376,7 +376,7 @@ public function testNonInstantiableType(): void $cache = new Psr16Cache(new ArrayAdapter()); - $mapper = new GlobTypeMapper($this->getNamespaceFactory()->createNamespace('TheCodingMachine\GraphQLite\Fixtures\NonInstantiableType'), $typeGenerator, $inputTypeGenerator, $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), $cache); + $mapper = new GlobTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\NonInstantiableType'), $typeGenerator, $inputTypeGenerator, $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), $cache); $this->expectException(GraphQLRuntimeException::class); $this->expectExceptionMessage('Class "TheCodingMachine\GraphQLite\Fixtures\NonInstantiableType\AbstractFooType" annotated with @Type(class="TheCodingMachine\GraphQLite\Fixtures\TestObject") must be instantiable.'); @@ -391,7 +391,7 @@ public function testNonInstantiableInput(): void $inputTypeGenerator = $this->getInputTypeGenerator(); $cache = new Psr16Cache(new ArrayAdapter()); - $mapper = new GlobTypeMapper($this->getNamespaceFactory()->createNamespace('TheCodingMachine\GraphQLite\Fixtures\NonInstantiableInput'), $typeGenerator, $inputTypeGenerator, $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), $cache); + $mapper = new GlobTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\NonInstantiableInput'), $typeGenerator, $inputTypeGenerator, $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), $cache); $this->expectException(FailedResolvingInputType::class); $this->expectExceptionMessage("Class 'TheCodingMachine\GraphQLite\Fixtures\NonInstantiableInput\AbstractFoo' annotated with @Input must be instantiable."); diff --git a/tests/Mappers/RecursiveTypeMapperTest.php b/tests/Mappers/RecursiveTypeMapperTest.php index 7cba67ae9d..57979da862 100644 --- a/tests/Mappers/RecursiveTypeMapperTest.php +++ b/tests/Mappers/RecursiveTypeMapperTest.php @@ -152,7 +152,7 @@ protected function getTypeMapper() $typeGenerator = new TypeGenerator($this->getAnnotationReader(), $namingStrategy, $this->getTypeRegistry(), $this->getRegistry(), $this->typeMapper, $this->getFieldsBuilder()); - $mapper = new GlobTypeMapper($this->getNamespaceFactory()->createNamespace('TheCodingMachine\GraphQLite\Fixtures\Interfaces\Types'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), $namingStrategy, $this->typeMapper, new Psr16Cache(new NullAdapter())); + $mapper = new GlobTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\Interfaces\Types'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), $namingStrategy, $this->typeMapper, new Psr16Cache(new NullAdapter())); $compositeMapper->addTypeMapper($mapper); } return $this->typeMapper; @@ -257,7 +257,7 @@ public function testMapNameToTypeDecorators(): void $cache = new Psr16Cache(new ArrayAdapter()); - $mapper = new GlobTypeMapper($this->getNamespaceFactory()->createNamespace('TheCodingMachine\GraphQLite\Fixtures\Integration'), $typeGenerator, $inputTypeGenerator, $this->getInputTypeUtils(), $this->getRegistry(), new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), $cache); + $mapper = new GlobTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\Integration'), $typeGenerator, $inputTypeGenerator, $this->getInputTypeUtils(), $this->getRegistry(), new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getTypeMapper(), $cache); $recursiveTypeMapper = new RecursiveTypeMapper( $mapper, diff --git a/tests/RootTypeMapperFactoryContextTest.php b/tests/RootTypeMapperFactoryContextTest.php index e33665c307..ad6e4dff82 100644 --- a/tests/RootTypeMapperFactoryContextTest.php +++ b/tests/RootTypeMapperFactoryContextTest.php @@ -19,7 +19,7 @@ public function testContext(): void $namingStrategy = new NamingStrategy(); $container = new EmptyContainer(); $arrayCache = new Psr16Cache(new ArrayAdapter()); - $nsList = [$this->getNamespaceFactory()->createNamespace('namespace')]; + $classFinder = $this->getClassFinder('namespace'); $context = new RootTypeMapperFactoryContext( $this->getAnnotationReader(), @@ -29,7 +29,7 @@ public function testContext(): void $this->getTypeMapper(), $container, $arrayCache, - $nsList, + $classFinder, self::GLOB_TTL_SECONDS ); @@ -40,8 +40,7 @@ public function testContext(): void $this->assertSame($this->getTypeMapper(), $context->getRecursiveTypeMapper()); $this->assertSame($container, $context->getContainer()); $this->assertSame($arrayCache, $context->getCache()); - $this->assertSame($nsList, $context->getTypeNamespaces()); - $this->assertContainsOnlyInstancesOf(NS::class, $context->getTypeNamespaces()); + $this->assertSame($classFinder, $context->getClassFinder()); $this->assertSame(self::GLOB_TTL_SECONDS, $context->getGlobTTL()); $this->assertNull($context->getMapTTL()); } From 173a63461c1ce3a32970091e4eb9ec049c494231 Mon Sep 17 00:00:00 2001 From: Oleksandr Prypkhan Date: Mon, 8 Apr 2024 22:55:18 +0300 Subject: [PATCH 05/28] Make caches only recompute on file changes --- src/Discovery/Cache/ClassFinderBoundCache.php | 25 ++ .../FileModificationClassFinderBoundCache.php | 140 +++++++++++ .../Cache/HardClassFinderBoundCache.php | 67 ++++++ src/Discovery/ClassFinder.php | 2 +- src/Discovery/EmptyClassFinder.php | 13 -- src/Discovery/KcsClassFinder.php | 10 +- src/Discovery/OldCachedClassFinder.php | 73 ------ src/Discovery/StaticClassFinder.php | 41 ++++ src/FactoryContext.php | 14 +- src/GlobControllerQueryProvider.php | 74 ++---- ...peMapper.php => ClassFinderTypeMapper.php} | 218 ++++++------------ src/Mappers/GlobAnnotationsCache.php | 5 + src/Mappers/GlobExtendAnnotationsCache.php | 4 + src/Mappers/GlobExtendTypeMapperCache.php | 6 +- src/Mappers/GlobTypeMapper.php | 67 ------ src/Mappers/GlobTypeMapperCache.php | 12 +- src/Mappers/Root/EnumTypeMapper.php | 35 ++- src/Mappers/Root/MyCLabsEnumTypeMapper.php | 28 ++- .../Root/RootTypeMapperFactoryContext.php | 13 +- src/Mappers/StaticClassListTypeMapper.php | 90 -------- .../StaticClassListTypeMapperFactory.php | 11 +- src/SchemaFactory.php | 77 +++---- tests/AbstractQueryProvider.php | 11 +- tests/GlobControllerQueryProviderTest.php | 5 +- tests/Integration/IntegrationTestCase.php | 29 +-- tests/Utils/NsTest.php | 145 ------------ 26 files changed, 499 insertions(+), 716 deletions(-) create mode 100644 src/Discovery/Cache/ClassFinderBoundCache.php create mode 100644 src/Discovery/Cache/FileModificationClassFinderBoundCache.php create mode 100644 src/Discovery/Cache/HardClassFinderBoundCache.php delete mode 100644 src/Discovery/EmptyClassFinder.php delete mode 100644 src/Discovery/OldCachedClassFinder.php create mode 100644 src/Discovery/StaticClassFinder.php rename src/Mappers/{AbstractTypeMapper.php => ClassFinderTypeMapper.php} (65%) delete mode 100644 src/Mappers/GlobTypeMapper.php delete mode 100644 src/Mappers/StaticClassListTypeMapper.php delete mode 100644 tests/Utils/NsTest.php diff --git a/src/Discovery/Cache/ClassFinderBoundCache.php b/src/Discovery/Cache/ClassFinderBoundCache.php new file mode 100644 index 0000000000..f53222dbac --- /dev/null +++ b/src/Discovery/Cache/ClassFinderBoundCache.php @@ -0,0 +1,25 @@ +): TEntry $map + * @param callable(array): TReturn $reduce + * + * @return TReturn + */ + public function reduce( + ClassFinder $classFinder, + string $key, + callable $map, + callable $reduce, + ): mixed; +} \ No newline at end of file diff --git a/src/Discovery/Cache/FileModificationClassFinderBoundCache.php b/src/Discovery/Cache/FileModificationClassFinderBoundCache.php new file mode 100644 index 0000000000..d5f48ff12b --- /dev/null +++ b/src/Discovery/Cache/FileModificationClassFinderBoundCache.php @@ -0,0 +1,140 @@ +): TEntry $map + * @param callable(array): TReturn $reduce + * + * @return TReturn + */ + public function reduce( + ClassFinder $classFinder, + string $key, + callable $map, + callable $reduce, + ): mixed + { + $entries = $this->entries($classFinder, "$key.entries", $map); + + return $reduce($entries); + } + + /** + * @template TEntry of mixed + * + * @param callable(ReflectionClass): TEntry $map + * + * @return array + */ + private function entries( + ClassFinder $classFinder, + string $key, + callable $map, + ): mixed + { + $previousEntries = $this->cache->get($key) ?? []; + $entries = []; + $result = []; + + $classFinder = $classFinder->withPathFilter(function (string $filename) use (&$entries, $previousEntries) { + $entry = $previousEntries[$filename] ?? null; + + // If there's no entry in cache for this filename (new file or previously uncached), + // or if it the file has been modified since caching, we'll try to autoload + // the class and collect the cached information (again). + if (!$entry || $this->dependenciesChanged($entry['dependencies'])) { + return true; + } + + $entries[$filename] = $entry; + + return false; + }); + + foreach ($classFinder as $classReflection) { + $filename = $classReflection->getFileName(); + + $result[$filename] = $map($classReflection); + $entries[$filename] = [ + 'dependencies' => $this->fileDependencies($classReflection), + 'data' => $result[$filename], + ]; + } + + $this->cache->set($key, $entries); + + return $result; + } + + /** + * @return array + */ + private function fileDependencies(ReflectionClass $refClass): array + { + $filename = $refClass->getFileName(); + + if ($filename === false) { + return []; + } + + $files = [$filename => filemtime($filename)]; + + if ($refClass->getParentClass() !== false) { + $files = array_merge($files, $this->fileDependencies($refClass->getParentClass())); + } + + foreach ($refClass->getTraits() as $trait) { + $files = array_merge($files, $this->fileDependencies($trait)); + } + + foreach ($refClass->getInterfaces() as $interface) { + $files = array_merge($files, $this->fileDependencies($interface)); + } + + return $files; + } + + private function dependenciesChanged(array $files): bool + { + foreach ($files as $filename => $modificationTime) { + if ($modificationTime !== filemtime($filename)) { + return true; + } + } + + return false; + } +} \ No newline at end of file diff --git a/src/Discovery/Cache/HardClassFinderBoundCache.php b/src/Discovery/Cache/HardClassFinderBoundCache.php new file mode 100644 index 0000000000..109b2a8053 --- /dev/null +++ b/src/Discovery/Cache/HardClassFinderBoundCache.php @@ -0,0 +1,67 @@ +): TEntry $map + * @param callable(array): TReturn $reduce + * + * @return TReturn + */ + public function reduce( + ClassFinder $classFinder, + string $key, + callable $map, + callable $reduce, + ): mixed + { + $result = $this->cache->get($key); + + if ($result !== null) { + return $result; + } + + $result = $reduce($this->entries($classFinder, $map)); + + $this->cache->set($key, $result); + + return $result; + } + + /** + * @template TEntry of mixed + * + * @param callable(ReflectionClass): TEntry $map + * + * @return array + */ + private function entries( + ClassFinder $classFinder, + callable $map, + ): mixed + { + $entries = []; + + foreach ($classFinder as $classReflection) { + $entries[$classReflection->getFileName()] = $map($classReflection); + } + + return $entries; + } +} \ No newline at end of file diff --git a/src/Discovery/ClassFinder.php b/src/Discovery/ClassFinder.php index b4e352b538..90c855c72f 100644 --- a/src/Discovery/ClassFinder.php +++ b/src/Discovery/ClassFinder.php @@ -7,5 +7,5 @@ */ interface ClassFinder extends \IteratorAggregate { - + public function withPathFilter(callable $filter): self; } \ No newline at end of file diff --git a/src/Discovery/EmptyClassFinder.php b/src/Discovery/EmptyClassFinder.php deleted file mode 100644 index ebbc7389ba..0000000000 --- a/src/Discovery/EmptyClassFinder.php +++ /dev/null @@ -1,13 +0,0 @@ -finder = (clone $that->finder)->pathFilter($filter); + + return $that; + } + public function getIterator(): Traversable { return $this->finder->getIterator(); diff --git a/src/Discovery/OldCachedClassFinder.php b/src/Discovery/OldCachedClassFinder.php deleted file mode 100644 index 2e34c4fb33..0000000000 --- a/src/Discovery/OldCachedClassFinder.php +++ /dev/null @@ -1,73 +0,0 @@ -> - */ - private array|null $classes = null; - - public function __construct( - private readonly ClassFinder $finder, - private readonly CacheInterface $cache, - private readonly int|null $globTTL = null, - ) - { - } - - public function getIterator(): \Traversable - { - if ($this->classes === null) { - $cacheKey = 'GraphQLite_NS_'; - try { - $classes = $this->cache->get($cacheKey); - if ($classes !== null) { - foreach ($classes as $class) { - if ( - ! class_exists($class, false) && - ! interface_exists($class, false) && - ! trait_exists($class, false) - ) { - // assume the cache is invalid - throw new class extends Exception implements CacheException { - }; - } - - $this->classes[$class] = new ReflectionClass($class); - } - } - } catch (CacheException | InvalidArgumentException | ReflectionException) { - $this->classes = null; - } - - if ($this->classes === null) { - $this->classes = []; - /** @var class-string $className */ - /** @var \ReflectionClass $reflector */ - foreach ($this->finder as $className => $reflector) { - if (! ($reflector instanceof ReflectionClass)) { - continue; - } - - $this->classes[$className] = $reflector; - } - try { - $this->cache->set($cacheKey, array_keys($this->classes), $this->globTTL); - } catch (InvalidArgumentException) { - // @ignoreException - } - } - } - - return new \ArrayIterator($this->classes); - } -} \ No newline at end of file diff --git a/src/Discovery/StaticClassFinder.php b/src/Discovery/StaticClassFinder.php new file mode 100644 index 0000000000..94f01a02f4 --- /dev/null +++ b/src/Discovery/StaticClassFinder.php @@ -0,0 +1,41 @@ + $classes + */ + public function __construct( + private readonly array $classes, + ) + { + } + + public function withPathFilter(callable $filter): ClassFinder + { + $that = clone $this; + $that->pathFilter = $filter; + + return $that; + } + + public function getIterator(): Traversable + { + foreach ($this->classes as $class) { + $classReflection = new \ReflectionClass($class); + + if ($this->pathFilter && !($this->pathFilter)($classReflection->getFileName())) { + continue; + } + + yield $class => $classReflection; + } + } +} \ No newline at end of file diff --git a/src/FactoryContext.php b/src/FactoryContext.php index a59dafd668..c1306ee2f8 100644 --- a/src/FactoryContext.php +++ b/src/FactoryContext.php @@ -6,6 +6,8 @@ use Psr\Container\ContainerInterface; use Psr\SimpleCache\CacheInterface; +use TheCodingMachine\GraphQLite\Discovery\ClassFinder; +use TheCodingMachine\GraphQLite\Discovery\Cache\ClassFinderBoundCache; use TheCodingMachine\GraphQLite\Mappers\RecursiveTypeMapperInterface; use TheCodingMachine\GraphQLite\Types\InputTypeValidatorInterface; use TheCodingMachine\GraphQLite\Types\TypeResolver; @@ -29,8 +31,8 @@ public function __construct( private readonly ContainerInterface $container, private readonly CacheInterface $cache, private readonly InputTypeValidatorInterface|null $inputTypeValidator, - private readonly int|null $globTTL, - private readonly int|null $mapTTL = null, + private readonly ClassFinder $classFinder, + private readonly ClassFinderBoundCache $classFinderBoundCache, ) { } @@ -89,13 +91,13 @@ public function getInputTypeValidator(): InputTypeValidatorInterface|null return $this->inputTypeValidator; } - public function getGlobTTL(): int|null + public function getClassFinder(): ClassFinder { - return $this->globTTL; + return $this->classFinder; } - public function getMapTTL(): int|null + public function getClassFinderBoundCache(): ClassFinderBoundCache { - return $this->mapTTL; + return $this->classFinderBoundCache; } } diff --git a/src/GlobControllerQueryProvider.php b/src/GlobControllerQueryProvider.php index bb5d5cbd37..61a7b2f233 100644 --- a/src/GlobControllerQueryProvider.php +++ b/src/GlobControllerQueryProvider.php @@ -17,6 +17,7 @@ use TheCodingMachine\GraphQLite\Annotations\Subscription; use TheCodingMachine\GraphQLite\Discovery\ClassFinder; +use TheCodingMachine\GraphQLite\Discovery\Cache\ClassFinderBoundCache; use function class_exists; use function interface_exists; use function is_array; @@ -30,36 +31,27 @@ */ final class GlobControllerQueryProvider implements QueryProviderInterface { - /** @var array|null */ - private array|null $instancesList = null; + /** @var array */ + private array $classList; private AggregateControllerQueryProvider|null $aggregateControllerQueryProvider = null; - private CacheContractInterface $cacheContract; /** - * @param string $namespace The namespace that contains the GraphQL types (they must have a `@Type` annotation) * @param ContainerInterface $container The container we will fetch controllers from. */ public function __construct( - private readonly string $namespace, private readonly FieldsBuilder $fieldsBuilder, private readonly ContainerInterface $container, private readonly AnnotationReader $annotationReader, - private readonly CacheInterface $cache, private readonly ClassFinder $classFinder, - int|null $cacheTtl = null, + private readonly ClassFinderBoundCache $classFinderBoundCache, ) { - $this->cacheContract = new Psr16Adapter( - $this->cache, - str_replace(['\\', '{', '}', '(', ')', '/', '@', ':'], '_', $namespace), - $cacheTtl ?? 0, - ); } private function getAggregateControllerQueryProvider(): AggregateControllerQueryProvider { $this->aggregateControllerQueryProvider ??= new AggregateControllerQueryProvider( - $this->getInstancesList(), + $this->getClassList(), $this->fieldsBuilder, $this->container, ); @@ -70,46 +62,28 @@ private function getAggregateControllerQueryProvider(): AggregateControllerQuery /** * Returns an array of fully qualified class names. * - * @return array + * @return array */ - private function getInstancesList(): array - { - if ($this->instancesList === null) { - $this->instancesList = $this->cacheContract->get( - 'globQueryProvider', - fn () => $this->buildInstancesList(), - ); - - if (! is_array($this->instancesList)) { - throw new InvalidArgumentException('The instance list returned is not an array. There might be an issue with your PSR-16 cache implementation.'); - } - } - - return $this->instancesList; - } - - /** @return array */ - private function buildInstancesList(): array + private function getClassList(): array { - $instances = []; - foreach ($this->classFinder as $className => $refClass) { - if (! class_exists($className) && ! interface_exists($className)) { - continue; - } - if (! $refClass instanceof ReflectionClass || ! $refClass->isInstantiable()) { - continue; - } - if (! $this->hasOperations($refClass)) { - continue; - } - if (! $this->container->has($className)) { - continue; - } - - $instances[] = $className; - } + $this->classList ??= $this->classFinderBoundCache->reduce( + $this->classFinder, + 'globQueryProvider', + function (ReflectionClass $classReflection): ?string { + if ( + ! $classReflection->isInstantiable() || + ! $this->hasOperations($classReflection) || + ! $this->container->has($classReflection->getName()) + ) { + return null; + } + + return $classReflection->getName(); + }, + fn (array $entries) => array_values(array_filter($entries)), + ); - return $instances; + return $this->classList; } /** @param ReflectionClass $reflectionClass */ diff --git a/src/Mappers/AbstractTypeMapper.php b/src/Mappers/ClassFinderTypeMapper.php similarity index 65% rename from src/Mappers/AbstractTypeMapper.php rename to src/Mappers/ClassFinderTypeMapper.php index db3946d532..4c6642996a 100644 --- a/src/Mappers/AbstractTypeMapper.php +++ b/src/Mappers/ClassFinderTypeMapper.php @@ -9,18 +9,12 @@ use GraphQL\Type\Definition\OutputType; use GraphQL\Type\Definition\Type; use Psr\Container\ContainerInterface; -use Psr\SimpleCache\CacheInterface; use ReflectionClass; use ReflectionException; use ReflectionMethod; -use Symfony\Component\Cache\Adapter\Psr16Adapter; -use Symfony\Contracts\Cache\CacheInterface as CacheContractInterface; -use TheCodingMachine\CacheUtils\ClassBoundCache; -use TheCodingMachine\CacheUtils\ClassBoundCacheContract; -use TheCodingMachine\CacheUtils\ClassBoundCacheContractInterface; -use TheCodingMachine\CacheUtils\ClassBoundMemoryAdapter; -use TheCodingMachine\CacheUtils\FileBoundCache; use TheCodingMachine\GraphQLite\AnnotationReader; +use TheCodingMachine\GraphQLite\Discovery\ClassFinder; +use TheCodingMachine\GraphQLite\Discovery\Cache\ClassFinderBoundCache; use TheCodingMachine\GraphQLite\InputTypeGenerator; use TheCodingMachine\GraphQLite\InputTypeUtils; use TheCodingMachine\GraphQLite\NamingStrategyInterface; @@ -29,60 +23,28 @@ use TheCodingMachine\GraphQLite\Types\MutableInterfaceType; use TheCodingMachine\GraphQLite\Types\MutableObjectType; use TheCodingMachine\GraphQLite\Types\ResolvableMutableInputInterface; - use function assert; /** * Analyzes classes and uses the @Type annotation to find the types automatically. - * - * Assumes that the container contains a class whose identifier is the same as the class name. */ -abstract class AbstractTypeMapper implements TypeMapperInterface +class ClassFinderTypeMapper implements TypeMapperInterface { - /** - * Cache storing the GlobAnnotationsCache objects linked to a given ReflectionClass. - */ - private ClassBoundCacheContractInterface $mapClassToAnnotationsCache; - /** - * Cache storing the GlobAnnotationsCache objects linked to a given ReflectionClass. - */ - private ClassBoundCacheContractInterface $mapClassToExtendAnnotationsCache; - - private CacheContractInterface $cacheContract; private GlobTypeMapperCache|null $globTypeMapperCache = null; private GlobExtendTypeMapperCache|null $globExtendTypeMapperCache = null; - /** @var array> */ - private array $registeredInputs; public function __construct( - string $cachePrefix, - private readonly TypeGenerator $typeGenerator, - private readonly InputTypeGenerator $inputTypeGenerator, - private readonly InputTypeUtils $inputTypeUtils, - private readonly ContainerInterface $container, - private readonly AnnotationReader $annotationReader, - private readonly NamingStrategyInterface $namingStrategy, + private readonly ClassFinder $classFinder, + private readonly TypeGenerator $typeGenerator, + private readonly InputTypeGenerator $inputTypeGenerator, + private readonly InputTypeUtils $inputTypeUtils, + private readonly ContainerInterface $container, + private readonly AnnotationReader $annotationReader, + private readonly NamingStrategyInterface $namingStrategy, private readonly RecursiveTypeMapperInterface $recursiveTypeMapper, - private readonly CacheInterface $cache, - protected int|null $globTTL = 2, - private readonly int|null $mapTTL = null, + private readonly ClassFinderBoundCache $classFinderBoundCache, ) { - $this->cacheContract = new Psr16Adapter($this->cache, $cachePrefix, $this->globTTL ?? 0); - - $classToAnnotationsCache = new ClassBoundCache( - new FileBoundCache($this->cache, 'classToAnnotations_' . $cachePrefix), - ); - $this->mapClassToAnnotationsCache = new ClassBoundCacheContract( - new ClassBoundMemoryAdapter($classToAnnotationsCache), - ); - - $classToExtendedAnnotationsCache = new ClassBoundCache( - new FileBoundCache($this->cache, 'classToExtendAnnotations_' . $cachePrefix), - ); - $this->mapClassToExtendAnnotationsCache = new ClassBoundCacheContract( - new ClassBoundMemoryAdapter($classToExtendedAnnotationsCache), - ); } /** @@ -90,48 +52,17 @@ public function __construct( */ private function getMaps(): GlobTypeMapperCache { - if ($this->globTypeMapperCache === null) { - $this->globTypeMapperCache = $this->cacheContract->get('fullMapComputed', function () { - return $this->buildMap(); - }); - } - - return $this->globTypeMapperCache; - } - - private function getMapClassToExtendTypeArray(): GlobExtendTypeMapperCache - { - if ($this->globExtendTypeMapperCache === null) { - $this->globExtendTypeMapperCache = $this->cacheContract->get('fullExtendMapComputed', function () { - return $this->buildMapClassToExtendTypeArray(); - }); - } - - return $this->globExtendTypeMapperCache; - } - - /** - * Returns the array of globbed classes. - * Only instantiable classes are returned. - * - * @return array> Key: fully qualified class name - */ - abstract protected function getClassList(): array; - - private function buildMap(): GlobTypeMapperCache - { - $globTypeMapperCache = new GlobTypeMapperCache(); - - /** @var array,ReflectionClass> $classes */ - $classes = $this->getClassList(); + $this->globTypeMapperCache ??= $this->classFinderBoundCache->reduce( + $this->classFinder, + 'classToAnnotations', + function (ReflectionClass $refClass): ?GlobAnnotationsCache { + if ($refClass->isEnum()) { + return null; + } - foreach ($classes as $className => $refClass) { - // Enum's are not types - if ($refClass->isEnum()) { - continue; - } - $annotationsCache = $this->mapClassToAnnotationsCache->get($refClass, function () use ($refClass, $className) { - $annotationsCache = new GlobAnnotationsCache(); + $annotationsCache = new GlobAnnotationsCache( + $className = $refClass->getName(), + ); $containsAnnotations = false; @@ -145,11 +76,6 @@ private function buildMap(): GlobTypeMapperCache $inputs = $this->annotationReader->getInputAnnotations($refClass); foreach ($inputs as $input) { $inputName = $this->namingStrategy->getInputTypeName($className, $input); - if (isset($this->registeredInputs[$inputName])) { - throw DuplicateMappingException::createForTwoInputs($inputName, $this->registeredInputs[$inputName], $refClass->getName()); - } - - $this->registeredInputs[$inputName] = $refClass->getName(); $annotationsCache = $annotationsCache->registerInput($inputName, $className, $input); $containsAnnotations = true; } @@ -179,71 +105,73 @@ private function buildMap(): GlobTypeMapperCache $containsAnnotations = true; } - if (! $containsAnnotations) { - return 'nothing'; + return $containsAnnotations ? $annotationsCache : null; + }, + fn (array $entries) => array_reduce($entries, function (GlobTypeMapperCache $globTypeMapperCache, ?GlobAnnotationsCache $annotationsCache) { + if ($annotationsCache === null) { + return $globTypeMapperCache; } - return $annotationsCache; - }, '', $this->mapTTL); + $globTypeMapperCache->registerAnnotations($annotationsCache->sourceClass, $annotationsCache); - if ($annotationsCache === 'nothing') { - continue; - } - - $globTypeMapperCache->registerAnnotations($refClass, $annotationsCache); - } + return $globTypeMapperCache; + }, new GlobTypeMapperCache()) + ); - return $globTypeMapperCache; + return $this->globTypeMapperCache; } - private function buildMapClassToExtendTypeArray(): GlobExtendTypeMapperCache + private function getMapClassToExtendTypeArray(): GlobExtendTypeMapperCache { - $globExtendTypeMapperCache = new GlobExtendTypeMapperCache(); - - $classes = $this->getClassList(); - foreach ($classes as $refClass) { - // Enum's are not types - if ($refClass->isEnum()) { - continue; - } - $annotationsCache = $this->mapClassToExtendAnnotationsCache->get($refClass, function () use ($refClass) { + $this->globExtendTypeMapperCache ??= $this->classFinderBoundCache->reduce( + $this->classFinder, + 'classToExtendAnnotations', + function (ReflectionClass $refClass): ?GlobExtendAnnotationsCache { + // Enum's are not types + if ($refClass->isEnum()) { + return null; + } + $extendType = $this->annotationReader->getExtendTypeAnnotation($refClass); - if ($extendType !== null) { - $extendClassName = $extendType->getClass(); - if ($extendClassName !== null) { - try { - $targetType = $this->recursiveTypeMapper->mapClassToType($extendClassName, null); - } catch (CannotMapTypeException $e) { - $e->addExtendTypeInfo($refClass, $extendType); - throw $e; - } - $typeName = $targetType->name; - } else { - $typeName = $extendType->getName(); - assert($typeName !== null); - $targetType = $this->recursiveTypeMapper->mapNameToType($typeName); - if (! $targetType instanceof MutableObjectType) { - throw CannotMapTypeException::extendTypeWithBadTargetedClass($refClass->getName(), $extendType); - } - $extendClassName = $targetType->getMappedClassName(); - } + if ($extendType === null) { + return null; + } - // FIXME: $extendClassName === NULL!!!!!! - return new GlobExtendAnnotationsCache($extendClassName, $typeName); + $extendClassName = $extendType->getClass(); + if ($extendClassName !== null) { + try { + $targetType = $this->recursiveTypeMapper->mapClassToType($extendClassName, null); + } catch (CannotMapTypeException $e) { + $e->addExtendTypeInfo($refClass, $extendType); + throw $e; + } + $typeName = $targetType->name; + } else { + $typeName = $extendType->getName(); + assert($typeName !== null); + $targetType = $this->recursiveTypeMapper->mapNameToType($typeName); + if (! $targetType instanceof MutableObjectType) { + throw CannotMapTypeException::extendTypeWithBadTargetedClass($refClass->getName(), $extendType); + } + $extendClassName = $targetType->getMappedClassName(); } - return 'nothing'; - }, '', $this->mapTTL); + // FIXME: $extendClassName === NULL!!!!!! + return new GlobExtendAnnotationsCache($refClass->getName(), $extendClassName, $typeName); + }, + fn (array $entries) => array_reduce($entries, function (GlobExtendTypeMapperCache $globExtendTypeMapperCache, ?GlobExtendAnnotationsCache $annotationsCache) { + if ($annotationsCache === null) { + return $globExtendTypeMapperCache; + } - if ($annotationsCache === 'nothing') { - continue; - } + $globExtendTypeMapperCache->registerAnnotations($annotationsCache->sourceClass, $annotationsCache); - $globExtendTypeMapperCache->registerAnnotations($refClass, $annotationsCache); - } + return $globExtendTypeMapperCache; + }, new GlobExtendTypeMapperCache()) + ); - return $globExtendTypeMapperCache; + return $this->globExtendTypeMapperCache; } /** diff --git a/src/Mappers/GlobAnnotationsCache.php b/src/Mappers/GlobAnnotationsCache.php index dd41c665e4..6f85251aa4 100644 --- a/src/Mappers/GlobAnnotationsCache.php +++ b/src/Mappers/GlobAnnotationsCache.php @@ -27,6 +27,7 @@ final class GlobAnnotationsCache * An array mapping an input type name to an input name / declaring class */ public function __construct( + public readonly string $sourceClass, private readonly string|null $typeClassName = null, private readonly string|null $typeName = null, private readonly bool $default = false, @@ -108,6 +109,10 @@ public function getDecorators(): array */ public function registerInput(string $name, string $className, Input $input): self { + if (isset($this->inputs[$name])) { + throw DuplicateMappingException::createForTwoInputs($name, $this->inputs[$name][0], $className); + } + return $this->with( inputs: [ ...$this->inputs, diff --git a/src/Mappers/GlobExtendAnnotationsCache.php b/src/Mappers/GlobExtendAnnotationsCache.php index b99d1acd28..b2063049f0 100644 --- a/src/Mappers/GlobExtendAnnotationsCache.php +++ b/src/Mappers/GlobExtendAnnotationsCache.php @@ -11,7 +11,11 @@ */ final class GlobExtendAnnotationsCache { + /** + * @param class-string $sourceClass + */ public function __construct( + public readonly string $sourceClass, private string|null $extendTypeClassName, private string $extendTypeName, ) diff --git a/src/Mappers/GlobExtendTypeMapperCache.php b/src/Mappers/GlobExtendTypeMapperCache.php index 590fe49220..046f5444d5 100644 --- a/src/Mappers/GlobExtendTypeMapperCache.php +++ b/src/Mappers/GlobExtendTypeMapperCache.php @@ -19,11 +19,11 @@ class GlobExtendTypeMapperCache /** * Merges annotations of a given class in the global cache. * - * @param ReflectionClass $refClass + * @param ReflectionClass|class-string $sourceClass */ - public function registerAnnotations(ReflectionClass $refClass, GlobExtendAnnotationsCache $globExtendAnnotationsCache): void + public function registerAnnotations(ReflectionClass|string $sourceClass, GlobExtendAnnotationsCache $globExtendAnnotationsCache): void { - $className = $refClass->getName(); + $className = $sourceClass instanceof ReflectionClass ? $sourceClass->getName() : $sourceClass; $typeClassName = $globExtendAnnotationsCache->getExtendTypeClassName(); if ($typeClassName !== null) { diff --git a/src/Mappers/GlobTypeMapper.php b/src/Mappers/GlobTypeMapper.php deleted file mode 100644 index 64df14b696..0000000000 --- a/src/Mappers/GlobTypeMapper.php +++ /dev/null @@ -1,67 +0,0 @@ -> Key: fully qualified class name - */ - protected function getClassList(): array - { - return iterator_to_array($this->classFinder); - } -} diff --git a/src/Mappers/GlobTypeMapperCache.php b/src/Mappers/GlobTypeMapperCache.php index 24be49646c..b66e4acc18 100644 --- a/src/Mappers/GlobTypeMapperCache.php +++ b/src/Mappers/GlobTypeMapperCache.php @@ -31,11 +31,11 @@ class GlobTypeMapperCache /** * Merges annotations of a given class in the global cache. * - * @param ReflectionClass $refClass + * @param ReflectionClass|class-string $sourceClass */ - public function registerAnnotations(ReflectionClass $refClass, GlobAnnotationsCache $globAnnotationsCache): void + public function registerAnnotations(ReflectionClass|string $sourceClass, GlobAnnotationsCache $globAnnotationsCache): void { - $className = $refClass->getName(); + $className = $sourceClass instanceof ReflectionClass ? $sourceClass->getName() : $sourceClass; $typeClassName = $globAnnotationsCache->getTypeClassName(); if ($typeClassName !== null) { @@ -72,12 +72,16 @@ public function registerAnnotations(ReflectionClass $refClass, GlobAnnotationsCa foreach ($globAnnotationsCache->getInputs() as $inputName => [$inputClassName, $isDefault, $description, $isUpdate]) { if ($isDefault) { if (isset($this->mapClassToInput[$inputClassName])) { - throw DuplicateMappingException::createForDefaultInput($refClass->getName()); + throw DuplicateMappingException::createForDefaultInput($className); } $this->mapClassToInput[$inputClassName] = [$className, $inputName, $description, $isUpdate]; } + if (isset($this->mapNameToInput[$inputName])) { + throw DuplicateMappingException::createForTwoInputs($inputName, $this->mapNameToInput[$inputName][0], $inputClassName); + } + $this->mapNameToInput[$inputName] = [$inputClassName, $description, $isUpdate]; } diff --git a/src/Mappers/Root/EnumTypeMapper.php b/src/Mappers/Root/EnumTypeMapper.php index 327306e681..6d5301fb75 100644 --- a/src/Mappers/Root/EnumTypeMapper.php +++ b/src/Mappers/Root/EnumTypeMapper.php @@ -17,12 +17,11 @@ use ReflectionEnum; use ReflectionMethod; use ReflectionProperty; -use Symfony\Contracts\Cache\CacheInterface; use TheCodingMachine\GraphQLite\AnnotationReader; use TheCodingMachine\GraphQLite\Discovery\ClassFinder; +use TheCodingMachine\GraphQLite\Discovery\Cache\ClassFinderBoundCache; use TheCodingMachine\GraphQLite\Types\EnumType; use UnitEnum; - use function assert; use function enum_exists; use function ltrim; @@ -37,13 +36,13 @@ class EnumTypeMapper implements RootTypeMapperInterface /** @var array */ private array $cacheByName = []; /** @var array> */ - private array|null $nameToClassMapping = null; + private array $nameToClassMapping; public function __construct( private readonly RootTypeMapperInterface $next, private readonly AnnotationReader $annotationReader, - private readonly CacheInterface $cache, - private readonly ClassFinder $classFinder, + private readonly ClassFinder $classFinder, + private readonly ClassFinderBoundCache $classFinderBoundCache, ) { } @@ -198,19 +197,19 @@ public function mapNameToType(string $typeName): NamedType&GraphQLType */ private function getNameToClassMapping(): array { - if ($this->nameToClassMapping === null) { - $this->nameToClassMapping = $this->cache->get('enum_name_to_class', function () { - $nameToClassMapping = []; - foreach ($this->classFinder as $className => $classRef) { - if (! enum_exists($className)) { - continue; - } - - $nameToClassMapping[$this->getTypeName($classRef)] = $className; - } - return $nameToClassMapping; - }); - } + $this->nameToClassMapping ??= $this->classFinderBoundCache->reduce( + $this->classFinder, + 'enum_name_to_class', + function (ReflectionClass $classReflection): ?array { + if (! $classReflection->isEnum()) { + return null; + } + + return [$this->getTypeName($classReflection) => $classReflection->getName()]; + }, + fn (array $entries) => array_merge(...array_values(array_filter($entries))), + ); + return $this->nameToClassMapping; } } diff --git a/src/Mappers/Root/MyCLabsEnumTypeMapper.php b/src/Mappers/Root/MyCLabsEnumTypeMapper.php index a2f35c9075..2b63adac9a 100644 --- a/src/Mappers/Root/MyCLabsEnumTypeMapper.php +++ b/src/Mappers/Root/MyCLabsEnumTypeMapper.php @@ -18,6 +18,7 @@ use Symfony\Contracts\Cache\CacheInterface; use TheCodingMachine\GraphQLite\AnnotationReader; use TheCodingMachine\GraphQLite\Discovery\ClassFinder; +use TheCodingMachine\GraphQLite\Discovery\Cache\ClassFinderBoundCache; use TheCodingMachine\GraphQLite\Types\MyCLabsEnumType; use function assert; @@ -35,13 +36,13 @@ class MyCLabsEnumTypeMapper implements RootTypeMapperInterface private array $cacheByName = []; /** @var array> */ - private array|null $nameToClassMapping = null; + private array $nameToClassMapping; public function __construct( private readonly RootTypeMapperInterface $next, private readonly AnnotationReader $annotationReader, - private readonly CacheInterface $cache, private readonly ClassFinder $classFinder, + private readonly ClassFinderBoundCache $classFinderBoundCache, ) { } @@ -153,21 +154,18 @@ public function mapNameToType(string $typeName): NamedType&\GraphQL\Type\Definit */ private function getNameToClassMapping(): array { - if ($this->nameToClassMapping === null) { - $this->nameToClassMapping = $this->cache->get('myclabsenum_name_to_class', function () { - $nameToClassMapping = []; - /** @var class-string $className */ - foreach ($this->classFinder as $className => $classRef) { - if (! $classRef->isSubclassOf(Enum::class)) { - continue; - } - - $nameToClassMapping[$this->getTypeName($classRef)] = $className; + $this->nameToClassMapping ??= $this->classFinderBoundCache->reduce( + $this->classFinder, + 'myclabsenum_name_to_class', + function (ReflectionClass $classReflection): ?array { + if (! $classReflection->isSubclassOf(Enum::class)) { + return null; } - return $nameToClassMapping; - }); - } + return [$this->getTypeName($classReflection) => $classReflection->getName()]; + }, + fn (array $entries) => array_merge(...array_values(array_filter($entries))), + ); return $this->nameToClassMapping; } diff --git a/src/Mappers/Root/RootTypeMapperFactoryContext.php b/src/Mappers/Root/RootTypeMapperFactoryContext.php index 3f85e206d4..d21e175b53 100644 --- a/src/Mappers/Root/RootTypeMapperFactoryContext.php +++ b/src/Mappers/Root/RootTypeMapperFactoryContext.php @@ -8,6 +8,7 @@ use Psr\SimpleCache\CacheInterface; use TheCodingMachine\GraphQLite\AnnotationReader; use TheCodingMachine\GraphQLite\Discovery\ClassFinder; +use TheCodingMachine\GraphQLite\Discovery\Cache\ClassFinderBoundCache; use TheCodingMachine\GraphQLite\Mappers\RecursiveTypeMapperInterface; use TheCodingMachine\GraphQLite\NamingStrategyInterface; use TheCodingMachine\GraphQLite\TypeRegistry; @@ -28,8 +29,7 @@ public function __construct( private readonly ContainerInterface $container, private readonly CacheInterface $cache, private readonly ClassFinder $classFinder, - private readonly int|null $globTTL, - private readonly int|null $mapTTL = null, + private readonly ClassFinderBoundCache $classFinderBoundCache, ) { } @@ -73,13 +73,8 @@ public function getClassFinder(): ClassFinder return $this->classFinder; } - public function getGlobTTL(): int|null + public function getClassFinderBoundCache(): ClassFinderBoundCache { - return $this->globTTL; - } - - public function getMapTTL(): int|null - { - return $this->mapTTL; + return $this->classFinderBoundCache; } } diff --git a/src/Mappers/StaticClassListTypeMapper.php b/src/Mappers/StaticClassListTypeMapper.php deleted file mode 100644 index eec76c3097..0000000000 --- a/src/Mappers/StaticClassListTypeMapper.php +++ /dev/null @@ -1,90 +0,0 @@ -> - */ - private array|null $classes = null; - - /** @param array $classList The list of classes to analyze. */ - public function __construct( - private array $classList, - TypeGenerator $typeGenerator, - InputTypeGenerator $inputTypeGenerator, - InputTypeUtils $inputTypeUtils, - ContainerInterface $container, - AnnotationReader $annotationReader, - NamingStrategyInterface $namingStrategy, - RecursiveTypeMapperInterface $recursiveTypeMapper, - CacheInterface $cache, - int|null $globTTL = 2, - int|null $mapTTL = null, - ) { - $cachePrefix = str_replace( - ['\\', '{', '}', '(', ')', '/', '@', ':'], - '_', - implode('_', $classList), - ); - - parent::__construct( - $cachePrefix, - $typeGenerator, - $inputTypeGenerator, - $inputTypeUtils, - $container, - $annotationReader, - $namingStrategy, - $recursiveTypeMapper, - $cache, - $globTTL, - $mapTTL, - ); - } - - /** - * Returns the array of globbed classes. - * Only instantiable classes are returned. - * - * @return array> Key: fully qualified class name - */ - protected function getClassList(): array - { - if ($this->classes === null) { - $this->classes = []; - foreach ($this->classList as $className) { - if (! class_exists($className) && ! interface_exists($className)) { - throw new GraphQLRuntimeException('Could not find class "' . $className . '"'); - } - $this->classes[$className] = new ReflectionClass($className); - } - } - - return $this->classes; - } -} diff --git a/src/Mappers/StaticClassListTypeMapperFactory.php b/src/Mappers/StaticClassListTypeMapperFactory.php index ff8becbc6f..6064600bb8 100644 --- a/src/Mappers/StaticClassListTypeMapperFactory.php +++ b/src/Mappers/StaticClassListTypeMapperFactory.php @@ -4,11 +4,12 @@ namespace TheCodingMachine\GraphQLite\Mappers; +use TheCodingMachine\GraphQLite\Discovery\StaticClassFinder; use TheCodingMachine\GraphQLite\FactoryContext; use TheCodingMachine\GraphQLite\InputTypeUtils; /** - * A type mapper that is passed the list of classes that it must scan (unlike the GlobTypeMapper that find those automatically). + * A type mapper that is passed the list of classes that it must scan. */ final class StaticClassListTypeMapperFactory implements TypeMapperFactoryInterface { @@ -26,8 +27,8 @@ public function create(FactoryContext $context): TypeMapperInterface { $inputTypeUtils = new InputTypeUtils($context->getAnnotationReader(), $context->getNamingStrategy()); - return new StaticClassListTypeMapper( - $this->classList, + return new ClassFinderTypeMapper( + new StaticClassFinder($this->classList), $context->getTypeGenerator(), $context->getInputTypeGenerator(), $inputTypeUtils, @@ -35,9 +36,7 @@ public function create(FactoryContext $context): TypeMapperInterface $context->getAnnotationReader(), $context->getNamingStrategy(), $context->getRecursiveTypeMapper(), - $context->getCache(), - $context->getGlobTTL(), - $context->getMapTTL(), + $context->getClassFinderBoundCache(), ); } } diff --git a/src/SchemaFactory.php b/src/SchemaFactory.php index d326ed3cf5..890d579a65 100644 --- a/src/SchemaFactory.php +++ b/src/SchemaFactory.php @@ -24,12 +24,13 @@ use TheCodingMachine\CacheUtils\ClassBoundCacheInterface; use TheCodingMachine\CacheUtils\ClassBoundMemoryAdapter; use TheCodingMachine\CacheUtils\FileBoundCache; +use TheCodingMachine\GraphQLite\Discovery\Cache\FileModificationClassFinderBoundCache; +use TheCodingMachine\GraphQLite\Discovery\Cache\HardClassFinderBoundCache; use TheCodingMachine\GraphQLite\Discovery\ClassFinder; -use TheCodingMachine\GraphQLite\Discovery\EmptyClassFinder; +use TheCodingMachine\GraphQLite\Discovery\StaticClassFinder; use TheCodingMachine\GraphQLite\Discovery\KcsClassFinder; -use TheCodingMachine\GraphQLite\Discovery\OldCachedClassFinder; +use TheCodingMachine\GraphQLite\Mappers\ClassFinderTypeMapper; use TheCodingMachine\GraphQLite\Mappers\CompositeTypeMapper; -use TheCodingMachine\GraphQLite\Mappers\GlobTypeMapper; use TheCodingMachine\GraphQLite\Mappers\Parameters\ContainerParameterHandler; use TheCodingMachine\GraphQLite\Mappers\Parameters\InjectUserParameterHandler; use TheCodingMachine\GraphQLite\Mappers\Parameters\ParameterMiddlewareInterface; @@ -85,8 +86,6 @@ */ class SchemaFactory { - public const GLOB_CACHE_SECONDS = 2; - /** @var array */ private array $namespaces = []; @@ -122,7 +121,7 @@ class SchemaFactory private SchemaConfig|null $schemaConfig = null; - private int|null $globTTL = self::GLOB_CACHE_SECONDS; + private bool $devMode = true; /** @var array */ private array $fieldMiddlewares = []; @@ -298,36 +297,24 @@ public function setFinder(FinderInterface $finder): self return $this; } - /** - * Sets the time to live time of the cache for annotations in files. - * By default this is set to 2 seconds which is ok for development environments. - * Set this to "null" (i.e. infinity) for production environments. - */ - public function setGlobTTL(int|null $globTTL): self - { - $this->globTTL = $globTTL; - - return $this; - } - /** * Sets GraphQLite in "prod" mode (cache settings optimized for best performance). - * - * This is a shortcut for `$schemaFactory->setGlobTTL(null)` */ public function prodMode(): self { - return $this->setGlobTTL(null); + $this->devMode = false; + + return $this; } /** * Sets GraphQLite in "dev" mode (this is the default mode: cache settings optimized for best developer experience). - * - * This is a shortcut for `$schemaFactory->setGlobTTL(2)` */ public function devMode(): self { - return $this->setGlobTTL(self::GLOB_CACHE_SECONDS); + $this->devMode = true; + + return $this; } /** @@ -379,7 +366,10 @@ public function createSchema(): Schema [$docBlockFactory, $docBlockContextFactory] = $this->createDocBlockFactory($nonInheritedClassBoundCache); $namingStrategy = $this->namingStrategy ?: new NamingStrategy(); $typeRegistry = new TypeRegistry(); - $classFinder = $this->createClassFinder($namespacedCache); + $classFinder = $this->createClassFinder(); + $classFinderBoundCache = $this->devMode ? + new FileModificationClassFinderBoundCache($this->cache) : + new HardClassFinderBoundCache($this->cache); $expressionLanguage = $this->expressionLanguage ?: new ExpressionLanguage($symfonyCache); $expressionLanguage->registerProvider(new SecurityExpressionLanguageProvider()); @@ -410,11 +400,11 @@ public function createSchema(): Schema $errorRootTypeMapper = new FinalRootTypeMapper($recursiveTypeMapper); $rootTypeMapper = new BaseTypeMapper($errorRootTypeMapper, $recursiveTypeMapper, $topRootTypeMapper); - $rootTypeMapper = new EnumTypeMapper($rootTypeMapper, $annotationReader, $symfonyCache, $classFinder); + $rootTypeMapper = new EnumTypeMapper($rootTypeMapper, $annotationReader, $classFinder, $classFinderBoundCache); if (class_exists(Enum::class)) { // Annotation support - deprecated - $rootTypeMapper = new MyCLabsEnumTypeMapper($rootTypeMapper, $annotationReader, $symfonyCache, $classFinder); + $rootTypeMapper = new MyCLabsEnumTypeMapper($rootTypeMapper, $annotationReader, $classFinder, $classFinderBoundCache); } if (!empty($this->rootTypeMapperFactories)) { @@ -427,7 +417,7 @@ public function createSchema(): Schema $this->container, $namespacedCache, $classFinder, - $this->globTTL, + $classFinderBoundCache, ); $reversedRootTypeMapperFactories = array_reverse($this->rootTypeMapperFactories); @@ -472,7 +462,7 @@ public function createSchema(): Schema $inputTypeGenerator = new InputTypeGenerator($inputTypeUtils, $fieldsBuilder, $this->inputTypeValidator); if ($this->namespaces) { - $compositeTypeMapper->addTypeMapper(new GlobTypeMapper( + $compositeTypeMapper->addTypeMapper(new ClassFinderTypeMapper( $classFinder, $typeGenerator, $inputTypeGenerator, @@ -481,8 +471,7 @@ public function createSchema(): Schema $annotationReader, $namingStrategy, $recursiveTypeMapper, - $namespacedCache, - $this->globTTL, + $classFinderBoundCache, )); } @@ -499,7 +488,8 @@ public function createSchema(): Schema $this->container, $namespacedCache, $this->inputTypeValidator, - $this->globTTL, + $classFinder, + $classFinderBoundCache, ); } @@ -518,15 +508,14 @@ public function createSchema(): Schema $compositeTypeMapper->addTypeMapper(new PorpaginasTypeMapper($recursiveTypeMapper)); $queryProviders = []; - foreach ($this->namespaces as $namespace) { + + if ($this->namespaces) { $queryProviders[] = new GlobControllerQueryProvider( - $namespace, $fieldsBuilder, $this->container, $annotationReader, - $namespacedCache, $classFinder, - $this->globTTL, + $classFinderBoundCache, ); } @@ -547,14 +536,14 @@ public function createSchema(): Schema return new Schema($aggregateQueryProvider, $recursiveTypeMapper, $typeResolver, $topRootTypeMapper, $this->schemaConfig); } - private function createClassFinder(CacheInterface $cache): ClassFinder + private function createClassFinder(): ClassFinder { // When no namespaces are specified, class finder uses all available namespaces to discover classes. // While this is technically okay, it doesn't follow SchemaFactory's semantics that allow it's // users to manually specify classes (see SchemaFactory::testCreateSchemaOnlyWithFactories()), // without having to specify namespaces to glob. This solves it by providing an empty iterator. if (!$this->namespaces) { - return new EmptyClassFinder(); + return new StaticClassFinder([]); } $finder = (clone ($this->finder ?? new ComposerFinder())); @@ -563,17 +552,7 @@ private function createClassFinder(CacheInterface $cache): ClassFinder $finder->inNamespace($namespace); } - return new OldCachedClassFinder( - new KcsClassFinder($finder), - $cache, - $this->globTTL - ); - -// if ($this->devMode) { -// $finder = new FileCachedFinder($finder); -// } - - return $finder; + return new KcsClassFinder($finder); } private function createDocBlockFactory(ClassBoundCacheInterface $nonInheritedClassBoundCache): array diff --git a/tests/AbstractQueryProvider.php b/tests/AbstractQueryProvider.php index f8a466b65f..5338bae5b4 100644 --- a/tests/AbstractQueryProvider.php +++ b/tests/AbstractQueryProvider.php @@ -24,8 +24,9 @@ use TheCodingMachine\GraphQLite\Containers\BasicAutoWiringContainer; use TheCodingMachine\GraphQLite\Containers\EmptyContainer; use TheCodingMachine\GraphQLite\Containers\LazyContainer; +use TheCodingMachine\GraphQLite\Discovery\Cache\HardClassFinderBoundCache; use TheCodingMachine\GraphQLite\Discovery\ClassFinder; -use TheCodingMachine\GraphQLite\Discovery\EmptyClassFinder; +use TheCodingMachine\GraphQLite\Discovery\StaticClassFinder; use TheCodingMachine\GraphQLite\Discovery\KcsClassFinder; use TheCodingMachine\GraphQLite\Discovery\OldCachedClassFinder; use TheCodingMachine\GraphQLite\Fixtures\Mocks\MockResolvableInputObjectType; @@ -401,15 +402,15 @@ protected function buildRootTypeMapper(): RootTypeMapperInterface $rootTypeMapper = new MyCLabsEnumTypeMapper( $rootTypeMapper, $this->getAnnotationReader(), - $arrayAdapter, - new EmptyClassFinder(), + new StaticClassFinder([]), + new HardClassFinderBoundCache(new Psr16Cache($arrayAdapter)), ); $rootTypeMapper = new EnumTypeMapper( $rootTypeMapper, $this->getAnnotationReader(), - $arrayAdapter, - new EmptyClassFinder(), + new StaticClassFinder([]), + new HardClassFinderBoundCache(new Psr16Cache($arrayAdapter)), ); $rootTypeMapper = new CompoundTypeMapper( diff --git a/tests/GlobControllerQueryProviderTest.php b/tests/GlobControllerQueryProviderTest.php index 458ac0ae1a..bda8e4c02b 100644 --- a/tests/GlobControllerQueryProviderTest.php +++ b/tests/GlobControllerQueryProviderTest.php @@ -9,6 +9,7 @@ use ReflectionClass; use Symfony\Component\Cache\Adapter\NullAdapter; use Symfony\Component\Cache\Psr16Cache; +use TheCodingMachine\GraphQLite\Discovery\Cache\HardClassFinderBoundCache; use TheCodingMachine\GraphQLite\Discovery\KcsClassFinder; use TheCodingMachine\GraphQLite\Fixtures\TestController; @@ -42,13 +43,11 @@ public function has($id): bool $finder->inNamespace('TheCodingMachine\\GraphQLite\\Fixtures'); $finder->filter(static fn (ReflectionClass $class) => $class->getNamespaceName() === 'TheCodingMachine\\GraphQLite\\Fixtures'); // Fix for recursive:false $globControllerQueryProvider = new GlobControllerQueryProvider( - 'TheCodingMachine\\GraphQLite\\Fixtures', $this->getFieldsBuilder(), $container, $this->getAnnotationReader(), - new Psr16Cache(new NullAdapter()), new KcsClassFinder($finder), - 0, + new HardClassFinderBoundCache(new Psr16Cache(new NullAdapter())) ); $queries = $globControllerQueryProvider->getQueries(); diff --git a/tests/Integration/IntegrationTestCase.php b/tests/Integration/IntegrationTestCase.php index 436dbc1d9f..bbcc729ffe 100644 --- a/tests/Integration/IntegrationTestCase.php +++ b/tests/Integration/IntegrationTestCase.php @@ -22,6 +22,8 @@ use TheCodingMachine\GraphQLite\Containers\BasicAutoWiringContainer; use TheCodingMachine\GraphQLite\Containers\EmptyContainer; use TheCodingMachine\GraphQLite\Containers\LazyContainer; +use TheCodingMachine\GraphQLite\Discovery\Cache\ClassFinderBoundCache; +use TheCodingMachine\GraphQLite\Discovery\Cache\HardClassFinderBoundCache; use TheCodingMachine\GraphQLite\Discovery\ClassFinder; use TheCodingMachine\GraphQLite\Discovery\KcsClassFinder; use TheCodingMachine\GraphQLite\Discovery\OldCachedClassFinder; @@ -30,6 +32,7 @@ use TheCodingMachine\GraphQLite\InputTypeGenerator; use TheCodingMachine\GraphQLite\InputTypeUtils; use TheCodingMachine\GraphQLite\Loggers\ExceptionLogger; +use TheCodingMachine\GraphQLite\Mappers\ClassFinderTypeMapper; use TheCodingMachine\GraphQLite\Mappers\CompositeTypeMapper; use TheCodingMachine\GraphQLite\Mappers\GlobTypeMapper; use TheCodingMachine\GraphQLite\Mappers\Parameters\ContainerParameterHandler; @@ -104,30 +107,30 @@ public function createContainer(array $overloadedServices = []): ContainerInterf $composerFinder->inNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration\\Models'); $composerFinder->inNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration\\Controllers'); - return new OldCachedClassFinder( - new KcsClassFinder($composerFinder), + return new KcsClassFinder($composerFinder); + }, + ClassFinderBoundCache::class => function () { + return new HardClassFinderBoundCache( new Psr16Cache(new ArrayAdapter()), ); }, QueryProviderInterface::class => static function (ContainerInterface $container) { $queryProvider = new GlobControllerQueryProvider( - 'TheCodingMachine\\GraphQLite\\Fixtures\\Integration\\Controllers', $container->get(FieldsBuilder::class), $container->get(BasicAutoWiringContainer::class), $container->get(AnnotationReader::class), - new Psr16Cache(new ArrayAdapter()), $container->get(ClassFinder::class), + $container->get(ClassFinderBoundCache::class), ); $queryProvider = new AggregateQueryProvider([ $queryProvider, new GlobControllerQueryProvider( - 'TheCodingMachine\\GraphQLite\\Fixtures\\Integration\\Controllers', $container->get(FieldsBuilder::class), $container->get(BasicAutoWiringContainer::class), $container->get(AnnotationReader::class), - new Psr16Cache(new ArrayAdapter()), $container->get(ClassFinder::class), + $container->get(ClassFinderBoundCache::class), ), ]); @@ -223,10 +226,10 @@ public function createContainer(array $overloadedServices = []): ContainerInterf TypeMapperInterface::class => static function (ContainerInterface $container) { return new CompositeTypeMapper(); }, - GlobTypeMapper::class => static function (ContainerInterface $container) { + ClassFinderTypeMapper::class => static function (ContainerInterface $container) { $arrayAdapter = new ArrayAdapter(); $arrayAdapter->setLogger(new ExceptionLogger()); - return new GlobTypeMapper( + return new ClassFinderTypeMapper( $container->get(ClassFinder::class), $container->get(TypeGenerator::class), $container->get(InputTypeGenerator::class), @@ -235,7 +238,7 @@ public function createContainer(array $overloadedServices = []): ContainerInterf $container->get(AnnotationReader::class), $container->get(NamingStrategyInterface::class), $container->get(RecursiveTypeMapperInterface::class), - new Psr16Cache($arrayAdapter), + $container->get(ClassFinderBoundCache::class), ); }, PorpaginasTypeMapper::class => static function (ContainerInterface $container) { @@ -245,8 +248,8 @@ public function createContainer(array $overloadedServices = []): ContainerInterf return new EnumTypeMapper( $container->get(RootTypeMapperInterface::class), $container->get(AnnotationReader::class), - new ArrayAdapter(), $container->get(ClassFinder::class), + $container->get(ClassFinderBoundCache::class), ); }, TypeGenerator::class => static function (ContainerInterface $container) { @@ -325,8 +328,8 @@ public function createContainer(array $overloadedServices = []): ContainerInterf // These are in reverse order of execution $errorRootTypeMapper = new FinalRootTypeMapper($container->get(RecursiveTypeMapperInterface::class)); $rootTypeMapper = new BaseTypeMapper($errorRootTypeMapper, $container->get(RecursiveTypeMapperInterface::class), $container->get(RootTypeMapperInterface::class)); - $rootTypeMapper = new MyCLabsEnumTypeMapper($rootTypeMapper, $container->get(AnnotationReader::class), new ArrayAdapter(), $container->get(ClassFinder::class)); - $rootTypeMapper = new EnumTypeMapper($rootTypeMapper, $container->get(AnnotationReader::class), new ArrayAdapter(), $container->get(ClassFinder::class)); + $rootTypeMapper = new MyCLabsEnumTypeMapper($rootTypeMapper, $container->get(AnnotationReader::class), $container->get(ClassFinder::class), $container->get(ClassFinderBoundCache::class)); + $rootTypeMapper = new EnumTypeMapper($rootTypeMapper, $container->get(AnnotationReader::class), $container->get(ClassFinder::class), $container->get(ClassFinderBoundCache::class)); $rootTypeMapper = new CompoundTypeMapper($rootTypeMapper, $container->get(RootTypeMapperInterface::class), $container->get(NamingStrategyInterface::class), $container->get(TypeRegistry::class), $container->get(RecursiveTypeMapperInterface::class)); $rootTypeMapper = new IteratorTypeMapper($rootTypeMapper, $container->get(RootTypeMapperInterface::class)); return $rootTypeMapper; @@ -356,7 +359,7 @@ public function createContainer(array $overloadedServices = []): ContainerInterf $container = new LazyContainer($overloadedServices + $services); $container->get(TypeResolver::class)->registerSchema($container->get(Schema::class)); - $container->get(TypeMapperInterface::class)->addTypeMapper($container->get(GlobTypeMapper::class)); + $container->get(TypeMapperInterface::class)->addTypeMapper($container->get(ClassFinderTypeMapper::class)); $container->get(TypeMapperInterface::class)->addTypeMapper($container->get(PorpaginasTypeMapper::class)); $container->get('topRootTypeMapper')->setNext($container->get('rootTypeMapper')); diff --git a/tests/Utils/NsTest.php b/tests/Utils/NsTest.php deleted file mode 100644 index b46d8bfe6d..0000000000 --- a/tests/Utils/NsTest.php +++ /dev/null @@ -1,145 +0,0 @@ -cache = new Psr16Cache(new ArrayAdapter()); - $this->namespace = 'TheCodingMachine\GraphQLite\Fixtures\Types'; - $this->finder = new ComposerFinder(); - $this->globTTL = 10; - } - - #[DataProvider('loadsClassListProvider')] - public function testLoadsClassList(array $expectedClasses, string $namespace): void - { - $ns = new NS( - namespace: $namespace, - cache: $this->cache, - finder: $this->finder, - globTTL: null, - ); - - self::assertEqualsCanonicalizing($expectedClasses, array_keys($ns->getClassList())); - } - - public static function loadsClassListProvider(): iterable - { - yield 'autoload' => [ - [ - TestFactory::class, - GetterSetterType::class, - FooType::class, - MagicGetterSetterType::class, - FooExtendType::class, - NoTypeAnnotation::class, - AbstractFooType::class, - EnumType::class - ], - 'TheCodingMachine\GraphQLite\Fixtures\Types', - ]; - - // The class should be ignored. - yield 'incorrect namespace class without autoload' => [ - [], - 'TheCodingMachine\GraphQLite\Fixtures\BadNamespace', - ]; - } - - public function testCaching(): void - { - $ns = new NS( - namespace: $this->namespace, - cache: $this->cache, - finder: $this->finder, - globTTL: $this->globTTL, - ); - self::assertNotNull($ns->getClassList()); - - // create with mock finder to test cache - $finder = $this->createMock(FinderInterface::class); - $finder->expects(self::never())->method('inNamespace')->willReturnSelf(); - $ns = new NS( - namespace: $this->namespace, - cache: $this->cache, - finder: $finder, - globTTL: $this->globTTL, - ); - self::assertNotNull($ns->getClassList()); - } - - public function testCachingWithInvalidKey(): void - { - $exception = new class extends Exception implements InvalidArgumentException { - }; - $cache = $this->createMock(CacheInterface::class); - $cache->expects(self::once())->method('get')->willThrowException($exception); - $cache->expects(self::once())->method('set')->willThrowException($exception); - $ns = new NS( - namespace: $this->namespace, - cache: $cache, - finder: $this->finder, - globTTL: $this->globTTL, - ); - $ns->getClassList(); - } - - public function testCachingWithInvalidCache(): void - { - $cache = $this->createMock(CacheInterface::class); - $cache->expects(self::once())->method('get')->willReturn(['foo']); - $ns = new NS( - namespace: $this->namespace, - cache: $cache, - finder: $this->finder, - globTTL: $this->globTTL, - ); - $classList = $ns->getClassList(); - self::assertNotNull($classList); - self::assertNotEmpty($classList); - } - - public function testFinderWithUnexpectedOutput() { - - $finder = $this->createMock(FinderInterface::class); - $finder->expects(self::once())->method('inNamespace')->willReturnSelf(); - $finder->expects(self::once())->method('getIterator')->willReturn(new \ArrayIterator([ 'test' => new \ReflectionException()])); - $ns = new NS( - namespace: $this->namespace, - cache: $this->cache, - finder: $finder, - globTTL: $this->globTTL, - ); - $classList = $ns->getClassList(); - self::assertNotNull($classList); - self::assertEmpty($classList);} -} From 6f21eb24ae594cfb96571bd6016ba2fe2f2d5958 Mon Sep 17 00:00:00 2001 From: Oleksandr Prypkhan Date: Mon, 8 Apr 2024 23:08:59 +0300 Subject: [PATCH 06/28] Refactor EnumTypeMapper to use cached doc block factory --- src/Mappers/Root/EnumTypeMapper.php | 24 +++++++---------------- src/SchemaFactory.php | 2 +- tests/AbstractQueryProvider.php | 1 + tests/Integration/IntegrationTestCase.php | 2 +- 4 files changed, 10 insertions(+), 19 deletions(-) diff --git a/src/Mappers/Root/EnumTypeMapper.php b/src/Mappers/Root/EnumTypeMapper.php index 6d5301fb75..24cbf89d66 100644 --- a/src/Mappers/Root/EnumTypeMapper.php +++ b/src/Mappers/Root/EnumTypeMapper.php @@ -10,7 +10,6 @@ use GraphQL\Type\Definition\Type as GraphQLType; use MyCLabs\Enum\Enum; use phpDocumentor\Reflection\DocBlock; -use phpDocumentor\Reflection\DocBlockFactory; use phpDocumentor\Reflection\Type; use phpDocumentor\Reflection\Types\Object_; use ReflectionClass; @@ -20,6 +19,7 @@ use TheCodingMachine\GraphQLite\AnnotationReader; use TheCodingMachine\GraphQLite\Discovery\ClassFinder; use TheCodingMachine\GraphQLite\Discovery\Cache\ClassFinderBoundCache; +use TheCodingMachine\GraphQLite\Reflection\DocBlock\DocBlockFactory; use TheCodingMachine\GraphQLite\Types\EnumType; use UnitEnum; use function assert; @@ -41,6 +41,7 @@ class EnumTypeMapper implements RootTypeMapperInterface public function __construct( private readonly RootTypeMapperInterface $next, private readonly AnnotationReader $annotationReader, + private readonly DocBlockFactory $docBlockFactory, private readonly ClassFinder $classFinder, private readonly ClassFinderBoundCache $classFinderBoundCache, ) { @@ -119,14 +120,9 @@ private function mapByClassName(string $enumClass): EnumType|null $reflectionEnum->isBacked() && (string) $reflectionEnum->getBackingType() === 'string'; - $docBlockFactory = DocBlockFactory::createInstance(); - - $enumDescription = null; - $docComment = $reflectionEnum->getDocComment(); - if ($docComment) { - $docBlock = $docBlockFactory->create($docComment); - $enumDescription = $docBlock->getSummary(); - } + $enumDescription = $this->docBlockFactory + ->createFromReflector($reflectionEnum) + ->getSummary() ?: null; /** @var array $enumCaseDescriptions */ $enumCaseDescriptions = []; @@ -134,15 +130,9 @@ private function mapByClassName(string $enumClass): EnumType|null $enumCaseDeprecationReasons = []; foreach ($reflectionEnum->getCases() as $reflectionEnumCase) { - $docComment = $reflectionEnumCase->getDocComment(); - if (! $docComment) { - continue; - } - - $docBlock = $docBlockFactory->create($docComment); - $enumCaseDescription = $docBlock->getSummary(); + $docBlock = $this->docBlockFactory->createFromReflector($reflectionEnumCase); - $enumCaseDescriptions[$reflectionEnumCase->getName()] = $enumCaseDescription; + $enumCaseDescriptions[$reflectionEnumCase->getName()] = $docBlock->getSummary() ?: null; $deprecation = $docBlock->getTagsByName('deprecated')[0] ?? null; // phpcs:ignore diff --git a/src/SchemaFactory.php b/src/SchemaFactory.php index 890d579a65..dee1c4c194 100644 --- a/src/SchemaFactory.php +++ b/src/SchemaFactory.php @@ -400,7 +400,7 @@ public function createSchema(): Schema $errorRootTypeMapper = new FinalRootTypeMapper($recursiveTypeMapper); $rootTypeMapper = new BaseTypeMapper($errorRootTypeMapper, $recursiveTypeMapper, $topRootTypeMapper); - $rootTypeMapper = new EnumTypeMapper($rootTypeMapper, $annotationReader, $classFinder, $classFinderBoundCache); + $rootTypeMapper = new EnumTypeMapper($rootTypeMapper, $annotationReader, $docBlockFactory, $classFinder, $classFinderBoundCache); if (class_exists(Enum::class)) { // Annotation support - deprecated diff --git a/tests/AbstractQueryProvider.php b/tests/AbstractQueryProvider.php index 5338bae5b4..e27e1b0b67 100644 --- a/tests/AbstractQueryProvider.php +++ b/tests/AbstractQueryProvider.php @@ -409,6 +409,7 @@ protected function buildRootTypeMapper(): RootTypeMapperInterface $rootTypeMapper = new EnumTypeMapper( $rootTypeMapper, $this->getAnnotationReader(), + $this->getDocBlockFactory(), new StaticClassFinder([]), new HardClassFinderBoundCache(new Psr16Cache($arrayAdapter)), ); diff --git a/tests/Integration/IntegrationTestCase.php b/tests/Integration/IntegrationTestCase.php index bbcc729ffe..3ca8a4f10d 100644 --- a/tests/Integration/IntegrationTestCase.php +++ b/tests/Integration/IntegrationTestCase.php @@ -329,7 +329,7 @@ public function createContainer(array $overloadedServices = []): ContainerInterf $errorRootTypeMapper = new FinalRootTypeMapper($container->get(RecursiveTypeMapperInterface::class)); $rootTypeMapper = new BaseTypeMapper($errorRootTypeMapper, $container->get(RecursiveTypeMapperInterface::class), $container->get(RootTypeMapperInterface::class)); $rootTypeMapper = new MyCLabsEnumTypeMapper($rootTypeMapper, $container->get(AnnotationReader::class), $container->get(ClassFinder::class), $container->get(ClassFinderBoundCache::class)); - $rootTypeMapper = new EnumTypeMapper($rootTypeMapper, $container->get(AnnotationReader::class), $container->get(ClassFinder::class), $container->get(ClassFinderBoundCache::class)); + $rootTypeMapper = new EnumTypeMapper($rootTypeMapper, $container->get(AnnotationReader::class), $container->get(DocBlockFactory::class), $container->get(ClassFinder::class), $container->get(ClassFinderBoundCache::class)); $rootTypeMapper = new CompoundTypeMapper($rootTypeMapper, $container->get(RootTypeMapperInterface::class), $container->get(NamingStrategyInterface::class), $container->get(TypeRegistry::class), $container->get(RecursiveTypeMapperInterface::class)); $rootTypeMapper = new IteratorTypeMapper($rootTypeMapper, $container->get(RootTypeMapperInterface::class)); return $rootTypeMapper; From 37979942d31970905100c857ca12d4ff38e46819 Mon Sep 17 00:00:00 2001 From: Oleksandr Prypkhan Date: Wed, 10 Apr 2024 19:12:17 +0300 Subject: [PATCH 07/28] Fix FileModificationClassFinderBoundCache --- .../FileModificationClassFinderBoundCache.php | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Discovery/Cache/FileModificationClassFinderBoundCache.php b/src/Discovery/Cache/FileModificationClassFinderBoundCache.php index d5f48ff12b..c0cff87eb7 100644 --- a/src/Discovery/Cache/FileModificationClassFinderBoundCache.php +++ b/src/Discovery/Cache/FileModificationClassFinderBoundCache.php @@ -66,19 +66,32 @@ private function entries( ): mixed { $previousEntries = $this->cache->get($key) ?? []; - $entries = []; $result = []; + $entries = []; - $classFinder = $classFinder->withPathFilter(function (string $filename) use (&$entries, $previousEntries) { + $classFinder = $classFinder->withPathFilter(function (string $filename) use (&$entries, &$result, $previousEntries) { $entry = $previousEntries[$filename] ?? null; // If there's no entry in cache for this filename (new file or previously uncached), // or if it the file has been modified since caching, we'll try to autoload // the class and collect the cached information (again). if (!$entry || $this->dependenciesChanged($entry['dependencies'])) { + // In case this file isn't a class, or doesn't match the provided namespace filter, + // it will not be emitted in the iterator and won't reach the `foreach()` below. + // So to avoid iterating over these files again, we'll mark them as non-matching. + // If they are matching, it'll be overwritten in the `foreach` loop below. + $entries[$filename] = [ + 'dependencies' => [$filename => filemtime($filename)], + 'matching' => false, + ]; + return true; } + if ($entry['matching']) { + $result[$filename] = $entry['data']; + } + $entries[$filename] = $entry; return false; @@ -91,6 +104,7 @@ private function entries( $entries[$filename] = [ 'dependencies' => $this->fileDependencies($classReflection), 'data' => $result[$filename], + 'matching' => true, ]; } From 7165342a44e986877921f2d7b5b08a406c6e257e Mon Sep 17 00:00:00 2001 From: Oleksandr Prypkhan Date: Mon, 26 Aug 2024 00:35:30 +0300 Subject: [PATCH 08/28] Use Kcs ClassFinder cache --- composer.json | 2 +- src/SchemaFactory.php | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index e482e16693..84e6c00ac7 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "symfony/expression-language": "^4 || ^5 || ^6 || ^7", "thecodingmachine/cache-utils": "^1", "webonyx/graphql-php": "^v15.0", - "kcs/class-finder": "^0.5.0" + "kcs/class-finder": "^0.5.1" }, "require-dev": { "beberlei/porpaginas": "^1.2 || ^2.0", diff --git a/src/SchemaFactory.php b/src/SchemaFactory.php index dee1c4c194..e37ecc7e2d 100644 --- a/src/SchemaFactory.php +++ b/src/SchemaFactory.php @@ -8,6 +8,8 @@ use Doctrine\Common\Annotations\PsrCachedReader; use Doctrine\Common\Annotations\Reader; use GraphQL\Type\SchemaConfig; +use Kcs\ClassFinder\FileFinder\CachedFileFinder; +use Kcs\ClassFinder\FileFinder\DefaultFileFinder; use Kcs\ClassFinder\Finder\ComposerFinder; use Kcs\ClassFinder\Finder\FinderInterface; use MyCLabs\Enum\Enum; @@ -17,6 +19,7 @@ use Psr\Cache\CacheItemPoolInterface; use Psr\Container\ContainerInterface; use Psr\SimpleCache\CacheInterface; +use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\Cache\Adapter\Psr16Adapter; use Symfony\Component\ExpressionLanguage\ExpressionLanguage; use TheCodingMachine\CacheUtils\ClassBoundCache; @@ -548,8 +551,13 @@ private function createClassFinder(): ClassFinder $finder = (clone ($this->finder ?? new ComposerFinder())); + // Because this finder may be iterated more than once, we need to make + // sure that the filesystem is only hit once in the lifetime of the application, + // as that may be expensive for larger projects or non-native filesystems. + $finder = $finder->withFileFinder(new CachedFileFinder(new DefaultFileFinder(), new ArrayAdapter())); + foreach ($this->namespaces as $namespace) { - $finder->inNamespace($namespace); + $finder = $finder->inNamespace($namespace); } return new KcsClassFinder($finder); From 821e85f132b69eb473e5f9b304f2b838fe94ff01 Mon Sep 17 00:00:00 2001 From: Oleksandr Prypkhan Date: Mon, 26 Aug 2024 00:35:43 +0300 Subject: [PATCH 09/28] One more tiny optimization for unchanged cache --- .../Cache/FileModificationClassFinderBoundCache.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Discovery/Cache/FileModificationClassFinderBoundCache.php b/src/Discovery/Cache/FileModificationClassFinderBoundCache.php index c0cff87eb7..3a4149f200 100644 --- a/src/Discovery/Cache/FileModificationClassFinderBoundCache.php +++ b/src/Discovery/Cache/FileModificationClassFinderBoundCache.php @@ -69,7 +69,10 @@ private function entries( $result = []; $entries = []; - $classFinder = $classFinder->withPathFilter(function (string $filename) use (&$entries, &$result, $previousEntries) { + // The size of the cache may be huge, so let's avoid writes when unnecessary. + $changed = false; + + $classFinder = $classFinder->withPathFilter(function (string $filename) use (&$entries, &$result, &$changed, $previousEntries) { $entry = $previousEntries[$filename] ?? null; // If there's no entry in cache for this filename (new file or previously uncached), @@ -85,6 +88,8 @@ private function entries( 'matching' => false, ]; + $changed = true; + return true; } @@ -106,9 +111,13 @@ private function entries( 'data' => $result[$filename], 'matching' => true, ]; + + $changed = true; } - $this->cache->set($key, $entries); + if ($changed) { + $this->cache->set($key, $entries); + } return $result; } From 2b60d838437c60d510cbecc28ba2664a71e3fda6 Mon Sep 17 00:00:00 2001 From: Oleksandr Prypkhan Date: Mon, 26 Aug 2024 22:52:18 +0300 Subject: [PATCH 10/28] Change name of ClassFinderBoundCache --- src/Discovery/Cache/ClassFinderBoundCache.php | 25 ------------ .../Cache/ClassFinderComputedCache.php | 39 +++++++++++++++++++ ...eModificationClassFinderComputedCache.php} | 4 +- ...e.php => HardClassFinderComputedCache.php} | 4 +- src/FactoryContext.php | 24 ++++++------ src/GlobControllerQueryProvider.php | 22 ++++------- src/Mappers/ClassFinderTypeMapper.php | 8 ++-- src/Mappers/Root/EnumTypeMapper.php | 14 +++---- src/Mappers/Root/MyCLabsEnumTypeMapper.php | 12 +++--- .../Root/RootTypeMapperFactoryContext.php | 20 +++++----- src/SchemaFactory.php | 8 ++-- tests/AbstractQueryProvider.php | 11 ++---- tests/GlobControllerQueryProviderTest.php | 4 +- tests/Integration/IntegrationTestCase.php | 22 +++++------ 14 files changed, 108 insertions(+), 109 deletions(-) delete mode 100644 src/Discovery/Cache/ClassFinderBoundCache.php create mode 100644 src/Discovery/Cache/ClassFinderComputedCache.php rename src/Discovery/Cache/{FileModificationClassFinderBoundCache.php => FileModificationClassFinderComputedCache.php} (97%) rename src/Discovery/Cache/{HardClassFinderBoundCache.php => HardClassFinderComputedCache.php} (93%) diff --git a/src/Discovery/Cache/ClassFinderBoundCache.php b/src/Discovery/Cache/ClassFinderBoundCache.php deleted file mode 100644 index f53222dbac..0000000000 --- a/src/Discovery/Cache/ClassFinderBoundCache.php +++ /dev/null @@ -1,25 +0,0 @@ -): TEntry $map - * @param callable(array): TReturn $reduce - * - * @return TReturn - */ - public function reduce( - ClassFinder $classFinder, - string $key, - callable $map, - callable $reduce, - ): mixed; -} \ No newline at end of file diff --git a/src/Discovery/Cache/ClassFinderComputedCache.php b/src/Discovery/Cache/ClassFinderComputedCache.php new file mode 100644 index 0000000000..ee1e682e07 --- /dev/null +++ b/src/Discovery/Cache/ClassFinderComputedCache.php @@ -0,0 +1,39 @@ +. Once all classes are iterated, + * $reduce will then be called with that map, and it's final result is returned. + * + * Now the point of this is now whenever file A changes, we can automatically remove entries generated for it + * and simply call $map only for classes from file A, leaving all other entries untouched and not having to + * waste resources on the rest of them. We then only need to call the cheap $reduce and have the final result :) + * + * @template TEntry of mixed + * @template TReturn of mixed + * + * @param callable(ReflectionClass): TEntry $map + * @param callable(array): TReturn $reduce + * + * @return TReturn + */ + public function compute( + ClassFinder $classFinder, + string $key, + callable $map, + callable $reduce, + ): mixed; +} \ No newline at end of file diff --git a/src/Discovery/Cache/FileModificationClassFinderBoundCache.php b/src/Discovery/Cache/FileModificationClassFinderComputedCache.php similarity index 97% rename from src/Discovery/Cache/FileModificationClassFinderBoundCache.php rename to src/Discovery/Cache/FileModificationClassFinderComputedCache.php index 3a4149f200..b1fdd2c9d1 100644 --- a/src/Discovery/Cache/FileModificationClassFinderBoundCache.php +++ b/src/Discovery/Cache/FileModificationClassFinderComputedCache.php @@ -23,7 +23,7 @@ * - if no cache exists, it iterates over the whole class finder and returns all reflection that match the filter * - if cache does exist, it only iterates over changed classes */ -class FileModificationClassFinderBoundCache implements ClassFinderBoundCache +class FileModificationClassFinderComputedCache implements ClassFinderComputedCache { public function __construct( private readonly CacheInterface $cache, @@ -40,7 +40,7 @@ public function __construct( * * @return TReturn */ - public function reduce( + public function compute( ClassFinder $classFinder, string $key, callable $map, diff --git a/src/Discovery/Cache/HardClassFinderBoundCache.php b/src/Discovery/Cache/HardClassFinderComputedCache.php similarity index 93% rename from src/Discovery/Cache/HardClassFinderBoundCache.php rename to src/Discovery/Cache/HardClassFinderComputedCache.php index 109b2a8053..46ac703d71 100644 --- a/src/Discovery/Cache/HardClassFinderBoundCache.php +++ b/src/Discovery/Cache/HardClassFinderComputedCache.php @@ -6,7 +6,7 @@ use TheCodingMachine\GraphQLite\Discovery\ClassFinder; use ReflectionClass; -class HardClassFinderBoundCache implements ClassFinderBoundCache +class HardClassFinderComputedCache implements ClassFinderComputedCache { public function __construct( @@ -24,7 +24,7 @@ public function __construct( * * @return TReturn */ - public function reduce( + public function compute( ClassFinder $classFinder, string $key, callable $map, diff --git a/src/FactoryContext.php b/src/FactoryContext.php index c1306ee2f8..a6d34f96ec 100644 --- a/src/FactoryContext.php +++ b/src/FactoryContext.php @@ -7,7 +7,7 @@ use Psr\Container\ContainerInterface; use Psr\SimpleCache\CacheInterface; use TheCodingMachine\GraphQLite\Discovery\ClassFinder; -use TheCodingMachine\GraphQLite\Discovery\Cache\ClassFinderBoundCache; +use TheCodingMachine\GraphQLite\Discovery\Cache\ClassFinderComputedCache; use TheCodingMachine\GraphQLite\Mappers\RecursiveTypeMapperInterface; use TheCodingMachine\GraphQLite\Types\InputTypeValidatorInterface; use TheCodingMachine\GraphQLite\Types\TypeResolver; @@ -22,17 +22,17 @@ final class FactoryContext public function __construct( private readonly AnnotationReader $annotationReader, private readonly TypeResolver $typeResolver, - private readonly NamingStrategyInterface $namingStrategy, - private readonly TypeRegistry $typeRegistry, - private readonly FieldsBuilder $fieldsBuilder, - private readonly TypeGenerator $typeGenerator, - private readonly InputTypeGenerator $inputTypeGenerator, - private readonly RecursiveTypeMapperInterface $recursiveTypeMapper, - private readonly ContainerInterface $container, - private readonly CacheInterface $cache, + private readonly NamingStrategyInterface $namingStrategy, + private readonly TypeRegistry $typeRegistry, + private readonly FieldsBuilder $fieldsBuilder, + private readonly TypeGenerator $typeGenerator, + private readonly InputTypeGenerator $inputTypeGenerator, + private readonly RecursiveTypeMapperInterface $recursiveTypeMapper, + private readonly ContainerInterface $container, + private readonly CacheInterface $cache, private readonly InputTypeValidatorInterface|null $inputTypeValidator, - private readonly ClassFinder $classFinder, - private readonly ClassFinderBoundCache $classFinderBoundCache, + private readonly ClassFinder $classFinder, + private readonly ClassFinderComputedCache $classFinderBoundCache, ) { } @@ -96,7 +96,7 @@ public function getClassFinder(): ClassFinder return $this->classFinder; } - public function getClassFinderBoundCache(): ClassFinderBoundCache + public function getClassFinderBoundCache(): ClassFinderComputedCache { return $this->classFinderBoundCache; } diff --git a/src/GlobControllerQueryProvider.php b/src/GlobControllerQueryProvider.php index 61a7b2f233..a01b572c8c 100644 --- a/src/GlobControllerQueryProvider.php +++ b/src/GlobControllerQueryProvider.php @@ -5,23 +5,15 @@ namespace TheCodingMachine\GraphQLite; use GraphQL\Type\Definition\FieldDefinition; -use InvalidArgumentException; use Psr\Container\ContainerInterface; -use Psr\SimpleCache\CacheInterface; use ReflectionClass; use ReflectionMethod; -use Symfony\Component\Cache\Adapter\Psr16Adapter; -use Symfony\Contracts\Cache\CacheInterface as CacheContractInterface; use TheCodingMachine\GraphQLite\Annotations\Mutation; use TheCodingMachine\GraphQLite\Annotations\Query; use TheCodingMachine\GraphQLite\Annotations\Subscription; use TheCodingMachine\GraphQLite\Discovery\ClassFinder; -use TheCodingMachine\GraphQLite\Discovery\Cache\ClassFinderBoundCache; -use function class_exists; -use function interface_exists; -use function is_array; -use function str_replace; +use TheCodingMachine\GraphQLite\Discovery\Cache\ClassFinderComputedCache; /** * Scans all the classes in a given namespace of the main project (not the vendor directory). @@ -39,11 +31,11 @@ final class GlobControllerQueryProvider implements QueryProviderInterface * @param ContainerInterface $container The container we will fetch controllers from. */ public function __construct( - private readonly FieldsBuilder $fieldsBuilder, - private readonly ContainerInterface $container, - private readonly AnnotationReader $annotationReader, - private readonly ClassFinder $classFinder, - private readonly ClassFinderBoundCache $classFinderBoundCache, + private readonly FieldsBuilder $fieldsBuilder, + private readonly ContainerInterface $container, + private readonly AnnotationReader $annotationReader, + private readonly ClassFinder $classFinder, + private readonly ClassFinderComputedCache $classFinderBoundCache, ) { } @@ -66,7 +58,7 @@ private function getAggregateControllerQueryProvider(): AggregateControllerQuery */ private function getClassList(): array { - $this->classList ??= $this->classFinderBoundCache->reduce( + $this->classList ??= $this->classFinderBoundCache->compute( $this->classFinder, 'globQueryProvider', function (ReflectionClass $classReflection): ?string { diff --git a/src/Mappers/ClassFinderTypeMapper.php b/src/Mappers/ClassFinderTypeMapper.php index 4c6642996a..0023b9f243 100644 --- a/src/Mappers/ClassFinderTypeMapper.php +++ b/src/Mappers/ClassFinderTypeMapper.php @@ -14,7 +14,7 @@ use ReflectionMethod; use TheCodingMachine\GraphQLite\AnnotationReader; use TheCodingMachine\GraphQLite\Discovery\ClassFinder; -use TheCodingMachine\GraphQLite\Discovery\Cache\ClassFinderBoundCache; +use TheCodingMachine\GraphQLite\Discovery\Cache\ClassFinderComputedCache; use TheCodingMachine\GraphQLite\InputTypeGenerator; use TheCodingMachine\GraphQLite\InputTypeUtils; use TheCodingMachine\GraphQLite\NamingStrategyInterface; @@ -42,7 +42,7 @@ public function __construct( private readonly AnnotationReader $annotationReader, private readonly NamingStrategyInterface $namingStrategy, private readonly RecursiveTypeMapperInterface $recursiveTypeMapper, - private readonly ClassFinderBoundCache $classFinderBoundCache, + private readonly ClassFinderComputedCache $classFinderBoundCache, ) { } @@ -52,7 +52,7 @@ public function __construct( */ private function getMaps(): GlobTypeMapperCache { - $this->globTypeMapperCache ??= $this->classFinderBoundCache->reduce( + $this->globTypeMapperCache ??= $this->classFinderBoundCache->compute( $this->classFinder, 'classToAnnotations', function (ReflectionClass $refClass): ?GlobAnnotationsCache { @@ -123,7 +123,7 @@ function (ReflectionClass $refClass): ?GlobAnnotationsCache { private function getMapClassToExtendTypeArray(): GlobExtendTypeMapperCache { - $this->globExtendTypeMapperCache ??= $this->classFinderBoundCache->reduce( + $this->globExtendTypeMapperCache ??= $this->classFinderBoundCache->compute( $this->classFinder, 'classToExtendAnnotations', function (ReflectionClass $refClass): ?GlobExtendAnnotationsCache { diff --git a/src/Mappers/Root/EnumTypeMapper.php b/src/Mappers/Root/EnumTypeMapper.php index 24cbf89d66..56cfa50bb0 100644 --- a/src/Mappers/Root/EnumTypeMapper.php +++ b/src/Mappers/Root/EnumTypeMapper.php @@ -18,7 +18,7 @@ use ReflectionProperty; use TheCodingMachine\GraphQLite\AnnotationReader; use TheCodingMachine\GraphQLite\Discovery\ClassFinder; -use TheCodingMachine\GraphQLite\Discovery\Cache\ClassFinderBoundCache; +use TheCodingMachine\GraphQLite\Discovery\Cache\ClassFinderComputedCache; use TheCodingMachine\GraphQLite\Reflection\DocBlock\DocBlockFactory; use TheCodingMachine\GraphQLite\Types\EnumType; use UnitEnum; @@ -39,11 +39,11 @@ class EnumTypeMapper implements RootTypeMapperInterface private array $nameToClassMapping; public function __construct( - private readonly RootTypeMapperInterface $next, - private readonly AnnotationReader $annotationReader, - private readonly DocBlockFactory $docBlockFactory, - private readonly ClassFinder $classFinder, - private readonly ClassFinderBoundCache $classFinderBoundCache, + private readonly RootTypeMapperInterface $next, + private readonly AnnotationReader $annotationReader, + private readonly DocBlockFactory $docBlockFactory, + private readonly ClassFinder $classFinder, + private readonly ClassFinderComputedCache $classFinderBoundCache, ) { } @@ -187,7 +187,7 @@ public function mapNameToType(string $typeName): NamedType&GraphQLType */ private function getNameToClassMapping(): array { - $this->nameToClassMapping ??= $this->classFinderBoundCache->reduce( + $this->nameToClassMapping ??= $this->classFinderBoundCache->compute( $this->classFinder, 'enum_name_to_class', function (ReflectionClass $classReflection): ?array { diff --git a/src/Mappers/Root/MyCLabsEnumTypeMapper.php b/src/Mappers/Root/MyCLabsEnumTypeMapper.php index 2b63adac9a..ef969c7089 100644 --- a/src/Mappers/Root/MyCLabsEnumTypeMapper.php +++ b/src/Mappers/Root/MyCLabsEnumTypeMapper.php @@ -18,7 +18,7 @@ use Symfony\Contracts\Cache\CacheInterface; use TheCodingMachine\GraphQLite\AnnotationReader; use TheCodingMachine\GraphQLite\Discovery\ClassFinder; -use TheCodingMachine\GraphQLite\Discovery\Cache\ClassFinderBoundCache; +use TheCodingMachine\GraphQLite\Discovery\Cache\ClassFinderComputedCache; use TheCodingMachine\GraphQLite\Types\MyCLabsEnumType; use function assert; @@ -39,10 +39,10 @@ class MyCLabsEnumTypeMapper implements RootTypeMapperInterface private array $nameToClassMapping; public function __construct( - private readonly RootTypeMapperInterface $next, - private readonly AnnotationReader $annotationReader, - private readonly ClassFinder $classFinder, - private readonly ClassFinderBoundCache $classFinderBoundCache, + private readonly RootTypeMapperInterface $next, + private readonly AnnotationReader $annotationReader, + private readonly ClassFinder $classFinder, + private readonly ClassFinderComputedCache $classFinderBoundCache, ) { } @@ -154,7 +154,7 @@ public function mapNameToType(string $typeName): NamedType&\GraphQL\Type\Definit */ private function getNameToClassMapping(): array { - $this->nameToClassMapping ??= $this->classFinderBoundCache->reduce( + $this->nameToClassMapping ??= $this->classFinderBoundCache->compute( $this->classFinder, 'myclabsenum_name_to_class', function (ReflectionClass $classReflection): ?array { diff --git a/src/Mappers/Root/RootTypeMapperFactoryContext.php b/src/Mappers/Root/RootTypeMapperFactoryContext.php index d21e175b53..54bb412881 100644 --- a/src/Mappers/Root/RootTypeMapperFactoryContext.php +++ b/src/Mappers/Root/RootTypeMapperFactoryContext.php @@ -8,7 +8,7 @@ use Psr\SimpleCache\CacheInterface; use TheCodingMachine\GraphQLite\AnnotationReader; use TheCodingMachine\GraphQLite\Discovery\ClassFinder; -use TheCodingMachine\GraphQLite\Discovery\Cache\ClassFinderBoundCache; +use TheCodingMachine\GraphQLite\Discovery\Cache\ClassFinderComputedCache; use TheCodingMachine\GraphQLite\Mappers\RecursiveTypeMapperInterface; use TheCodingMachine\GraphQLite\NamingStrategyInterface; use TheCodingMachine\GraphQLite\TypeRegistry; @@ -21,15 +21,15 @@ final class RootTypeMapperFactoryContext { public function __construct( - private readonly AnnotationReader $annotationReader, - private readonly TypeResolver $typeResolver, - private readonly NamingStrategyInterface $namingStrategy, - private readonly TypeRegistry $typeRegistry, + private readonly AnnotationReader $annotationReader, + private readonly TypeResolver $typeResolver, + private readonly NamingStrategyInterface $namingStrategy, + private readonly TypeRegistry $typeRegistry, private readonly RecursiveTypeMapperInterface $recursiveTypeMapper, - private readonly ContainerInterface $container, - private readonly CacheInterface $cache, - private readonly ClassFinder $classFinder, - private readonly ClassFinderBoundCache $classFinderBoundCache, + private readonly ContainerInterface $container, + private readonly CacheInterface $cache, + private readonly ClassFinder $classFinder, + private readonly ClassFinderComputedCache $classFinderBoundCache, ) { } @@ -73,7 +73,7 @@ public function getClassFinder(): ClassFinder return $this->classFinder; } - public function getClassFinderBoundCache(): ClassFinderBoundCache + public function getClassFinderBoundCache(): ClassFinderComputedCache { return $this->classFinderBoundCache; } diff --git a/src/SchemaFactory.php b/src/SchemaFactory.php index e37ecc7e2d..91c81f17d0 100644 --- a/src/SchemaFactory.php +++ b/src/SchemaFactory.php @@ -27,8 +27,8 @@ use TheCodingMachine\CacheUtils\ClassBoundCacheInterface; use TheCodingMachine\CacheUtils\ClassBoundMemoryAdapter; use TheCodingMachine\CacheUtils\FileBoundCache; -use TheCodingMachine\GraphQLite\Discovery\Cache\FileModificationClassFinderBoundCache; -use TheCodingMachine\GraphQLite\Discovery\Cache\HardClassFinderBoundCache; +use TheCodingMachine\GraphQLite\Discovery\Cache\FileModificationClassFinderComputedCache; +use TheCodingMachine\GraphQLite\Discovery\Cache\HardClassFinderComputedCache; use TheCodingMachine\GraphQLite\Discovery\ClassFinder; use TheCodingMachine\GraphQLite\Discovery\StaticClassFinder; use TheCodingMachine\GraphQLite\Discovery\KcsClassFinder; @@ -371,8 +371,8 @@ public function createSchema(): Schema $typeRegistry = new TypeRegistry(); $classFinder = $this->createClassFinder(); $classFinderBoundCache = $this->devMode ? - new FileModificationClassFinderBoundCache($this->cache) : - new HardClassFinderBoundCache($this->cache); + new FileModificationClassFinderComputedCache($this->cache) : + new HardClassFinderComputedCache($this->cache); $expressionLanguage = $this->expressionLanguage ?: new ExpressionLanguage($symfonyCache); $expressionLanguage->registerProvider(new SecurityExpressionLanguageProvider()); diff --git a/tests/AbstractQueryProvider.php b/tests/AbstractQueryProvider.php index e27e1b0b67..63ec690ec5 100644 --- a/tests/AbstractQueryProvider.php +++ b/tests/AbstractQueryProvider.php @@ -24,11 +24,10 @@ use TheCodingMachine\GraphQLite\Containers\BasicAutoWiringContainer; use TheCodingMachine\GraphQLite\Containers\EmptyContainer; use TheCodingMachine\GraphQLite\Containers\LazyContainer; -use TheCodingMachine\GraphQLite\Discovery\Cache\HardClassFinderBoundCache; +use TheCodingMachine\GraphQLite\Discovery\Cache\HardClassFinderComputedCache; use TheCodingMachine\GraphQLite\Discovery\ClassFinder; use TheCodingMachine\GraphQLite\Discovery\StaticClassFinder; use TheCodingMachine\GraphQLite\Discovery\KcsClassFinder; -use TheCodingMachine\GraphQLite\Discovery\OldCachedClassFinder; use TheCodingMachine\GraphQLite\Fixtures\Mocks\MockResolvableInputObjectType; use TheCodingMachine\GraphQLite\Fixtures\TestObject; use TheCodingMachine\GraphQLite\Fixtures\TestObject2; @@ -52,7 +51,6 @@ use TheCodingMachine\GraphQLite\Middlewares\AuthorizationFieldMiddleware; use TheCodingMachine\GraphQLite\Middlewares\FieldMiddlewarePipe; use TheCodingMachine\GraphQLite\Middlewares\InputFieldMiddlewarePipe; -use TheCodingMachine\GraphQLite\Middlewares\PrefetchFieldMiddleware; use TheCodingMachine\GraphQLite\Middlewares\SecurityFieldMiddleware; use TheCodingMachine\GraphQLite\Reflection\DocBlock\CachedDocBlockContextFactory; use TheCodingMachine\GraphQLite\Reflection\DocBlock\CachedDocBlockFactory; @@ -68,8 +66,6 @@ use TheCodingMachine\GraphQLite\Types\MutableObjectType; use TheCodingMachine\GraphQLite\Types\ResolvableMutableInputInterface; use TheCodingMachine\GraphQLite\Types\TypeResolver; -use TheCodingMachine\GraphQLite\Utils\Namespaces\NamespaceFactory; -use Traversable; abstract class AbstractQueryProvider extends TestCase { @@ -88,7 +84,6 @@ abstract class AbstractQueryProvider extends TestCase private $typeRegistry; private $parameterMiddlewarePipe; private $rootTypeMapper; - private $namespaceFactory; protected function getTestObjectType(): MutableObjectType { @@ -403,7 +398,7 @@ protected function buildRootTypeMapper(): RootTypeMapperInterface $rootTypeMapper, $this->getAnnotationReader(), new StaticClassFinder([]), - new HardClassFinderBoundCache(new Psr16Cache($arrayAdapter)), + new HardClassFinderComputedCache(new Psr16Cache($arrayAdapter)), ); $rootTypeMapper = new EnumTypeMapper( @@ -411,7 +406,7 @@ protected function buildRootTypeMapper(): RootTypeMapperInterface $this->getAnnotationReader(), $this->getDocBlockFactory(), new StaticClassFinder([]), - new HardClassFinderBoundCache(new Psr16Cache($arrayAdapter)), + new HardClassFinderComputedCache(new Psr16Cache($arrayAdapter)), ); $rootTypeMapper = new CompoundTypeMapper( diff --git a/tests/GlobControllerQueryProviderTest.php b/tests/GlobControllerQueryProviderTest.php index bda8e4c02b..da72610c7b 100644 --- a/tests/GlobControllerQueryProviderTest.php +++ b/tests/GlobControllerQueryProviderTest.php @@ -9,7 +9,7 @@ use ReflectionClass; use Symfony\Component\Cache\Adapter\NullAdapter; use Symfony\Component\Cache\Psr16Cache; -use TheCodingMachine\GraphQLite\Discovery\Cache\HardClassFinderBoundCache; +use TheCodingMachine\GraphQLite\Discovery\Cache\HardClassFinderComputedCache; use TheCodingMachine\GraphQLite\Discovery\KcsClassFinder; use TheCodingMachine\GraphQLite\Fixtures\TestController; @@ -47,7 +47,7 @@ public function has($id): bool $container, $this->getAnnotationReader(), new KcsClassFinder($finder), - new HardClassFinderBoundCache(new Psr16Cache(new NullAdapter())) + new HardClassFinderComputedCache(new Psr16Cache(new NullAdapter())) ); $queries = $globControllerQueryProvider->getQueries(); diff --git a/tests/Integration/IntegrationTestCase.php b/tests/Integration/IntegrationTestCase.php index 3ca8a4f10d..de38731dfb 100644 --- a/tests/Integration/IntegrationTestCase.php +++ b/tests/Integration/IntegrationTestCase.php @@ -22,11 +22,10 @@ use TheCodingMachine\GraphQLite\Containers\BasicAutoWiringContainer; use TheCodingMachine\GraphQLite\Containers\EmptyContainer; use TheCodingMachine\GraphQLite\Containers\LazyContainer; -use TheCodingMachine\GraphQLite\Discovery\Cache\ClassFinderBoundCache; -use TheCodingMachine\GraphQLite\Discovery\Cache\HardClassFinderBoundCache; +use TheCodingMachine\GraphQLite\Discovery\Cache\ClassFinderComputedCache; +use TheCodingMachine\GraphQLite\Discovery\Cache\HardClassFinderComputedCache; use TheCodingMachine\GraphQLite\Discovery\ClassFinder; use TheCodingMachine\GraphQLite\Discovery\KcsClassFinder; -use TheCodingMachine\GraphQLite\Discovery\OldCachedClassFinder; use TheCodingMachine\GraphQLite\FieldsBuilder; use TheCodingMachine\GraphQLite\GlobControllerQueryProvider; use TheCodingMachine\GraphQLite\InputTypeGenerator; @@ -34,7 +33,6 @@ use TheCodingMachine\GraphQLite\Loggers\ExceptionLogger; use TheCodingMachine\GraphQLite\Mappers\ClassFinderTypeMapper; use TheCodingMachine\GraphQLite\Mappers\CompositeTypeMapper; -use TheCodingMachine\GraphQLite\Mappers\GlobTypeMapper; use TheCodingMachine\GraphQLite\Mappers\Parameters\ContainerParameterHandler; use TheCodingMachine\GraphQLite\Mappers\Parameters\InjectUserParameterHandler; use TheCodingMachine\GraphQLite\Mappers\Parameters\ParameterMiddlewareInterface; @@ -109,8 +107,8 @@ public function createContainer(array $overloadedServices = []): ContainerInterf return new KcsClassFinder($composerFinder); }, - ClassFinderBoundCache::class => function () { - return new HardClassFinderBoundCache( + ClassFinderComputedCache::class => function () { + return new HardClassFinderComputedCache( new Psr16Cache(new ArrayAdapter()), ); }, @@ -120,7 +118,7 @@ public function createContainer(array $overloadedServices = []): ContainerInterf $container->get(BasicAutoWiringContainer::class), $container->get(AnnotationReader::class), $container->get(ClassFinder::class), - $container->get(ClassFinderBoundCache::class), + $container->get(ClassFinderComputedCache::class), ); $queryProvider = new AggregateQueryProvider([ @@ -130,7 +128,7 @@ public function createContainer(array $overloadedServices = []): ContainerInterf $container->get(BasicAutoWiringContainer::class), $container->get(AnnotationReader::class), $container->get(ClassFinder::class), - $container->get(ClassFinderBoundCache::class), + $container->get(ClassFinderComputedCache::class), ), ]); @@ -238,7 +236,7 @@ public function createContainer(array $overloadedServices = []): ContainerInterf $container->get(AnnotationReader::class), $container->get(NamingStrategyInterface::class), $container->get(RecursiveTypeMapperInterface::class), - $container->get(ClassFinderBoundCache::class), + $container->get(ClassFinderComputedCache::class), ); }, PorpaginasTypeMapper::class => static function (ContainerInterface $container) { @@ -249,7 +247,7 @@ public function createContainer(array $overloadedServices = []): ContainerInterf $container->get(RootTypeMapperInterface::class), $container->get(AnnotationReader::class), $container->get(ClassFinder::class), - $container->get(ClassFinderBoundCache::class), + $container->get(ClassFinderComputedCache::class), ); }, TypeGenerator::class => static function (ContainerInterface $container) { @@ -328,8 +326,8 @@ public function createContainer(array $overloadedServices = []): ContainerInterf // These are in reverse order of execution $errorRootTypeMapper = new FinalRootTypeMapper($container->get(RecursiveTypeMapperInterface::class)); $rootTypeMapper = new BaseTypeMapper($errorRootTypeMapper, $container->get(RecursiveTypeMapperInterface::class), $container->get(RootTypeMapperInterface::class)); - $rootTypeMapper = new MyCLabsEnumTypeMapper($rootTypeMapper, $container->get(AnnotationReader::class), $container->get(ClassFinder::class), $container->get(ClassFinderBoundCache::class)); - $rootTypeMapper = new EnumTypeMapper($rootTypeMapper, $container->get(AnnotationReader::class), $container->get(DocBlockFactory::class), $container->get(ClassFinder::class), $container->get(ClassFinderBoundCache::class)); + $rootTypeMapper = new MyCLabsEnumTypeMapper($rootTypeMapper, $container->get(AnnotationReader::class), $container->get(ClassFinder::class), $container->get(ClassFinderComputedCache::class)); + $rootTypeMapper = new EnumTypeMapper($rootTypeMapper, $container->get(AnnotationReader::class), $container->get(DocBlockFactory::class), $container->get(ClassFinder::class), $container->get(ClassFinderComputedCache::class)); $rootTypeMapper = new CompoundTypeMapper($rootTypeMapper, $container->get(RootTypeMapperInterface::class), $container->get(NamingStrategyInterface::class), $container->get(TypeRegistry::class), $container->get(RecursiveTypeMapperInterface::class)); $rootTypeMapper = new IteratorTypeMapper($rootTypeMapper, $container->get(RootTypeMapperInterface::class)); return $rootTypeMapper; From d52b18a382520d782d3244f699957bc6ad79bd4a Mon Sep 17 00:00:00 2001 From: Oleksandr Prypkhan Date: Tue, 27 Aug 2024 01:15:27 +0300 Subject: [PATCH 11/28] Code style --- .../Cache/ClassFinderComputedCache.php | 10 +++--- ...leModificationClassFinderComputedCache.php | 27 +++++++------- .../Cache/HardClassFinderComputedCache.php | 17 ++++----- src/Discovery/ClassFinder.php | 13 ++++--- src/Discovery/KcsClassFinder.php | 6 +++- src/Discovery/StaticClassFinder.php | 14 ++++---- src/FactoryContext.php | 8 ++--- src/FieldsBuilder.php | 22 ++++++------ src/GlobControllerQueryProvider.php | 22 ++++++------ src/Mappers/ClassFinderTypeMapper.php | 32 +++++++++-------- src/Mappers/GlobExtendAnnotationsCache.php | 4 +-- src/Mappers/Parameters/TypeHandler.php | 6 ++-- src/Mappers/Root/EnumTypeMapper.php | 36 ++++++++++--------- src/Mappers/Root/MyCLabsEnumTypeMapper.php | 16 +++++---- .../Root/RootTypeMapperFactoryContext.php | 18 +++++----- .../DocBlock/CachedDocBlockContextFactory.php | 6 ++-- .../DocBlock/CachedDocBlockFactory.php | 7 ++-- .../DocBlock/DocBlockContextFactory.php | 4 ++- src/Reflection/DocBlock/DocBlockFactory.php | 5 +-- .../PhpDocumentorDocBlockContextFactory.php | 4 ++- .../DocBlock/PhpDocumentorDocBlockFactory.php | 4 ++- src/SchemaFactory.php | 22 +++++++----- 22 files changed, 171 insertions(+), 132 deletions(-) diff --git a/src/Discovery/Cache/ClassFinderComputedCache.php b/src/Discovery/Cache/ClassFinderComputedCache.php index ee1e682e07..66f7fd05c2 100644 --- a/src/Discovery/Cache/ClassFinderComputedCache.php +++ b/src/Discovery/Cache/ClassFinderComputedCache.php @@ -1,5 +1,7 @@ ): TEntry $map * @param callable(array): TReturn $reduce * * @return TReturn + * + * @template TEntry of mixed + * @template TReturn of mixed */ public function compute( ClassFinder $classFinder, @@ -36,4 +38,4 @@ public function compute( callable $map, callable $reduce, ): mixed; -} \ No newline at end of file +} diff --git a/src/Discovery/Cache/FileModificationClassFinderComputedCache.php b/src/Discovery/Cache/FileModificationClassFinderComputedCache.php index b1fdd2c9d1..dfcba30be5 100644 --- a/src/Discovery/Cache/FileModificationClassFinderComputedCache.php +++ b/src/Discovery/Cache/FileModificationClassFinderComputedCache.php @@ -1,10 +1,14 @@ ): TEntry $map * @param callable(array): TReturn $reduce * * @return TReturn + * + * @template TEntry of mixed + * @template TReturn of mixed */ public function compute( ClassFinder $classFinder, @@ -47,17 +51,17 @@ public function compute( callable $reduce, ): mixed { - $entries = $this->entries($classFinder, "$key.entries", $map); + $entries = $this->entries($classFinder, $key . '.entries', $map); return $reduce($entries); } /** - * @template TEntry of mixed - * * @param callable(ReflectionClass): TEntry $map * * @return array + * + * @template TEntry of mixed */ private function entries( ClassFinder $classFinder, @@ -78,7 +82,7 @@ private function entries( // If there's no entry in cache for this filename (new file or previously uncached), // or if it the file has been modified since caching, we'll try to autoload // the class and collect the cached information (again). - if (!$entry || $this->dependenciesChanged($entry['dependencies'])) { + if (! $entry || $this->dependenciesChanged($entry['dependencies'])) { // In case this file isn't a class, or doesn't match the provided namespace filter, // it will not be emitted in the iterator and won't reach the `foreach()` below. // So to avoid iterating over these files again, we'll mark them as non-matching. @@ -122,9 +126,7 @@ private function entries( return $result; } - /** - * @return array - */ + /** @return array */ private function fileDependencies(ReflectionClass $refClass): array { $filename = $refClass->getFileName(); @@ -150,6 +152,7 @@ private function fileDependencies(ReflectionClass $refClass): array return $files; } + /** @param array $files */ private function dependenciesChanged(array $files): bool { foreach ($files as $filename => $modificationTime) { @@ -160,4 +163,4 @@ private function dependenciesChanged(array $files): bool return false; } -} \ No newline at end of file +} diff --git a/src/Discovery/Cache/HardClassFinderComputedCache.php b/src/Discovery/Cache/HardClassFinderComputedCache.php index 46ac703d71..da981a8e3d 100644 --- a/src/Discovery/Cache/HardClassFinderComputedCache.php +++ b/src/Discovery/Cache/HardClassFinderComputedCache.php @@ -1,14 +1,15 @@ ): TEntry $map * @param callable(array): TReturn $reduce * * @return TReturn + * + * @template TEntry of mixed + * @template TReturn of mixed */ public function compute( ClassFinder $classFinder, @@ -45,11 +46,11 @@ public function compute( } /** - * @template TEntry of mixed - * * @param callable(ReflectionClass): TEntry $map * * @return array + * + * @template TEntry of mixed */ private function entries( ClassFinder $classFinder, @@ -64,4 +65,4 @@ private function entries( return $entries; } -} \ No newline at end of file +} diff --git a/src/Discovery/ClassFinder.php b/src/Discovery/ClassFinder.php index 90c855c72f..67d2cf3b1a 100644 --- a/src/Discovery/ClassFinder.php +++ b/src/Discovery/ClassFinder.php @@ -1,11 +1,14 @@ > - */ -interface ClassFinder extends \IteratorAggregate +use IteratorAggregate; +use ReflectionClass; + +/** @extends IteratorAggregate> */ +interface ClassFinder extends IteratorAggregate { public function withPathFilter(callable $filter): self; -} \ No newline at end of file +} diff --git a/src/Discovery/KcsClassFinder.php b/src/Discovery/KcsClassFinder.php index 70f01476fd..d54616b8ec 100644 --- a/src/Discovery/KcsClassFinder.php +++ b/src/Discovery/KcsClassFinder.php @@ -1,8 +1,11 @@ */ public function getIterator(): Traversable { return $this->finder->getIterator(); } -} \ No newline at end of file +} diff --git a/src/Discovery/StaticClassFinder.php b/src/Discovery/StaticClassFinder.php index 94f01a02f4..60f078cbac 100644 --- a/src/Discovery/StaticClassFinder.php +++ b/src/Discovery/StaticClassFinder.php @@ -1,7 +1,10 @@ $classes - */ + /** @param array $classes */ public function __construct( private readonly array $classes, ) @@ -26,16 +27,17 @@ public function withPathFilter(callable $filter): ClassFinder return $that; } + /** @return Traversable */ public function getIterator(): Traversable { foreach ($this->classes as $class) { - $classReflection = new \ReflectionClass($class); + $classReflection = new ReflectionClass($class); - if ($this->pathFilter && !($this->pathFilter)($classReflection->getFileName())) { + if ($this->pathFilter && ! ($this->pathFilter)($classReflection->getFileName())) { continue; } yield $class => $classReflection; } } -} \ No newline at end of file +} diff --git a/src/FactoryContext.php b/src/FactoryContext.php index 8e95f31feb..8573530d7f 100644 --- a/src/FactoryContext.php +++ b/src/FactoryContext.php @@ -6,9 +6,9 @@ use Psr\Container\ContainerInterface; use Psr\SimpleCache\CacheInterface; -use TheCodingMachine\GraphQLite\Discovery\ClassFinder; -use TheCodingMachine\GraphQLite\Discovery\Cache\ClassFinderComputedCache; use TheCodingMachine\GraphQLite\Cache\ClassBoundCacheContractFactoryInterface; +use TheCodingMachine\GraphQLite\Discovery\Cache\ClassFinderComputedCache; +use TheCodingMachine\GraphQLite\Discovery\ClassFinder; use TheCodingMachine\GraphQLite\Mappers\RecursiveTypeMapperInterface; use TheCodingMachine\GraphQLite\Types\InputTypeValidatorInterface; use TheCodingMachine\GraphQLite\Types\TypeResolver; @@ -32,8 +32,8 @@ public function __construct( private readonly ContainerInterface $container, private readonly CacheInterface $cache, private readonly InputTypeValidatorInterface|null $inputTypeValidator, - private readonly ClassFinder $classFinder, - private readonly ClassFinderComputedCache $classFinderBoundCache, + private readonly ClassFinder $classFinder, + private readonly ClassFinderComputedCache $classFinderBoundCache, private readonly ClassBoundCacheContractFactoryInterface|null $classBoundCacheContractFactory = null, ) { } diff --git a/src/FieldsBuilder.php b/src/FieldsBuilder.php index 12e7c63eac..3708c3426d 100644 --- a/src/FieldsBuilder.php +++ b/src/FieldsBuilder.php @@ -52,6 +52,7 @@ use TheCodingMachine\GraphQLite\Types\MutableObjectType; use TheCodingMachine\GraphQLite\Types\TypeResolver; use TheCodingMachine\GraphQLite\Utils\PropertyAccessor; + use function array_diff_key; use function array_fill_keys; use function array_intersect_key; @@ -69,6 +70,7 @@ use function rtrim; use function str_starts_with; use function trim; + use const PHP_EOL; /** @@ -79,16 +81,16 @@ class FieldsBuilder private TypeHandler $typeMapper; public function __construct( - private readonly AnnotationReader $annotationReader, - private readonly RecursiveTypeMapperInterface $recursiveTypeMapper, - private readonly ArgumentResolver $argumentResolver, - private readonly TypeResolver $typeResolver, - private readonly DocBlockFactory $docBlockFactory, - private readonly DocBlockContextFactory $docBlockContextFactory, - private readonly NamingStrategyInterface $namingStrategy, - private readonly RootTypeMapperInterface $rootTypeMapper, - private readonly ParameterMiddlewareInterface $parameterMapper, - private readonly FieldMiddlewareInterface $fieldMiddleware, + private readonly AnnotationReader $annotationReader, + private readonly RecursiveTypeMapperInterface $recursiveTypeMapper, + private readonly ArgumentResolver $argumentResolver, + private readonly TypeResolver $typeResolver, + private readonly DocBlockFactory $docBlockFactory, + private readonly DocBlockContextFactory $docBlockContextFactory, + private readonly NamingStrategyInterface $namingStrategy, + private readonly RootTypeMapperInterface $rootTypeMapper, + private readonly ParameterMiddlewareInterface $parameterMapper, + private readonly FieldMiddlewareInterface $fieldMiddleware, private readonly InputFieldMiddlewareInterface $inputFieldMiddleware, ) { diff --git a/src/GlobControllerQueryProvider.php b/src/GlobControllerQueryProvider.php index a01b572c8c..c4f9ef5a17 100644 --- a/src/GlobControllerQueryProvider.php +++ b/src/GlobControllerQueryProvider.php @@ -11,9 +11,11 @@ use TheCodingMachine\GraphQLite\Annotations\Mutation; use TheCodingMachine\GraphQLite\Annotations\Query; use TheCodingMachine\GraphQLite\Annotations\Subscription; - -use TheCodingMachine\GraphQLite\Discovery\ClassFinder; use TheCodingMachine\GraphQLite\Discovery\Cache\ClassFinderComputedCache; +use TheCodingMachine\GraphQLite\Discovery\ClassFinder; + +use function array_filter; +use function array_values; /** * Scans all the classes in a given namespace of the main project (not the vendor directory). @@ -27,14 +29,12 @@ final class GlobControllerQueryProvider implements QueryProviderInterface private array $classList; private AggregateControllerQueryProvider|null $aggregateControllerQueryProvider = null; - /** - * @param ContainerInterface $container The container we will fetch controllers from. - */ + /** @param ContainerInterface $container The container we will fetch controllers from. */ public function __construct( - private readonly FieldsBuilder $fieldsBuilder, - private readonly ContainerInterface $container, - private readonly AnnotationReader $annotationReader, - private readonly ClassFinder $classFinder, + private readonly FieldsBuilder $fieldsBuilder, + private readonly ContainerInterface $container, + private readonly AnnotationReader $annotationReader, + private readonly ClassFinder $classFinder, private readonly ClassFinderComputedCache $classFinderBoundCache, ) { @@ -61,7 +61,7 @@ private function getClassList(): array $this->classList ??= $this->classFinderBoundCache->compute( $this->classFinder, 'globQueryProvider', - function (ReflectionClass $classReflection): ?string { + function (ReflectionClass $classReflection): string|null { if ( ! $classReflection->isInstantiable() || ! $this->hasOperations($classReflection) || @@ -72,7 +72,7 @@ function (ReflectionClass $classReflection): ?string { return $classReflection->getName(); }, - fn (array $entries) => array_values(array_filter($entries)), + static fn (array $entries) => array_values(array_filter($entries)), ); return $this->classList; diff --git a/src/Mappers/ClassFinderTypeMapper.php b/src/Mappers/ClassFinderTypeMapper.php index 0023b9f243..0a9960207e 100644 --- a/src/Mappers/ClassFinderTypeMapper.php +++ b/src/Mappers/ClassFinderTypeMapper.php @@ -13,8 +13,8 @@ use ReflectionException; use ReflectionMethod; use TheCodingMachine\GraphQLite\AnnotationReader; -use TheCodingMachine\GraphQLite\Discovery\ClassFinder; use TheCodingMachine\GraphQLite\Discovery\Cache\ClassFinderComputedCache; +use TheCodingMachine\GraphQLite\Discovery\ClassFinder; use TheCodingMachine\GraphQLite\InputTypeGenerator; use TheCodingMachine\GraphQLite\InputTypeUtils; use TheCodingMachine\GraphQLite\NamingStrategyInterface; @@ -23,6 +23,8 @@ use TheCodingMachine\GraphQLite\Types\MutableInterfaceType; use TheCodingMachine\GraphQLite\Types\MutableObjectType; use TheCodingMachine\GraphQLite\Types\ResolvableMutableInputInterface; + +use function array_reduce; use function assert; /** @@ -34,15 +36,15 @@ class ClassFinderTypeMapper implements TypeMapperInterface private GlobExtendTypeMapperCache|null $globExtendTypeMapperCache = null; public function __construct( - private readonly ClassFinder $classFinder, - private readonly TypeGenerator $typeGenerator, - private readonly InputTypeGenerator $inputTypeGenerator, - private readonly InputTypeUtils $inputTypeUtils, - private readonly ContainerInterface $container, - private readonly AnnotationReader $annotationReader, - private readonly NamingStrategyInterface $namingStrategy, + private readonly ClassFinder $classFinder, + private readonly TypeGenerator $typeGenerator, + private readonly InputTypeGenerator $inputTypeGenerator, + private readonly InputTypeUtils $inputTypeUtils, + private readonly ContainerInterface $container, + private readonly AnnotationReader $annotationReader, + private readonly NamingStrategyInterface $namingStrategy, private readonly RecursiveTypeMapperInterface $recursiveTypeMapper, - private readonly ClassFinderComputedCache $classFinderBoundCache, + private readonly ClassFinderComputedCache $classFinderBoundCache, ) { } @@ -55,7 +57,7 @@ private function getMaps(): GlobTypeMapperCache $this->globTypeMapperCache ??= $this->classFinderBoundCache->compute( $this->classFinder, 'classToAnnotations', - function (ReflectionClass $refClass): ?GlobAnnotationsCache { + function (ReflectionClass $refClass): GlobAnnotationsCache|null { if ($refClass->isEnum()) { return null; } @@ -107,7 +109,7 @@ function (ReflectionClass $refClass): ?GlobAnnotationsCache { return $containsAnnotations ? $annotationsCache : null; }, - fn (array $entries) => array_reduce($entries, function (GlobTypeMapperCache $globTypeMapperCache, ?GlobAnnotationsCache $annotationsCache) { + static fn (array $entries) => array_reduce($entries, static function (GlobTypeMapperCache $globTypeMapperCache, GlobAnnotationsCache|null $annotationsCache) { if ($annotationsCache === null) { return $globTypeMapperCache; } @@ -115,7 +117,7 @@ function (ReflectionClass $refClass): ?GlobAnnotationsCache { $globTypeMapperCache->registerAnnotations($annotationsCache->sourceClass, $annotationsCache); return $globTypeMapperCache; - }, new GlobTypeMapperCache()) + }, new GlobTypeMapperCache()), ); return $this->globTypeMapperCache; @@ -126,7 +128,7 @@ private function getMapClassToExtendTypeArray(): GlobExtendTypeMapperCache $this->globExtendTypeMapperCache ??= $this->classFinderBoundCache->compute( $this->classFinder, 'classToExtendAnnotations', - function (ReflectionClass $refClass): ?GlobExtendAnnotationsCache { + function (ReflectionClass $refClass): GlobExtendAnnotationsCache|null { // Enum's are not types if ($refClass->isEnum()) { return null; @@ -160,7 +162,7 @@ function (ReflectionClass $refClass): ?GlobExtendAnnotationsCache { // FIXME: $extendClassName === NULL!!!!!! return new GlobExtendAnnotationsCache($refClass->getName(), $extendClassName, $typeName); }, - fn (array $entries) => array_reduce($entries, function (GlobExtendTypeMapperCache $globExtendTypeMapperCache, ?GlobExtendAnnotationsCache $annotationsCache) { + static fn (array $entries) => array_reduce($entries, static function (GlobExtendTypeMapperCache $globExtendTypeMapperCache, GlobExtendAnnotationsCache|null $annotationsCache) { if ($annotationsCache === null) { return $globExtendTypeMapperCache; } @@ -168,7 +170,7 @@ function (ReflectionClass $refClass): ?GlobExtendAnnotationsCache { $globExtendTypeMapperCache->registerAnnotations($annotationsCache->sourceClass, $annotationsCache); return $globExtendTypeMapperCache; - }, new GlobExtendTypeMapperCache()) + }, new GlobExtendTypeMapperCache()), ); return $this->globExtendTypeMapperCache; diff --git a/src/Mappers/GlobExtendAnnotationsCache.php b/src/Mappers/GlobExtendAnnotationsCache.php index b2063049f0..ca62e9536a 100644 --- a/src/Mappers/GlobExtendAnnotationsCache.php +++ b/src/Mappers/GlobExtendAnnotationsCache.php @@ -11,9 +11,7 @@ */ final class GlobExtendAnnotationsCache { - /** - * @param class-string $sourceClass - */ + /** @param class-string $sourceClass */ public function __construct( public readonly string $sourceClass, private string|null $extendTypeClassName, diff --git a/src/Mappers/Parameters/TypeHandler.php b/src/Mappers/Parameters/TypeHandler.php index 27ae6a4675..fc68dad311 100644 --- a/src/Mappers/Parameters/TypeHandler.php +++ b/src/Mappers/Parameters/TypeHandler.php @@ -65,10 +65,10 @@ class TypeHandler implements ParameterHandlerInterface private PhpDocumentorTypeResolver $phpDocumentorTypeResolver; public function __construct( - private readonly ArgumentResolver $argumentResolver, + private readonly ArgumentResolver $argumentResolver, private readonly RootTypeMapperInterface $rootTypeMapper, - private readonly TypeResolver $typeResolver, - private readonly DocBlockFactory $docBlockFactory, + private readonly TypeResolver $typeResolver, + private readonly DocBlockFactory $docBlockFactory, ) { $this->phpDocumentorTypeResolver = new PhpDocumentorTypeResolver(); diff --git a/src/Mappers/Root/EnumTypeMapper.php b/src/Mappers/Root/EnumTypeMapper.php index 56cfa50bb0..130b285289 100644 --- a/src/Mappers/Root/EnumTypeMapper.php +++ b/src/Mappers/Root/EnumTypeMapper.php @@ -17,11 +17,15 @@ use ReflectionMethod; use ReflectionProperty; use TheCodingMachine\GraphQLite\AnnotationReader; -use TheCodingMachine\GraphQLite\Discovery\ClassFinder; use TheCodingMachine\GraphQLite\Discovery\Cache\ClassFinderComputedCache; +use TheCodingMachine\GraphQLite\Discovery\ClassFinder; use TheCodingMachine\GraphQLite\Reflection\DocBlock\DocBlockFactory; use TheCodingMachine\GraphQLite\Types\EnumType; use UnitEnum; + +use function array_filter; +use function array_merge; +use function array_values; use function assert; use function enum_exists; use function ltrim; @@ -39,10 +43,10 @@ class EnumTypeMapper implements RootTypeMapperInterface private array $nameToClassMapping; public function __construct( - private readonly RootTypeMapperInterface $next, - private readonly AnnotationReader $annotationReader, - private readonly DocBlockFactory $docBlockFactory, - private readonly ClassFinder $classFinder, + private readonly RootTypeMapperInterface $next, + private readonly AnnotationReader $annotationReader, + private readonly DocBlockFactory $docBlockFactory, + private readonly ClassFinder $classFinder, private readonly ClassFinderComputedCache $classFinderBoundCache, ) { } @@ -188,17 +192,17 @@ public function mapNameToType(string $typeName): NamedType&GraphQLType private function getNameToClassMapping(): array { $this->nameToClassMapping ??= $this->classFinderBoundCache->compute( - $this->classFinder, - 'enum_name_to_class', - function (ReflectionClass $classReflection): ?array { - if (! $classReflection->isEnum()) { - return null; - } - - return [$this->getTypeName($classReflection) => $classReflection->getName()]; - }, - fn (array $entries) => array_merge(...array_values(array_filter($entries))), - ); + $this->classFinder, + 'enum_name_to_class', + function (ReflectionClass $classReflection): array|null { + if (! $classReflection->isEnum()) { + return null; + } + + return [$this->getTypeName($classReflection) => $classReflection->getName()]; + }, + static fn (array $entries) => array_merge(...array_values(array_filter($entries))), + ); return $this->nameToClassMapping; } diff --git a/src/Mappers/Root/MyCLabsEnumTypeMapper.php b/src/Mappers/Root/MyCLabsEnumTypeMapper.php index ef969c7089..c2bc80abdc 100644 --- a/src/Mappers/Root/MyCLabsEnumTypeMapper.php +++ b/src/Mappers/Root/MyCLabsEnumTypeMapper.php @@ -15,12 +15,14 @@ use ReflectionClass; use ReflectionMethod; use ReflectionProperty; -use Symfony\Contracts\Cache\CacheInterface; use TheCodingMachine\GraphQLite\AnnotationReader; -use TheCodingMachine\GraphQLite\Discovery\ClassFinder; use TheCodingMachine\GraphQLite\Discovery\Cache\ClassFinderComputedCache; +use TheCodingMachine\GraphQLite\Discovery\ClassFinder; use TheCodingMachine\GraphQLite\Types\MyCLabsEnumType; +use function array_filter; +use function array_merge; +use function array_values; use function assert; use function is_a; use function ltrim; @@ -39,9 +41,9 @@ class MyCLabsEnumTypeMapper implements RootTypeMapperInterface private array $nameToClassMapping; public function __construct( - private readonly RootTypeMapperInterface $next, - private readonly AnnotationReader $annotationReader, - private readonly ClassFinder $classFinder, + private readonly RootTypeMapperInterface $next, + private readonly AnnotationReader $annotationReader, + private readonly ClassFinder $classFinder, private readonly ClassFinderComputedCache $classFinderBoundCache, ) { } @@ -157,14 +159,14 @@ private function getNameToClassMapping(): array $this->nameToClassMapping ??= $this->classFinderBoundCache->compute( $this->classFinder, 'myclabsenum_name_to_class', - function (ReflectionClass $classReflection): ?array { + function (ReflectionClass $classReflection): array|null { if (! $classReflection->isSubclassOf(Enum::class)) { return null; } return [$this->getTypeName($classReflection) => $classReflection->getName()]; }, - fn (array $entries) => array_merge(...array_values(array_filter($entries))), + static fn (array $entries) => array_merge(...array_values(array_filter($entries))), ); return $this->nameToClassMapping; diff --git a/src/Mappers/Root/RootTypeMapperFactoryContext.php b/src/Mappers/Root/RootTypeMapperFactoryContext.php index 54bb412881..90057a60b4 100644 --- a/src/Mappers/Root/RootTypeMapperFactoryContext.php +++ b/src/Mappers/Root/RootTypeMapperFactoryContext.php @@ -7,8 +7,8 @@ use Psr\Container\ContainerInterface; use Psr\SimpleCache\CacheInterface; use TheCodingMachine\GraphQLite\AnnotationReader; -use TheCodingMachine\GraphQLite\Discovery\ClassFinder; use TheCodingMachine\GraphQLite\Discovery\Cache\ClassFinderComputedCache; +use TheCodingMachine\GraphQLite\Discovery\ClassFinder; use TheCodingMachine\GraphQLite\Mappers\RecursiveTypeMapperInterface; use TheCodingMachine\GraphQLite\NamingStrategyInterface; use TheCodingMachine\GraphQLite\TypeRegistry; @@ -21,15 +21,15 @@ final class RootTypeMapperFactoryContext { public function __construct( - private readonly AnnotationReader $annotationReader, - private readonly TypeResolver $typeResolver, - private readonly NamingStrategyInterface $namingStrategy, - private readonly TypeRegistry $typeRegistry, + private readonly AnnotationReader $annotationReader, + private readonly TypeResolver $typeResolver, + private readonly NamingStrategyInterface $namingStrategy, + private readonly TypeRegistry $typeRegistry, private readonly RecursiveTypeMapperInterface $recursiveTypeMapper, - private readonly ContainerInterface $container, - private readonly CacheInterface $cache, - private readonly ClassFinder $classFinder, - private readonly ClassFinderComputedCache $classFinderBoundCache, + private readonly ContainerInterface $container, + private readonly CacheInterface $cache, + private readonly ClassFinder $classFinder, + private readonly ClassFinderComputedCache $classFinderBoundCache, ) { } diff --git a/src/Reflection/DocBlock/CachedDocBlockContextFactory.php b/src/Reflection/DocBlock/CachedDocBlockContextFactory.php index 46ac64a7b6..21c988d448 100644 --- a/src/Reflection/DocBlock/CachedDocBlockContextFactory.php +++ b/src/Reflection/DocBlock/CachedDocBlockContextFactory.php @@ -1,5 +1,7 @@ getDeclaringClass(); diff --git a/src/Reflection/DocBlock/DocBlockContextFactory.php b/src/Reflection/DocBlock/DocBlockContextFactory.php index 9e6c65c68d..ab8b0153a3 100644 --- a/src/Reflection/DocBlock/DocBlockContextFactory.php +++ b/src/Reflection/DocBlock/DocBlockContextFactory.php @@ -1,5 +1,7 @@ contextFactory->createFromReflector($reflector); } -} \ No newline at end of file +} diff --git a/src/Reflection/DocBlock/PhpDocumentorDocBlockFactory.php b/src/Reflection/DocBlock/PhpDocumentorDocBlockFactory.php index 92899af5c2..8f82227489 100644 --- a/src/Reflection/DocBlock/PhpDocumentorDocBlockFactory.php +++ b/src/Reflection/DocBlock/PhpDocumentorDocBlockFactory.php @@ -1,5 +1,7 @@ docBlockFactory->create($docblock, $context); } -} \ No newline at end of file +} diff --git a/src/SchemaFactory.php b/src/SchemaFactory.php index 69564bcf71..0046f87c0b 100644 --- a/src/SchemaFactory.php +++ b/src/SchemaFactory.php @@ -13,7 +13,6 @@ use PackageVersions\Versions; use phpDocumentor\Reflection\DocBlockFactory; use phpDocumentor\Reflection\Types\ContextFactory; -use Psr\Cache\CacheItemPoolInterface; use Psr\Container\ContainerInterface; use Psr\SimpleCache\CacheInterface; use Symfony\Component\Cache\Adapter\ArrayAdapter; @@ -24,13 +23,13 @@ use TheCodingMachine\CacheUtils\ClassBoundCacheInterface; use TheCodingMachine\CacheUtils\ClassBoundMemoryAdapter; use TheCodingMachine\CacheUtils\FileBoundCache; +use TheCodingMachine\GraphQLite\Cache\ClassBoundCacheContractFactoryInterface; use TheCodingMachine\GraphQLite\Discovery\Cache\FileModificationClassFinderComputedCache; use TheCodingMachine\GraphQLite\Discovery\Cache\HardClassFinderComputedCache; use TheCodingMachine\GraphQLite\Discovery\ClassFinder; -use TheCodingMachine\GraphQLite\Discovery\StaticClassFinder; use TheCodingMachine\GraphQLite\Discovery\KcsClassFinder; +use TheCodingMachine\GraphQLite\Discovery\StaticClassFinder; use TheCodingMachine\GraphQLite\Mappers\ClassFinderTypeMapper; -use TheCodingMachine\GraphQLite\Cache\ClassBoundCacheContractFactoryInterface; use TheCodingMachine\GraphQLite\Mappers\CompositeTypeMapper; use TheCodingMachine\GraphQLite\Mappers\Parameters\ContainerParameterHandler; use TheCodingMachine\GraphQLite\Mappers\Parameters\InjectUserParameterHandler; @@ -64,6 +63,7 @@ use TheCodingMachine\GraphQLite\Middlewares\SecurityInputFieldMiddleware; use TheCodingMachine\GraphQLite\Reflection\DocBlock\CachedDocBlockContextFactory; use TheCodingMachine\GraphQLite\Reflection\DocBlock\CachedDocBlockFactory; +use TheCodingMachine\GraphQLite\Reflection\DocBlock\DocBlockContextFactory; use TheCodingMachine\GraphQLite\Reflection\DocBlock\PhpDocumentorDocBlockContextFactory; use TheCodingMachine\GraphQLite\Reflection\DocBlock\PhpDocumentorDocBlockFactory; use TheCodingMachine\GraphQLite\Security\AuthenticationServiceInterface; @@ -75,11 +75,14 @@ use TheCodingMachine\GraphQLite\Types\InputTypeValidatorInterface; use TheCodingMachine\GraphQLite\Types\TypeResolver; use TheCodingMachine\GraphQLite\Utils\NamespacedCache; -use function array_map; + use function array_reverse; use function class_exists; use function md5; use function substr; +use function trigger_error; + +use const E_USER_DEPRECATED; /** * A class to help getting started with GraphQLite. @@ -145,7 +148,7 @@ public function __construct(private readonly CacheInterface $cache, private read public function addControllerNamespace(string $namespace): self { trigger_error( - "Using SchemaFactory::addControllerNamespace() is deprecated in favor of SchemaFactory::addNamespace().", + 'Using SchemaFactory::addControllerNamespace() is deprecated in favor of SchemaFactory::addNamespace().', E_USER_DEPRECATED, ); @@ -160,7 +163,7 @@ public function addControllerNamespace(string $namespace): self public function addTypeNamespace(string $namespace): self { trigger_error( - "Using SchemaFactory::addTypeNamespace() is deprecated in favor of SchemaFactory::addNamespace().", + 'Using SchemaFactory::addTypeNamespace() is deprecated in favor of SchemaFactory::addNamespace().', E_USER_DEPRECATED, ); @@ -401,7 +404,7 @@ public function createSchema(): Schema $rootTypeMapper = new MyCLabsEnumTypeMapper($rootTypeMapper, $annotationReader, $classFinder, $classFinderBoundCache); } - if (!empty($this->rootTypeMapperFactories)) { + if (! empty($this->rootTypeMapperFactories)) { $rootSchemaFactoryContext = new RootTypeMapperFactoryContext( $annotationReader, $typeResolver, @@ -538,7 +541,7 @@ private function createClassFinder(): ClassFinder // While this is technically okay, it doesn't follow SchemaFactory's semantics that allow it's // users to manually specify classes (see SchemaFactory::testCreateSchemaOnlyWithFactories()), // without having to specify namespaces to glob. This solves it by providing an empty iterator. - if (!$this->namespaces) { + if (! $this->namespaces) { return new StaticClassFinder([]); } @@ -556,11 +559,12 @@ private function createClassFinder(): ClassFinder return new KcsClassFinder($finder); } + /** @return array{ DocBlockFactory, DocBlockContextFactory } */ private function createDocBlockFactory(ClassBoundCacheInterface $nonInheritedClassBoundCache): array { $docBlockContextFactory = new CachedDocBlockContextFactory( new ClassBoundCacheContract(new ClassBoundMemoryAdapter($nonInheritedClassBoundCache)), - new PhpDocumentorDocBlockContextFactory(new ContextFactory()) + new PhpDocumentorDocBlockContextFactory(new ContextFactory()), ); $docBlockFactory = new CachedDocBlockFactory( new ClassBoundCacheContract(new ClassBoundMemoryAdapter($nonInheritedClassBoundCache)), From 3fc99327ffaf76442efdf36c799098765a39437a Mon Sep 17 00:00:00 2001 From: Oleksandr Prypkhan Date: Tue, 27 Aug 2024 01:24:09 +0300 Subject: [PATCH 12/28] PHPStan --- .../Cache/FileModificationClassFinderComputedCache.php | 3 ++- src/Discovery/StaticClassFinder.php | 1 + src/Mappers/GlobAnnotationsCache.php | 1 + src/Mappers/GlobTypeMapperCache.php | 2 +- src/Mappers/StaticClassListTypeMapperFactory.php | 3 +-- src/SchemaFactory.php | 3 +-- 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Discovery/Cache/FileModificationClassFinderComputedCache.php b/src/Discovery/Cache/FileModificationClassFinderComputedCache.php index dfcba30be5..92e97600d0 100644 --- a/src/Discovery/Cache/FileModificationClassFinderComputedCache.php +++ b/src/Discovery/Cache/FileModificationClassFinderComputedCache.php @@ -70,6 +70,7 @@ private function entries( ): mixed { $previousEntries = $this->cache->get($key) ?? []; + /** @var array $result */ $result = []; $entries = []; @@ -126,7 +127,7 @@ private function entries( return $result; } - /** @return array */ + /** @return array */ private function fileDependencies(ReflectionClass $refClass): array { $filename = $refClass->getFileName(); diff --git a/src/Discovery/StaticClassFinder.php b/src/Discovery/StaticClassFinder.php index 60f078cbac..2f46daf8f9 100644 --- a/src/Discovery/StaticClassFinder.php +++ b/src/Discovery/StaticClassFinder.php @@ -33,6 +33,7 @@ public function getIterator(): Traversable foreach ($this->classes as $class) { $classReflection = new ReflectionClass($class); + /** @phpstan-ignore-next-line */ if ($this->pathFilter && ! ($this->pathFilter)($classReflection->getFileName())) { continue; } diff --git a/src/Mappers/GlobAnnotationsCache.php b/src/Mappers/GlobAnnotationsCache.php index 6f85251aa4..0ecef554a3 100644 --- a/src/Mappers/GlobAnnotationsCache.php +++ b/src/Mappers/GlobAnnotationsCache.php @@ -17,6 +17,7 @@ final class GlobAnnotationsCache use Cloneable; /** + * @param class-string $sourceClass * @param class-string|null $typeClassName * @param array|null, 2:bool, 3:class-string}> $factories * An array mapping a factory method name to an input name / class name / default flag / diff --git a/src/Mappers/GlobTypeMapperCache.php b/src/Mappers/GlobTypeMapperCache.php index b66e4acc18..3fae8c5d35 100644 --- a/src/Mappers/GlobTypeMapperCache.php +++ b/src/Mappers/GlobTypeMapperCache.php @@ -55,7 +55,7 @@ public function registerAnnotations(ReflectionClass|string $sourceClass, GlobAnn foreach ($globAnnotationsCache->getFactories() as $methodName => [$inputName, $inputClassName, $isDefault, $declaringClass]) { if ($isDefault) { if ($inputClassName !== null && isset($this->mapClassToFactory[$inputClassName])) { - throw DuplicateMappingException::createForFactory($inputClassName, $this->mapClassToFactory[$inputClassName][0], $this->mapClassToFactory[$inputClassName][1], $refClass->getName(), $methodName); + throw DuplicateMappingException::createForFactory($inputClassName, $this->mapClassToFactory[$inputClassName][0], $this->mapClassToFactory[$inputClassName][1], $sourceClass, $methodName); } } else { // If this is not the default factory, let's not map the class name to the factory. diff --git a/src/Mappers/StaticClassListTypeMapperFactory.php b/src/Mappers/StaticClassListTypeMapperFactory.php index 22fcd6fcc4..84bd8f7ed7 100644 --- a/src/Mappers/StaticClassListTypeMapperFactory.php +++ b/src/Mappers/StaticClassListTypeMapperFactory.php @@ -16,7 +16,7 @@ final class StaticClassListTypeMapperFactory implements TypeMapperFactoryInterfa /** * StaticClassListTypeMapperFactory constructor. * - * @param array $classList The list of classes to be scanned. + * @param array $classList The list of classes to be scanned. */ public function __construct( private array $classList, @@ -37,7 +37,6 @@ public function create(FactoryContext $context): TypeMapperInterface $context->getNamingStrategy(), $context->getRecursiveTypeMapper(), $context->getClassFinderBoundCache(), - $context->getClassBoundCacheContractFactory(), ); } } diff --git a/src/SchemaFactory.php b/src/SchemaFactory.php index 0046f87c0b..8459bab7f9 100644 --- a/src/SchemaFactory.php +++ b/src/SchemaFactory.php @@ -469,7 +469,6 @@ public function createSchema(): Schema $namingStrategy, $recursiveTypeMapper, $classFinderBoundCache, - classBoundCacheContractFactory: $this->classBoundCacheContractFactory, )); } @@ -559,7 +558,7 @@ private function createClassFinder(): ClassFinder return new KcsClassFinder($finder); } - /** @return array{ DocBlockFactory, DocBlockContextFactory } */ + /** @return array{ Reflection\DocBlock\DocBlockFactory, DocBlockContextFactory } */ private function createDocBlockFactory(ClassBoundCacheInterface $nonInheritedClassBoundCache): array { $docBlockContextFactory = new CachedDocBlockContextFactory( From 4e8e8b0e4dc58e358fac6f4b1f520c76e55f30bf Mon Sep 17 00:00:00 2001 From: Oleksandr Prypkhan Date: Tue, 27 Aug 2024 01:29:15 +0300 Subject: [PATCH 13/28] Some fixes and renames --- src/FactoryContext.php | 6 +++--- src/GlobControllerQueryProvider.php | 4 ++-- src/Mappers/ClassFinderTypeMapper.php | 6 +++--- src/Mappers/Root/EnumTypeMapper.php | 4 ++-- src/Mappers/Root/MyCLabsEnumTypeMapper.php | 4 ++-- src/Mappers/Root/RootTypeMapperFactoryContext.php | 6 +++--- src/Mappers/StaticClassListTypeMapperFactory.php | 2 +- src/SchemaFactory.php | 14 +++++++------- tests/AbstractQueryProvider.php | 1 + tests/FactoryContextTest.php | 1 - 10 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/FactoryContext.php b/src/FactoryContext.php index 8573530d7f..45779452a5 100644 --- a/src/FactoryContext.php +++ b/src/FactoryContext.php @@ -33,7 +33,7 @@ public function __construct( private readonly CacheInterface $cache, private readonly InputTypeValidatorInterface|null $inputTypeValidator, private readonly ClassFinder $classFinder, - private readonly ClassFinderComputedCache $classFinderBoundCache, + private readonly ClassFinderComputedCache $classFinderComputedCache, private readonly ClassBoundCacheContractFactoryInterface|null $classBoundCacheContractFactory = null, ) { } @@ -103,8 +103,8 @@ public function getClassFinder(): ClassFinder return $this->classFinder; } - public function getClassFinderBoundCache(): ClassFinderComputedCache + public function getClassFinderComputedCache(): ClassFinderComputedCache { - return $this->classFinderBoundCache; + return $this->classFinderComputedCache; } } diff --git a/src/GlobControllerQueryProvider.php b/src/GlobControllerQueryProvider.php index c4f9ef5a17..15369715f2 100644 --- a/src/GlobControllerQueryProvider.php +++ b/src/GlobControllerQueryProvider.php @@ -35,7 +35,7 @@ public function __construct( private readonly ContainerInterface $container, private readonly AnnotationReader $annotationReader, private readonly ClassFinder $classFinder, - private readonly ClassFinderComputedCache $classFinderBoundCache, + private readonly ClassFinderComputedCache $classFinderComputedCache, ) { } @@ -58,7 +58,7 @@ private function getAggregateControllerQueryProvider(): AggregateControllerQuery */ private function getClassList(): array { - $this->classList ??= $this->classFinderBoundCache->compute( + $this->classList ??= $this->classFinderComputedCache->compute( $this->classFinder, 'globQueryProvider', function (ReflectionClass $classReflection): string|null { diff --git a/src/Mappers/ClassFinderTypeMapper.php b/src/Mappers/ClassFinderTypeMapper.php index 0a9960207e..c9911d4b28 100644 --- a/src/Mappers/ClassFinderTypeMapper.php +++ b/src/Mappers/ClassFinderTypeMapper.php @@ -44,7 +44,7 @@ public function __construct( private readonly AnnotationReader $annotationReader, private readonly NamingStrategyInterface $namingStrategy, private readonly RecursiveTypeMapperInterface $recursiveTypeMapper, - private readonly ClassFinderComputedCache $classFinderBoundCache, + private readonly ClassFinderComputedCache $classFinderComputedCache, ) { } @@ -54,7 +54,7 @@ public function __construct( */ private function getMaps(): GlobTypeMapperCache { - $this->globTypeMapperCache ??= $this->classFinderBoundCache->compute( + $this->globTypeMapperCache ??= $this->classFinderComputedCache->compute( $this->classFinder, 'classToAnnotations', function (ReflectionClass $refClass): GlobAnnotationsCache|null { @@ -125,7 +125,7 @@ function (ReflectionClass $refClass): GlobAnnotationsCache|null { private function getMapClassToExtendTypeArray(): GlobExtendTypeMapperCache { - $this->globExtendTypeMapperCache ??= $this->classFinderBoundCache->compute( + $this->globExtendTypeMapperCache ??= $this->classFinderComputedCache->compute( $this->classFinder, 'classToExtendAnnotations', function (ReflectionClass $refClass): GlobExtendAnnotationsCache|null { diff --git a/src/Mappers/Root/EnumTypeMapper.php b/src/Mappers/Root/EnumTypeMapper.php index 130b285289..38b5b60356 100644 --- a/src/Mappers/Root/EnumTypeMapper.php +++ b/src/Mappers/Root/EnumTypeMapper.php @@ -47,7 +47,7 @@ public function __construct( private readonly AnnotationReader $annotationReader, private readonly DocBlockFactory $docBlockFactory, private readonly ClassFinder $classFinder, - private readonly ClassFinderComputedCache $classFinderBoundCache, + private readonly ClassFinderComputedCache $classFinderComputedCache, ) { } @@ -191,7 +191,7 @@ public function mapNameToType(string $typeName): NamedType&GraphQLType */ private function getNameToClassMapping(): array { - $this->nameToClassMapping ??= $this->classFinderBoundCache->compute( + $this->nameToClassMapping ??= $this->classFinderComputedCache->compute( $this->classFinder, 'enum_name_to_class', function (ReflectionClass $classReflection): array|null { diff --git a/src/Mappers/Root/MyCLabsEnumTypeMapper.php b/src/Mappers/Root/MyCLabsEnumTypeMapper.php index c2bc80abdc..2a3bed7906 100644 --- a/src/Mappers/Root/MyCLabsEnumTypeMapper.php +++ b/src/Mappers/Root/MyCLabsEnumTypeMapper.php @@ -44,7 +44,7 @@ public function __construct( private readonly RootTypeMapperInterface $next, private readonly AnnotationReader $annotationReader, private readonly ClassFinder $classFinder, - private readonly ClassFinderComputedCache $classFinderBoundCache, + private readonly ClassFinderComputedCache $classFinderComputedCache, ) { } @@ -156,7 +156,7 @@ public function mapNameToType(string $typeName): NamedType&\GraphQL\Type\Definit */ private function getNameToClassMapping(): array { - $this->nameToClassMapping ??= $this->classFinderBoundCache->compute( + $this->nameToClassMapping ??= $this->classFinderComputedCache->compute( $this->classFinder, 'myclabsenum_name_to_class', function (ReflectionClass $classReflection): array|null { diff --git a/src/Mappers/Root/RootTypeMapperFactoryContext.php b/src/Mappers/Root/RootTypeMapperFactoryContext.php index 90057a60b4..513fa25171 100644 --- a/src/Mappers/Root/RootTypeMapperFactoryContext.php +++ b/src/Mappers/Root/RootTypeMapperFactoryContext.php @@ -29,7 +29,7 @@ public function __construct( private readonly ContainerInterface $container, private readonly CacheInterface $cache, private readonly ClassFinder $classFinder, - private readonly ClassFinderComputedCache $classFinderBoundCache, + private readonly ClassFinderComputedCache $classFinderComputedCache, ) { } @@ -73,8 +73,8 @@ public function getClassFinder(): ClassFinder return $this->classFinder; } - public function getClassFinderBoundCache(): ClassFinderComputedCache + public function getClassFinderComputedCache(): ClassFinderComputedCache { - return $this->classFinderBoundCache; + return $this->classFinderComputedCache; } } diff --git a/src/Mappers/StaticClassListTypeMapperFactory.php b/src/Mappers/StaticClassListTypeMapperFactory.php index 84bd8f7ed7..09fca187ae 100644 --- a/src/Mappers/StaticClassListTypeMapperFactory.php +++ b/src/Mappers/StaticClassListTypeMapperFactory.php @@ -36,7 +36,7 @@ public function create(FactoryContext $context): TypeMapperInterface $context->getAnnotationReader(), $context->getNamingStrategy(), $context->getRecursiveTypeMapper(), - $context->getClassFinderBoundCache(), + $context->getClassFinderComputedCache(), ); } } diff --git a/src/SchemaFactory.php b/src/SchemaFactory.php index 8459bab7f9..426f783ab7 100644 --- a/src/SchemaFactory.php +++ b/src/SchemaFactory.php @@ -364,7 +364,7 @@ public function createSchema(): Schema $namingStrategy = $this->namingStrategy ?: new NamingStrategy(); $typeRegistry = new TypeRegistry(); $classFinder = $this->createClassFinder(); - $classFinderBoundCache = $this->devMode ? + $classFinderComputedCache = $this->devMode ? new FileModificationClassFinderComputedCache($this->cache) : new HardClassFinderComputedCache($this->cache); @@ -397,11 +397,11 @@ public function createSchema(): Schema $errorRootTypeMapper = new FinalRootTypeMapper($recursiveTypeMapper); $rootTypeMapper = new BaseTypeMapper($errorRootTypeMapper, $recursiveTypeMapper, $topRootTypeMapper); - $rootTypeMapper = new EnumTypeMapper($rootTypeMapper, $annotationReader, $docBlockFactory, $classFinder, $classFinderBoundCache); + $rootTypeMapper = new EnumTypeMapper($rootTypeMapper, $annotationReader, $docBlockFactory, $classFinder, $classFinderComputedCache); if (class_exists(Enum::class)) { // Annotation support - deprecated - $rootTypeMapper = new MyCLabsEnumTypeMapper($rootTypeMapper, $annotationReader, $classFinder, $classFinderBoundCache); + $rootTypeMapper = new MyCLabsEnumTypeMapper($rootTypeMapper, $annotationReader, $classFinder, $classFinderComputedCache); } if (! empty($this->rootTypeMapperFactories)) { @@ -414,7 +414,7 @@ public function createSchema(): Schema $this->container, $namespacedCache, $classFinder, - $classFinderBoundCache, + $classFinderComputedCache, ); $reversedRootTypeMapperFactories = array_reverse($this->rootTypeMapperFactories); @@ -468,7 +468,7 @@ public function createSchema(): Schema $annotationReader, $namingStrategy, $recursiveTypeMapper, - $classFinderBoundCache, + $classFinderComputedCache, )); } @@ -486,7 +486,7 @@ public function createSchema(): Schema $namespacedCache, $this->inputTypeValidator, $classFinder, - $classFinderBoundCache, + $classFinderComputedCache, classBoundCacheContractFactory: $this->classBoundCacheContractFactory, ); } @@ -513,7 +513,7 @@ classBoundCacheContractFactory: $this->classBoundCacheContractFactory, $this->container, $annotationReader, $classFinder, - $classFinderBoundCache, + $classFinderComputedCache, ); } diff --git a/tests/AbstractQueryProvider.php b/tests/AbstractQueryProvider.php index c0bae0905b..d5a2fddd86 100644 --- a/tests/AbstractQueryProvider.php +++ b/tests/AbstractQueryProvider.php @@ -22,6 +22,7 @@ use TheCodingMachine\CacheUtils\ClassBoundCache; use TheCodingMachine\CacheUtils\ClassBoundCacheContract; use TheCodingMachine\CacheUtils\FileBoundCache; +use TheCodingMachine\GraphQLite\Containers\BasicAutoWiringContainer; use TheCodingMachine\GraphQLite\Containers\EmptyContainer; use TheCodingMachine\GraphQLite\Containers\LazyContainer; use TheCodingMachine\GraphQLite\Discovery\Cache\HardClassFinderComputedCache; diff --git a/tests/FactoryContextTest.php b/tests/FactoryContextTest.php index 9f02729924..78dd0cd83e 100644 --- a/tests/FactoryContextTest.php +++ b/tests/FactoryContextTest.php @@ -32,7 +32,6 @@ public function testContext(): void $container, $arrayCache, $validator, - self::GLOB_TTL_SECONDS, classBoundCacheContractFactory: $classBoundCacheContractFactory, ); From 0e2218cff6fff31c9236103680228686a0bbb3d9 Mon Sep 17 00:00:00 2001 From: Oleksandr Prypkhan Date: Tue, 27 Aug 2024 18:11:20 +0300 Subject: [PATCH 14/28] Fix broken class bound cache after merge --- src/Cache/ClassBoundCache.php | 25 ++++++ src/Cache/ClassBoundCacheContract.php | 43 ---------- src/Cache/ClassBoundCacheContractFactory.php | 15 ---- ...lassBoundCacheContractFactoryInterface.php | 12 --- .../ClassBoundCacheContractInterface.php | 13 --- src/Cache/ClassSnapshot.php | 69 +++++++++++++++ src/Cache/FileModificationClassBoundCache.php | 39 +++++++++ src/Cache/HardClassBoundCache.php | 36 ++++++++ ...leModificationClassFinderComputedCache.php | 48 ++--------- src/FactoryContext.php | 14 ++-- .../DocBlock/CachedDocBlockContextFactory.php | 6 +- .../DocBlock/CachedDocBlockFactory.php | 6 +- src/SchemaFactory.php | 46 +++++----- tests/AbstractQueryProvider.php | 37 ++++---- tests/FactoryContextTest.php | 17 ++-- tests/FieldsBuilderTest.php | 1 + tests/Integration/EndToEndTest.php | 2 +- tests/Integration/IntegrationTestCase.php | 20 ++--- ...Test.php => ClassFinderTypeMapperTest.php} | 84 +++++++++---------- tests/Mappers/RecursiveTypeMapperTest.php | 6 +- 20 files changed, 293 insertions(+), 246 deletions(-) create mode 100644 src/Cache/ClassBoundCache.php delete mode 100644 src/Cache/ClassBoundCacheContract.php delete mode 100644 src/Cache/ClassBoundCacheContractFactory.php delete mode 100644 src/Cache/ClassBoundCacheContractFactoryInterface.php delete mode 100644 src/Cache/ClassBoundCacheContractInterface.php create mode 100644 src/Cache/ClassSnapshot.php create mode 100644 src/Cache/FileModificationClassBoundCache.php create mode 100644 src/Cache/HardClassBoundCache.php rename tests/Mappers/{GlobTypeMapperTest.php => ClassFinderTypeMapperTest.php} (68%) diff --git a/src/Cache/ClassBoundCache.php b/src/Cache/ClassBoundCache.php new file mode 100644 index 0000000000..4e88b10e68 --- /dev/null +++ b/src/Cache/ClassBoundCache.php @@ -0,0 +1,25 @@ +cachePrefix = str_replace(['\\', '{', '}', '(', ')', '/', '@', ':'], '_', $cachePrefix); - } - - /** - * @param string $key An optional key to differentiate between cache items attached to the same class. - * - * @throws InvalidArgumentException - */ - public function get(ReflectionClass $reflectionClass, callable $resolver, string $key = '', int|null $ttl = null): mixed - { - $cacheKey = $reflectionClass->getName() . '__' . $key; - $cacheKey = $this->cachePrefix . str_replace(['\\', '{', '}', '(', ')', '/', '@', ':'], '_', $cacheKey); - - $item = $this->classBoundCache->get($cacheKey); - if ($item !== null) { - return $item; - } - - $item = $resolver(); - - $this->classBoundCache->set($cacheKey, $item, $ttl); - - return $item; - } -} diff --git a/src/Cache/ClassBoundCacheContractFactory.php b/src/Cache/ClassBoundCacheContractFactory.php deleted file mode 100644 index 0c2dfc7750..0000000000 --- a/src/Cache/ClassBoundCacheContractFactory.php +++ /dev/null @@ -1,15 +0,0 @@ - $dependencies */ + public function __construct( + private readonly array $dependencies, + ) + { + } + + public static function fromReflection(ReflectionClass $class, bool $useInheritance = false): self + { + return new self( + self::dependencies($class, $useInheritance), + ); + } + + /** @return array */ + private static function dependencies(ReflectionClass $class, bool $useInheritance = false): array + { + $filename = $class->getFileName(); + + // Internal classes are treated as always the same, e.g. you'll have to drop the cache between PHP versions. + if ($filename === false) { + return []; + } + + $files = [$filename => filemtime($filename)]; + + if (! $useInheritance) { + return []; + } + + if ($class->getParentClass() !== false) { + $files = array_merge($files, self::dependencies($class->getParentClass())); + } + + foreach ($class->getTraits() as $trait) { + $files = array_merge($files, self::dependencies($trait)); + } + + foreach ($class->getInterfaces() as $interface) { + $files = array_merge($files, self::dependencies($interface)); + } + + return $files; + } + + public function changed(): bool + { + foreach ($this->dependencies as $filename => $modificationTime) { + if ($modificationTime !== filemtime($filename)) { + return true; + } + } + + return false; + } +} diff --git a/src/Cache/FileModificationClassBoundCache.php b/src/Cache/FileModificationClassBoundCache.php new file mode 100644 index 0000000000..0638709c3b --- /dev/null +++ b/src/Cache/FileModificationClassBoundCache.php @@ -0,0 +1,39 @@ +getName() . '__' . $key; + $cacheKey = str_replace(['\\', '{', '}', '(', ')', '/', '@', ':'], '_', $cacheKey); + + $item = $this->cache->get($cacheKey); + + if ($item !== null && ! $item['snapshot']->changed()) { + return $item['data']; + } + + $item = [ + 'data' => $resolver(), + 'snapshot' => ClassSnapshot::fromReflection($reflectionClass), + ]; + + $this->cache->set($cacheKey, $item); + + return $item['data']; + } +} diff --git a/src/Cache/HardClassBoundCache.php b/src/Cache/HardClassBoundCache.php new file mode 100644 index 0000000000..abc31cd086 --- /dev/null +++ b/src/Cache/HardClassBoundCache.php @@ -0,0 +1,36 @@ +getName() . '__' . $key; + $cacheKey = str_replace(['\\', '{', '}', '(', ')', '/', '@', ':'], '_', $cacheKey); + + $item = $this->cache->get($cacheKey); + + if ($item !== null) { + return $item; + } + + $item = $resolver(); + + $this->cache->set($cacheKey, $item); + + return $item; + } +} diff --git a/src/Discovery/Cache/FileModificationClassFinderComputedCache.php b/src/Discovery/Cache/FileModificationClassFinderComputedCache.php index 92e97600d0..960660be59 100644 --- a/src/Discovery/Cache/FileModificationClassFinderComputedCache.php +++ b/src/Discovery/Cache/FileModificationClassFinderComputedCache.php @@ -6,9 +6,9 @@ use Psr\SimpleCache\CacheInterface; use ReflectionClass; +use TheCodingMachine\GraphQLite\Cache\ClassSnapshot; use TheCodingMachine\GraphQLite\Discovery\ClassFinder; -use function array_merge; use function Safe\filemtime; /** @@ -77,19 +77,19 @@ private function entries( // The size of the cache may be huge, so let's avoid writes when unnecessary. $changed = false; - $classFinder = $classFinder->withPathFilter(function (string $filename) use (&$entries, &$result, &$changed, $previousEntries) { + $classFinder = $classFinder->withPathFilter(static function (string $filename) use (&$entries, &$result, &$changed, $previousEntries) { $entry = $previousEntries[$filename] ?? null; // If there's no entry in cache for this filename (new file or previously uncached), // or if it the file has been modified since caching, we'll try to autoload // the class and collect the cached information (again). - if (! $entry || $this->dependenciesChanged($entry['dependencies'])) { + if (! $entry || $entry['dependencies']->changed()) { // In case this file isn't a class, or doesn't match the provided namespace filter, // it will not be emitted in the iterator and won't reach the `foreach()` below. // So to avoid iterating over these files again, we'll mark them as non-matching. // If they are matching, it'll be overwritten in the `foreach` loop below. $entries[$filename] = [ - 'dependencies' => [$filename => filemtime($filename)], + 'dependencies' => new ClassSnapshot([$filename => filemtime($filename)]), 'matching' => false, ]; @@ -112,7 +112,7 @@ private function entries( $result[$filename] = $map($classReflection); $entries[$filename] = [ - 'dependencies' => $this->fileDependencies($classReflection), + 'dependencies' => ClassSnapshot::fromReflection($classReflection), 'data' => $result[$filename], 'matching' => true, ]; @@ -126,42 +126,4 @@ private function entries( return $result; } - - /** @return array */ - private function fileDependencies(ReflectionClass $refClass): array - { - $filename = $refClass->getFileName(); - - if ($filename === false) { - return []; - } - - $files = [$filename => filemtime($filename)]; - - if ($refClass->getParentClass() !== false) { - $files = array_merge($files, $this->fileDependencies($refClass->getParentClass())); - } - - foreach ($refClass->getTraits() as $trait) { - $files = array_merge($files, $this->fileDependencies($trait)); - } - - foreach ($refClass->getInterfaces() as $interface) { - $files = array_merge($files, $this->fileDependencies($interface)); - } - - return $files; - } - - /** @param array $files */ - private function dependenciesChanged(array $files): bool - { - foreach ($files as $filename => $modificationTime) { - if ($modificationTime !== filemtime($filename)) { - return true; - } - } - - return false; - } } diff --git a/src/FactoryContext.php b/src/FactoryContext.php index 45779452a5..0aaf40ac42 100644 --- a/src/FactoryContext.php +++ b/src/FactoryContext.php @@ -6,7 +6,7 @@ use Psr\Container\ContainerInterface; use Psr\SimpleCache\CacheInterface; -use TheCodingMachine\GraphQLite\Cache\ClassBoundCacheContractFactoryInterface; +use TheCodingMachine\GraphQLite\Cache\ClassBoundCache; use TheCodingMachine\GraphQLite\Discovery\Cache\ClassFinderComputedCache; use TheCodingMachine\GraphQLite\Discovery\ClassFinder; use TheCodingMachine\GraphQLite\Mappers\RecursiveTypeMapperInterface; @@ -34,7 +34,7 @@ public function __construct( private readonly InputTypeValidatorInterface|null $inputTypeValidator, private readonly ClassFinder $classFinder, private readonly ClassFinderComputedCache $classFinderComputedCache, - private readonly ClassBoundCacheContractFactoryInterface|null $classBoundCacheContractFactory = null, + private readonly ClassBoundCache $classBoundCache, ) { } @@ -88,11 +88,6 @@ public function getCache(): CacheInterface return $this->cache; } - public function getClassBoundCacheContractFactory(): ClassBoundCacheContractFactoryInterface|null - { - return $this->classBoundCacheContractFactory; - } - public function getInputTypeValidator(): InputTypeValidatorInterface|null { return $this->inputTypeValidator; @@ -107,4 +102,9 @@ public function getClassFinderComputedCache(): ClassFinderComputedCache { return $this->classFinderComputedCache; } + + public function getClassBoundCache(): ClassBoundCache|null + { + return $this->classBoundCache; + } } diff --git a/src/Reflection/DocBlock/CachedDocBlockContextFactory.php b/src/Reflection/DocBlock/CachedDocBlockContextFactory.php index 21c988d448..ae90d9139b 100644 --- a/src/Reflection/DocBlock/CachedDocBlockContextFactory.php +++ b/src/Reflection/DocBlock/CachedDocBlockContextFactory.php @@ -7,12 +7,12 @@ use phpDocumentor\Reflection\Types\Context; use ReflectionClass; use Reflector; -use TheCodingMachine\CacheUtils\ClassBoundCacheContractInterface; +use TheCodingMachine\GraphQLite\Cache\ClassBoundCache; class CachedDocBlockContextFactory implements DocBlockContextFactory { public function __construct( - private readonly ClassBoundCacheContractInterface $classBoundCacheContract, + private readonly ClassBoundCache $classBoundCache, private readonly DocBlockContextFactory $contextFactory, ) { @@ -22,7 +22,7 @@ public function createFromReflector(Reflector $reflector): Context { $reflector = $reflector instanceof ReflectionClass ? $reflector : $reflector->getDeclaringClass(); - return $this->classBoundCacheContract->get( + return $this->classBoundCache->get( $reflector, fn () => $this->contextFactory->createFromReflector($reflector), 'reflection.docBlockContext', diff --git a/src/Reflection/DocBlock/CachedDocBlockFactory.php b/src/Reflection/DocBlock/CachedDocBlockFactory.php index a4a4c2da65..45e0673402 100644 --- a/src/Reflection/DocBlock/CachedDocBlockFactory.php +++ b/src/Reflection/DocBlock/CachedDocBlockFactory.php @@ -7,7 +7,7 @@ use phpDocumentor\Reflection\DocBlock; use ReflectionClass; use Reflector; -use TheCodingMachine\CacheUtils\ClassBoundCacheContractInterface; +use TheCodingMachine\GraphQLite\Cache\ClassBoundCache; use function md5; @@ -17,7 +17,7 @@ class CachedDocBlockFactory implements DocBlockFactory { public function __construct( - private readonly ClassBoundCacheContractInterface $classBoundCacheContract, + private readonly ClassBoundCache $classBoundCache, private readonly DocBlockFactory $docBlockFactory, ) { @@ -27,7 +27,7 @@ public function createFromReflector(Reflector $reflector): DocBlock { $class = $reflector instanceof ReflectionClass ? $reflector : $reflector->getDeclaringClass(); - return $this->classBoundCacheContract->get( + return $this->classBoundCache->get( $class, fn () => $this->docBlockFactory->createFromReflector($reflector), 'reflection.docBlock.' . md5($reflector::class . '.' . $reflector->getName()), diff --git a/src/SchemaFactory.php b/src/SchemaFactory.php index 426f783ab7..70d68deef6 100644 --- a/src/SchemaFactory.php +++ b/src/SchemaFactory.php @@ -18,12 +18,9 @@ use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\Cache\Adapter\Psr16Adapter; use Symfony\Component\ExpressionLanguage\ExpressionLanguage; -use TheCodingMachine\CacheUtils\ClassBoundCache; -use TheCodingMachine\CacheUtils\ClassBoundCacheContract; -use TheCodingMachine\CacheUtils\ClassBoundCacheInterface; -use TheCodingMachine\CacheUtils\ClassBoundMemoryAdapter; -use TheCodingMachine\CacheUtils\FileBoundCache; -use TheCodingMachine\GraphQLite\Cache\ClassBoundCacheContractFactoryInterface; +use TheCodingMachine\GraphQLite\Cache\ClassBoundCache; +use TheCodingMachine\GraphQLite\Cache\FileModificationClassBoundCache; +use TheCodingMachine\GraphQLite\Cache\HardClassBoundCache; use TheCodingMachine\GraphQLite\Discovery\Cache\FileModificationClassFinderComputedCache; use TheCodingMachine\GraphQLite\Discovery\Cache\HardClassFinderComputedCache; use TheCodingMachine\GraphQLite\Discovery\ClassFinder; @@ -135,8 +132,11 @@ class SchemaFactory private string $cacheNamespace; - public function __construct(private readonly CacheInterface $cache, private readonly ContainerInterface $container, private ClassBoundCacheContractFactoryInterface|null $classBoundCacheContractFactory = null) - { + public function __construct( + private readonly CacheInterface $cache, + private readonly ContainerInterface $container, + private ClassBoundCache|null $classBoundCache = null, + ) { $this->cacheNamespace = substr(md5(Versions::getVersion('thecodingmachine/graphqlite')), 0, 8); } @@ -283,13 +283,11 @@ public function setFinder(FinderInterface $finder): self } /** - * Set a custom ClassBoundCacheContractFactory. - * This is used to create CacheContracts that store reflection results. - * Set this to "null" to use the default fallback factory. + * Set a custom class bound cache. By default in dev mode it looks at file modification times. */ - public function setClassBoundCacheContractFactory(ClassBoundCacheContractFactoryInterface|null $classBoundCacheContractFactory): self + public function setClassBoundCache(ClassBoundCache|null $classBoundCache): self { - $this->classBoundCacheContractFactory = $classBoundCacheContractFactory; + $this->classBoundCache = $classBoundCache; return $this; } @@ -353,14 +351,8 @@ public function createSchema(): Schema $authorizationService = $this->authorizationService ?: new FailAuthorizationService(); $typeResolver = new TypeResolver(); $namespacedCache = new NamespacedCache($this->cache); - $fileBoundCache = new FileBoundCache($this->cache); - $nonInheritedClassBoundCache = new ClassBoundCache( - fileBoundCache: $fileBoundCache, - analyzeParentClasses: false, - analyzeTraits: false, - analyzeInterfaces: false, - ); - [$docBlockFactory, $docBlockContextFactory] = $this->createDocBlockFactory($nonInheritedClassBoundCache); + $classBoundCache = $this->classBoundCache ?: ($this->devMode ? new FileModificationClassBoundCache($this->cache) : new HardClassBoundCache($this->cache)); + [$docBlockFactory, $docBlockContextFactory] = $this->createDocBlockFactory($classBoundCache); $namingStrategy = $this->namingStrategy ?: new NamingStrategy(); $typeRegistry = new TypeRegistry(); $classFinder = $this->createClassFinder(); @@ -485,9 +477,9 @@ public function createSchema(): Schema $this->container, $namespacedCache, $this->inputTypeValidator, - $classFinder, - $classFinderComputedCache, - classBoundCacheContractFactory: $this->classBoundCacheContractFactory, + classFinder: $classFinder, + classFinderComputedCache: $classFinderComputedCache, + classBoundCache: $classBoundCache, ); } @@ -559,14 +551,14 @@ private function createClassFinder(): ClassFinder } /** @return array{ Reflection\DocBlock\DocBlockFactory, DocBlockContextFactory } */ - private function createDocBlockFactory(ClassBoundCacheInterface $nonInheritedClassBoundCache): array + private function createDocBlockFactory(ClassBoundCache $classBoundCache): array { $docBlockContextFactory = new CachedDocBlockContextFactory( - new ClassBoundCacheContract(new ClassBoundMemoryAdapter($nonInheritedClassBoundCache)), + $classBoundCache, new PhpDocumentorDocBlockContextFactory(new ContextFactory()), ); $docBlockFactory = new CachedDocBlockFactory( - new ClassBoundCacheContract(new ClassBoundMemoryAdapter($nonInheritedClassBoundCache)), + $classBoundCache, new PhpDocumentorDocBlockFactory( DocBlockFactory::createInstance(), $docBlockContextFactory, diff --git a/tests/AbstractQueryProvider.php b/tests/AbstractQueryProvider.php index d5a2fddd86..b1fddda296 100644 --- a/tests/AbstractQueryProvider.php +++ b/tests/AbstractQueryProvider.php @@ -10,6 +10,8 @@ use GraphQL\Type\Definition\OutputType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Schema; +use Kcs\ClassFinder\FileFinder\CachedFileFinder; +use Kcs\ClassFinder\FileFinder\DefaultFileFinder; use Kcs\ClassFinder\Finder\ComposerFinder; use phpDocumentor\Reflection\TypeResolver as PhpDocumentorTypeResolver; use phpDocumentor\Reflection\Types\ContextFactory; @@ -19,12 +21,12 @@ use Symfony\Component\Cache\Adapter\Psr16Adapter; use Symfony\Component\Cache\Psr16Cache; use Symfony\Component\ExpressionLanguage\ExpressionLanguage; -use TheCodingMachine\CacheUtils\ClassBoundCache; -use TheCodingMachine\CacheUtils\ClassBoundCacheContract; -use TheCodingMachine\CacheUtils\FileBoundCache; +use TheCodingMachine\GraphQLite\Cache\ClassBoundCache; +use TheCodingMachine\GraphQLite\Cache\HardClassBoundCache; use TheCodingMachine\GraphQLite\Containers\BasicAutoWiringContainer; use TheCodingMachine\GraphQLite\Containers\EmptyContainer; use TheCodingMachine\GraphQLite\Containers\LazyContainer; +use TheCodingMachine\GraphQLite\Discovery\Cache\ClassFinderComputedCache; use TheCodingMachine\GraphQLite\Discovery\Cache\HardClassFinderComputedCache; use TheCodingMachine\GraphQLite\Discovery\ClassFinder; use TheCodingMachine\GraphQLite\Discovery\StaticClassFinder; @@ -279,7 +281,7 @@ protected function getParameterMiddlewarePipe(): ParameterMiddlewarePipe protected function getDocBlockFactory(): DocBlockFactory { return new CachedDocBlockFactory( - $this->getClassBoundCacheContract(false), + $this->getClassBoundCache(), new PhpDocumentorDocBlockFactory( \phpDocumentor\Reflection\DocBlockFactory::createInstance(), $this->getDocBlockContextFactory(), @@ -290,25 +292,18 @@ protected function getDocBlockFactory(): DocBlockFactory protected function getDocBlockContextFactory(): DocBlockContextFactory { return new CachedDocBlockContextFactory( - $this->getClassBoundCacheContract(false), + $this->getClassBoundCache(), new PhpDocumentorDocBlockContextFactory(new ContextFactory()) ); } - private function getClassBoundCacheContract(bool $analyzeParents): ClassBoundCacheContract + private function getClassBoundCache(): ClassBoundCache { $arrayAdapter = new ArrayAdapter(); $arrayAdapter->setLogger(new ExceptionLogger()); $psr16Cache = new Psr16Cache($arrayAdapter); - return new ClassBoundCacheContract( - new ClassBoundCache( - fileBoundCache: new FileBoundCache($psr16Cache), - analyzeParentClasses: $analyzeParents, - analyzeTraits: $analyzeParents, - analyzeInterfaces: $analyzeParents, - ), - ); + return new HardClassBoundCache($psr16Cache); } protected function buildFieldsBuilder(): FieldsBuilder @@ -496,6 +491,20 @@ protected function getClassFinder(array|string $namespaces): ClassFinder $finder->inNamespace($namespace); } + $arrayAdapter = new ArrayAdapter(); + $arrayAdapter->setLogger(new ExceptionLogger()); + + $finder = $finder->withFileFinder(new CachedFileFinder(new DefaultFileFinder(), $arrayAdapter)); + return new KcsClassFinder($finder); } + + protected function getClassFinderComputedCache(): ClassFinderComputedCache + { + $arrayAdapter = new ArrayAdapter(); + $arrayAdapter->setLogger(new ExceptionLogger()); + $psr16Cache = new Psr16Cache($arrayAdapter); + + return new HardClassFinderComputedCache($psr16Cache); + } } diff --git a/tests/FactoryContextTest.php b/tests/FactoryContextTest.php index 78dd0cd83e..76b3f644f7 100644 --- a/tests/FactoryContextTest.php +++ b/tests/FactoryContextTest.php @@ -4,8 +4,10 @@ use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\Cache\Psr16Cache; -use TheCodingMachine\GraphQLite\Cache\ClassBoundCacheContractFactory; +use TheCodingMachine\GraphQLite\Cache\HardClassBoundCache; use TheCodingMachine\GraphQLite\Containers\EmptyContainer; +use TheCodingMachine\GraphQLite\Discovery\Cache\HardClassFinderComputedCache; +use TheCodingMachine\GraphQLite\Discovery\StaticClassFinder; use TheCodingMachine\GraphQLite\Fixtures\Inputs\Validator; class FactoryContextTest extends AbstractQueryProvider @@ -17,7 +19,9 @@ public function testContext(): void $namingStrategy = new NamingStrategy(); $container = new EmptyContainer(); $arrayCache = new Psr16Cache(new ArrayAdapter()); - $classBoundCacheContractFactory = new ClassBoundCacheContractFactory(); + $classFinder = new StaticClassFinder([]); + $classFinderComputedCache = new HardClassFinderComputedCache($arrayCache); + $classBoundCache = new HardClassBoundCache($arrayCache); $validator = new Validator(); $context = new FactoryContext( @@ -32,7 +36,9 @@ public function testContext(): void $container, $arrayCache, $validator, - classBoundCacheContractFactory: $classBoundCacheContractFactory, + classFinder: $classFinder, + classFinderComputedCache: $classFinderComputedCache, + classBoundCache: $classBoundCache, ); $this->assertSame($this->getAnnotationReader(), $context->getAnnotationReader()); @@ -46,7 +52,8 @@ classBoundCacheContractFactory: $classBoundCacheContractFactory, $this->assertSame($container, $context->getContainer()); $this->assertSame($arrayCache, $context->getCache()); $this->assertSame($validator, $context->getInputTypeValidator()); - $this->assertSame(self::GLOB_TTL_SECONDS, $context->getGlobTTL()); - $this->assertNull($context->getMapTTL()); + $this->assertSame($classFinder, $context->getClassFinder()); + $this->assertSame($classFinderComputedCache, $context->getClassFinderComputedCache()); + $this->assertSame($classBoundCache, $context->getClassBoundCache()); } } diff --git a/tests/FieldsBuilderTest.php b/tests/FieldsBuilderTest.php index 114f5e0a50..a640956102 100644 --- a/tests/FieldsBuilderTest.php +++ b/tests/FieldsBuilderTest.php @@ -21,6 +21,7 @@ use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\Cache\Psr16Cache; use TheCodingMachine\GraphQLite\Annotations\Exceptions\InvalidParameterException; +use TheCodingMachine\GraphQLite\Annotations\Query; use TheCodingMachine\GraphQLite\Fixtures\PropertyPromotionInputType; use TheCodingMachine\GraphQLite\Fixtures\PropertyPromotionInputTypeWithoutGenericDoc; use TheCodingMachine\GraphQLite\Fixtures\TestController; diff --git a/tests/Integration/EndToEndTest.php b/tests/Integration/EndToEndTest.php index 83eeefef7c..0d9f2086c4 100644 --- a/tests/Integration/EndToEndTest.php +++ b/tests/Integration/EndToEndTest.php @@ -632,7 +632,7 @@ public function testEndToEndStaticFactories(): void 'echoFilters' => ['foo', 'bar', '12', '42', '62'], ], $this->getSuccessResult($result)); - // Call again to test GlobTypeMapper cache + // Call again to test ClassFinderTypeMapper cache $result = GraphQL::executeQuery( $schema, $queryString, diff --git a/tests/Integration/IntegrationTestCase.php b/tests/Integration/IntegrationTestCase.php index 6c6ee5381f..b9b9b005e4 100644 --- a/tests/Integration/IntegrationTestCase.php +++ b/tests/Integration/IntegrationTestCase.php @@ -13,11 +13,10 @@ use Symfony\Component\Cache\Adapter\Psr16Adapter; use Symfony\Component\Cache\Psr16Cache; use Symfony\Component\ExpressionLanguage\ExpressionLanguage; -use TheCodingMachine\CacheUtils\ClassBoundCache; -use TheCodingMachine\CacheUtils\ClassBoundCacheContract; -use TheCodingMachine\CacheUtils\FileBoundCache; use TheCodingMachine\GraphQLite\AggregateQueryProvider; use TheCodingMachine\GraphQLite\AnnotationReader; +use TheCodingMachine\GraphQLite\Cache\ClassBoundCache; +use TheCodingMachine\GraphQLite\Cache\HardClassBoundCache; use TheCodingMachine\GraphQLite\Containers\BasicAutoWiringContainer; use TheCodingMachine\GraphQLite\Containers\EmptyContainer; use TheCodingMachine\GraphQLite\Containers\LazyContainer; @@ -280,23 +279,16 @@ public function createContainer(array $overloadedServices = []): ContainerInterf NamingStrategyInterface::class => static function () { return new NamingStrategy(); }, - 'nonInheritedClassBoundCacheContract' => static function () { + ClassBoundCache::class => static function () { $arrayAdapter = new ArrayAdapter(); $arrayAdapter->setLogger(new ExceptionLogger()); $psr16Cache = new Psr16Cache($arrayAdapter); - return new ClassBoundCacheContract( - new ClassBoundCache( - fileBoundCache: new FileBoundCache($psr16Cache), - analyzeParentClasses: false, - analyzeTraits: false, - analyzeInterfaces: false, - ), - ); + return new HardClassBoundCache($psr16Cache); }, DocBlockFactory::class => static function (ContainerInterface $container) { return new CachedDocBlockFactory( - $container->get('nonInheritedClassBoundCacheContract'), + $container->get(ClassBoundCache::class), new PhpDocumentorDocBlockFactory( \phpDocumentor\Reflection\DocBlockFactory::createInstance(), $container->get(DocBlockContextFactory::class), @@ -305,7 +297,7 @@ public function createContainer(array $overloadedServices = []): ContainerInterf }, DocBlockContextFactory::class => static function (ContainerInterface $container) { return new CachedDocBlockContextFactory( - $container->get('nonInheritedClassBoundCacheContract'), + $container->get(ClassBoundCache::class), new PhpDocumentorDocBlockContextFactory( new ContextFactory(), ) diff --git a/tests/Mappers/GlobTypeMapperTest.php b/tests/Mappers/ClassFinderTypeMapperTest.php similarity index 68% rename from tests/Mappers/GlobTypeMapperTest.php rename to tests/Mappers/ClassFinderTypeMapperTest.php index a43c37d394..cb5831e500 100644 --- a/tests/Mappers/GlobTypeMapperTest.php +++ b/tests/Mappers/ClassFinderTypeMapperTest.php @@ -31,9 +31,9 @@ use TheCodingMachine\GraphQLite\NamingStrategy; use TheCodingMachine\GraphQLite\Types\MutableObjectType; -class GlobTypeMapperTest extends AbstractQueryProvider +class ClassFinderTypeMapperTest extends AbstractQueryProvider { - public function testGlobTypeMapper(): void + public function testClassFinderTypeMapper(): void { $container = new LazyContainer([ FooType::class => static function () { @@ -44,9 +44,9 @@ public function testGlobTypeMapper(): void $typeGenerator = $this->getTypeGenerator(); $inputTypeGenerator = $this->getInputTypeGenerator(); - $cache = new Psr16Cache(new ArrayAdapter()); + $classFinderComputedCache = $this->getClassFinderComputedCache(); - $mapper = new GlobTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\Types'), $typeGenerator, $inputTypeGenerator, $this->getInputTypeUtils(), $container, new AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), $cache); + $mapper = new ClassFinderTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\Types'), $typeGenerator, $inputTypeGenerator, $this->getInputTypeUtils(), $container, new AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), $classFinderComputedCache); $this->assertSame([TestObject::class], $mapper->getSupportedClasses()); $this->assertTrue($mapper->canMapClassToType(TestObject::class)); @@ -56,7 +56,7 @@ public function testGlobTypeMapper(): void $this->assertFalse($mapper->canMapNameToType('NotExists')); // Again to test cache - $anotherMapperSameCache = new GlobTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\Types'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), $cache); + $anotherMapperSameCache = new ClassFinderTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\Types'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), $classFinderComputedCache); $this->assertTrue($anotherMapperSameCache->canMapClassToType(TestObject::class)); $this->assertTrue($anotherMapperSameCache->canMapNameToType('Foo')); @@ -64,7 +64,7 @@ public function testGlobTypeMapper(): void $mapper->mapClassToType(stdClass::class, null); } - public function testGlobTypeMapperDuplicateTypesException(): void + public function testClassFinderTypeMapperDuplicateTypesException(): void { $container = new LazyContainer([ TestType::class => static function () { @@ -74,13 +74,13 @@ public function testGlobTypeMapperDuplicateTypesException(): void $typeGenerator = $this->getTypeGenerator(); - $mapper = new GlobTypeMapper($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(), new Psr16Cache(new NullAdapter())); $this->expectException(DuplicateMappingException::class); $mapper->canMapClassToType(TestType::class); } - public function testGlobTypeMapperDuplicateInputsException(): void + public function testClassFinderTypeMapperDuplicateInputsException(): void { $container = new LazyContainer([ TestInput::class => static function () { @@ -90,13 +90,13 @@ public function testGlobTypeMapperDuplicateInputsException(): void $typeGenerator = $this->getTypeGenerator(); - $mapper = new GlobTypeMapper($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(), new Psr16Cache(new NullAdapter())); $this->expectException(DuplicateMappingException::class); $mapper->canMapClassToInputType(TestInput::class); } - public function testGlobTypeMapperDuplicateInputTypesException(): void + public function testClassFinderTypeMapperDuplicateInputTypesException(): void { $container = new LazyContainer([ /*TestType::class => function() { @@ -106,7 +106,7 @@ public function testGlobTypeMapperDuplicateInputTypesException(): void $typeGenerator = $this->getTypeGenerator(); - $mapper = new GlobTypeMapper($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(), new Psr16Cache(new NullAdapter())); $caught = false; try { @@ -125,7 +125,7 @@ public function testGlobTypeMapperDuplicateInputTypesException(): void $this->assertTrue($caught, 'DuplicateMappingException is thrown'); } - public function testGlobTypeMapperInheritedInputTypesException(): void + public function testClassFinderTypeMapperInheritedInputTypesException(): void { $container = new LazyContainer([ ChildTestFactory::class => static function () { @@ -135,7 +135,7 @@ public function testGlobTypeMapperInheritedInputTypesException(): void $typeGenerator = $this->getTypeGenerator(); - $mapper = new GlobTypeMapper($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(), new Psr16Cache(new NullAdapter())); //$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\''); @@ -143,7 +143,7 @@ public function testGlobTypeMapperInheritedInputTypesException(): void $mapper->mapClassToInputType(TestObject::class); } - public function testGlobTypeMapperClassNotFoundException(): void + public function testClassFinderTypeMapperClassNotFoundException(): void { $container = new LazyContainer([ TestType::class => static function () { @@ -153,14 +153,14 @@ public function testGlobTypeMapperClassNotFoundException(): void $typeGenerator = $this->getTypeGenerator(); - $mapper = new GlobTypeMapper($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(), new Psr16Cache(new NullAdapter())); $this->expectException(ClassNotFoundException::class); $this->expectExceptionMessage("Could not autoload class 'Foobar' defined in #[Type] attribute of class 'TheCodingMachine\\GraphQLite\\Fixtures\\BadClassType\\TestType'"); $mapper->canMapClassToType(TestType::class); } - public function testGlobTypeMapperNameNotFoundException(): void + public function testClassFinderTypeMapperNameNotFoundException(): void { $container = new LazyContainer([ FooType::class => static function () { @@ -170,13 +170,13 @@ public function testGlobTypeMapperNameNotFoundException(): void $typeGenerator = $this->getTypeGenerator(); - $mapper = new GlobTypeMapper($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(), new Psr16Cache(new NullAdapter())); $this->expectException(CannotMapTypeException::class); $mapper->mapNameToType('NotExists', $this->getTypeMapper()); } - public function testGlobTypeMapperInputType(): void + public function testClassFinderTypeMapperInputType(): void { $container = new LazyContainer([ FooType::class => static function () { @@ -189,9 +189,9 @@ public function testGlobTypeMapperInputType(): void $typeGenerator = $this->getTypeGenerator(); - $cache = new Psr16Cache(new ArrayAdapter()); + $classFinderComputedCache = $this->getClassFinderComputedCache(); - $mapper = new GlobTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\Types'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), $cache); + $mapper = new ClassFinderTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\Types'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), $classFinderComputedCache); $this->assertTrue($mapper->canMapClassToInputType(TestObject::class)); @@ -200,7 +200,7 @@ public function testGlobTypeMapperInputType(): void $this->assertSame('TestObjectInput', $inputType->name); // Again to test cache - $anotherMapperSameCache = new GlobTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\Types'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), $cache); + $anotherMapperSameCache = new ClassFinderTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\Types'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), $classFinderComputedCache); $this->assertTrue($anotherMapperSameCache->canMapClassToInputType(TestObject::class)); $this->assertSame('TestObjectInput', $anotherMapperSameCache->mapClassToInputType(TestObject::class, $this->getTypeMapper())->name); @@ -209,7 +209,7 @@ public function testGlobTypeMapperInputType(): void $mapper->mapClassToInputType(TestType::class, $this->getTypeMapper()); } - public function testGlobTypeMapperExtend(): void + public function testClassFinderTypeMapperExtend(): void { $container = new LazyContainer([ FooType::class => static function () { @@ -223,9 +223,9 @@ public function testGlobTypeMapperExtend(): void $typeGenerator = $this->getTypeGenerator(); $inputTypeGenerator = $this->getInputTypeGenerator(); - $cache = new Psr16Cache(new ArrayAdapter()); + $classFinderComputedCache = $this->getClassFinderComputedCache(); - $mapper = new GlobTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\Types'), $typeGenerator, $inputTypeGenerator, $this->getInputTypeUtils(), $container, new AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), $cache); + $mapper = new ClassFinderTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\Types'), $typeGenerator, $inputTypeGenerator, $this->getInputTypeUtils(), $container, new AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), $classFinderComputedCache); $type = $mapper->mapClassToType(TestObject::class, null); @@ -236,7 +236,7 @@ public function testGlobTypeMapperExtend(): void $this->assertFalse($mapper->canExtendTypeForName('NotExists', $type)); // Again to test cache - $anotherMapperSameCache = new GlobTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\Types'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), $cache); + $anotherMapperSameCache = new ClassFinderTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\Types'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), $classFinderComputedCache); $this->assertTrue($anotherMapperSameCache->canExtendTypeForClass(TestObject::class, $type)); $this->assertTrue($anotherMapperSameCache->canExtendTypeForName('TestObject', $type)); @@ -244,21 +244,21 @@ public function testGlobTypeMapperExtend(): void $mapper->extendTypeForClass(stdClass::class, $type); } - public function testEmptyGlobTypeMapper(): void + public function testEmptyClassFinderTypeMapper(): void { $container = new LazyContainer([]); $typeGenerator = $this->getTypeGenerator(); $inputTypeGenerator = $this->getInputTypeGenerator(); - $cache = new Psr16Cache(new ArrayAdapter()); + $classFinderComputedCache = $this->getClassFinderComputedCache(); - $mapper = new GlobTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\Integration\Controllers'), $typeGenerator, $inputTypeGenerator, $this->getInputTypeUtils(), $container, new AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), $cache); + $mapper = new ClassFinderTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\Integration\Controllers'), $typeGenerator, $inputTypeGenerator, $this->getInputTypeUtils(), $container, new AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), $classFinderComputedCache); $this->assertSame([], $mapper->getSupportedClasses()); } - public function testGlobTypeMapperDecorate(): void + public function testClassFinderTypeMapperDecorate(): void { $container = new LazyContainer([ FilterDecorator::class => static function () { @@ -269,9 +269,9 @@ public function testGlobTypeMapperDecorate(): void $typeGenerator = $this->getTypeGenerator(); $inputTypeGenerator = $this->getInputTypeGenerator(); - $cache = new Psr16Cache(new ArrayAdapter()); + $classFinderComputedCache = $this->getClassFinderComputedCache(); - $mapper = new GlobTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\Integration\Types'), $typeGenerator, $inputTypeGenerator, $this->getInputTypeUtils(), $container, new AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), $cache); + $mapper = new ClassFinderTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\Integration\Types'), $typeGenerator, $inputTypeGenerator, $this->getInputTypeUtils(), $container, new AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), $classFinderComputedCache); $inputType = new MockResolvableInputObjectType(['name' => 'FilterInput']); @@ -294,14 +294,14 @@ public function testInvalidName(): void $typeGenerator = $this->getTypeGenerator(); - $mapper = new GlobTypeMapper($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(), new Psr16Cache(new ArrayAdapter())); $this->assertFalse($mapper->canExtendTypeForName('{}()/\\@:', new MutableObjectType(['name' => 'foo']))); $this->assertFalse($mapper->canDecorateInputTypeForName('{}()/\\@:', new MockResolvableInputObjectType(['name' => 'foo']))); $this->assertFalse($mapper->canMapNameToType('{}()/\\@:')); } - public function testGlobTypeMapperExtendBadName(): void + public function testClassFinderTypeMapperExtendBadName(): void { $container = new LazyContainer([ FooType::class => static function () { @@ -318,9 +318,9 @@ public function testGlobTypeMapperExtendBadName(): void $typeGenerator = $this->getTypeGenerator(); $inputTypeGenerator = $this->getInputTypeGenerator(); - $cache = new Psr16Cache(new ArrayAdapter()); + $classFinderComputedCache = $this->getClassFinderComputedCache(); - $mapper = new GlobTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\BadExtendType'), $typeGenerator, $inputTypeGenerator, $this->getInputTypeUtils(), $container, new AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), $cache); + $mapper = new ClassFinderTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\BadExtendType'), $typeGenerator, $inputTypeGenerator, $this->getInputTypeUtils(), $container, new AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), $classFinderComputedCache); $testObjectType = new MutableObjectType([ 'name' => 'TestObject', @@ -334,7 +334,7 @@ public function testGlobTypeMapperExtendBadName(): void $mapper->extendTypeForName('TestObject', $testObjectType); } - public function testGlobTypeMapperExtendBadClass(): void + public function testClassFinderTypeMapperExtendBadClass(): void { $container = new LazyContainer([ FooType::class => static function () { @@ -351,9 +351,9 @@ public function testGlobTypeMapperExtendBadClass(): void $typeGenerator = $this->getTypeGenerator(); $inputTypeGenerator = $this->getInputTypeGenerator(); - $cache = new Psr16Cache(new ArrayAdapter()); + $classFinderComputedCache = $this->getClassFinderComputedCache(); - $mapper = new GlobTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\BadExtendType2'), $typeGenerator, $inputTypeGenerator, $this->getInputTypeUtils(), $container, new AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), $cache); + $mapper = new ClassFinderTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\BadExtendType2'), $typeGenerator, $inputTypeGenerator, $this->getInputTypeUtils(), $container, new AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), $classFinderComputedCache); $testObjectType = new MutableObjectType([ 'name' => 'TestObject', @@ -378,9 +378,9 @@ public function testNonInstantiableType(): void $typeGenerator = $this->getTypeGenerator(); $inputTypeGenerator = $this->getInputTypeGenerator(); - $cache = new Psr16Cache(new ArrayAdapter()); + $classFinderComputedCache = $this->getClassFinderComputedCache(); - $mapper = new GlobTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\NonInstantiableType'), $typeGenerator, $inputTypeGenerator, $this->getInputTypeUtils(), $container, new AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), $cache); + $mapper = new ClassFinderTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\NonInstantiableType'), $typeGenerator, $inputTypeGenerator, $this->getInputTypeUtils(), $container, new AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), $classFinderComputedCache); $this->expectException(GraphQLRuntimeException::class); $this->expectExceptionMessage('Class "TheCodingMachine\GraphQLite\Fixtures\NonInstantiableType\AbstractFooType" annotated with @Type(class="TheCodingMachine\GraphQLite\Fixtures\TestObject") must be instantiable.'); @@ -394,8 +394,8 @@ public function testNonInstantiableInput(): void $typeGenerator = $this->getTypeGenerator(); $inputTypeGenerator = $this->getInputTypeGenerator(); - $cache = new Psr16Cache(new ArrayAdapter()); - $mapper = new GlobTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\NonInstantiableInput'), $typeGenerator, $inputTypeGenerator, $this->getInputTypeUtils(), $container, new AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), $cache); + $classFinderComputedCache = $this->getClassFinderComputedCache(); + $mapper = new ClassFinderTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\NonInstantiableInput'), $typeGenerator, $inputTypeGenerator, $this->getInputTypeUtils(), $container, new AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), $classFinderComputedCache); $this->expectException(FailedResolvingInputType::class); $this->expectExceptionMessage("Class 'TheCodingMachine\GraphQLite\Fixtures\NonInstantiableInput\AbstractFoo' annotated with @Input must be instantiable."); diff --git a/tests/Mappers/RecursiveTypeMapperTest.php b/tests/Mappers/RecursiveTypeMapperTest.php index cc6fe11f6c..a99b70fce9 100644 --- a/tests/Mappers/RecursiveTypeMapperTest.php +++ b/tests/Mappers/RecursiveTypeMapperTest.php @@ -133,7 +133,7 @@ protected function getTypeMapper(): RecursiveTypeMapper $typeGenerator = new TypeGenerator($this->getAnnotationReader(), $namingStrategy, $this->getTypeRegistry(), $this->getRegistry(), $this->typeMapper, $this->getFieldsBuilder()); - $mapper = new GlobTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\Interfaces\Types'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(), $namingStrategy, $this->typeMapper, new Psr16Cache(new NullAdapter())); + $mapper = new ClassFinderTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\Interfaces\Types'), $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(), $namingStrategy, $this->typeMapper, $this->getClassFinderComputedCache()); $compositeMapper->addTypeMapper($mapper); } return $this->typeMapper; @@ -230,9 +230,7 @@ public function testMapNameToTypeDecorators(): void $typeGenerator = $this->getTypeGenerator(); $inputTypeGenerator = $this->getInputTypeGenerator(); - $cache = new Psr16Cache(new ArrayAdapter()); - - $mapper = new GlobTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\Integration'), $typeGenerator, $inputTypeGenerator, $this->getInputTypeUtils(), $this->getRegistry(), new \TheCodingMachine\GraphQLite\AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), $cache); + $mapper = new ClassFinderTypeMapper($this->getClassFinder('TheCodingMachine\GraphQLite\Fixtures\Integration'), $typeGenerator, $inputTypeGenerator, $this->getInputTypeUtils(), $this->getRegistry(), new \TheCodingMachine\GraphQLite\AnnotationReader(), new NamingStrategy(), $this->getTypeMapper(), $this->getClassFinderComputedCache()); $recursiveTypeMapper = new RecursiveTypeMapper( $mapper, From 28775bd51635e11d9701e1bdae6738ff4c7d05a5 Mon Sep 17 00:00:00 2001 From: Oleksandr Prypkhan Date: Tue, 27 Aug 2024 18:51:14 +0300 Subject: [PATCH 15/28] Fix some tests and PHPStan --- ...FileModificationClassFinderComputedCache.php | 2 ++ .../Cache/HardClassFinderComputedCache.php | 1 + src/GlobControllerQueryProvider.php | 2 ++ src/Mappers/GlobTypeMapperCache.php | 2 +- .../Root/RootTypeMapperFactoryContext.php | 7 +++++++ .../DocBlock/CachedDocBlockContextFactory.php | 6 ++++-- .../DocBlock/CachedDocBlockFactory.php | 6 ++++-- .../DocBlock/DocBlockContextFactory.php | 7 +++++-- src/Reflection/DocBlock/DocBlockFactory.php | 7 +++++-- .../PhpDocumentorDocBlockContextFactory.php | 7 +++++-- .../DocBlock/PhpDocumentorDocBlockFactory.php | 11 +++++++---- src/SchemaFactory.php | 17 ++++++++++++----- tests/RootTypeMapperFactoryContextTest.php | 10 +++++++--- 13 files changed, 62 insertions(+), 23 deletions(-) diff --git a/src/Discovery/Cache/FileModificationClassFinderComputedCache.php b/src/Discovery/Cache/FileModificationClassFinderComputedCache.php index 960660be59..fb8b7cb24b 100644 --- a/src/Discovery/Cache/FileModificationClassFinderComputedCache.php +++ b/src/Discovery/Cache/FileModificationClassFinderComputedCache.php @@ -78,6 +78,7 @@ private function entries( $changed = false; $classFinder = $classFinder->withPathFilter(static function (string $filename) use (&$entries, &$result, &$changed, $previousEntries) { + /** @var array{ data: TEntry, dependencies: ClassSnapshot, matching: bool } $entry */ $entry = $previousEntries[$filename] ?? null; // If there's no entry in cache for this filename (new file or previously uncached), @@ -124,6 +125,7 @@ private function entries( $this->cache->set($key, $entries); } + /** @phpstan-ignore return.type */ return $result; } } diff --git a/src/Discovery/Cache/HardClassFinderComputedCache.php b/src/Discovery/Cache/HardClassFinderComputedCache.php index da981a8e3d..472aa5a58a 100644 --- a/src/Discovery/Cache/HardClassFinderComputedCache.php +++ b/src/Discovery/Cache/HardClassFinderComputedCache.php @@ -63,6 +63,7 @@ private function entries( $entries[$classReflection->getFileName()] = $map($classReflection); } + /** @phpstan-ignore return.type */ return $entries; } } diff --git a/src/GlobControllerQueryProvider.php b/src/GlobControllerQueryProvider.php index 15369715f2..69f66e0d70 100644 --- a/src/GlobControllerQueryProvider.php +++ b/src/GlobControllerQueryProvider.php @@ -58,6 +58,7 @@ private function getAggregateControllerQueryProvider(): AggregateControllerQuery */ private function getClassList(): array { + /** @phpstan-ignore assign.propertyType */ $this->classList ??= $this->classFinderComputedCache->compute( $this->classFinder, 'globQueryProvider', @@ -75,6 +76,7 @@ function (ReflectionClass $classReflection): string|null { static fn (array $entries) => array_values(array_filter($entries)), ); + /** @phpstan-ignore return.type */ return $this->classList; } diff --git a/src/Mappers/GlobTypeMapperCache.php b/src/Mappers/GlobTypeMapperCache.php index 3fae8c5d35..032f0f51c9 100644 --- a/src/Mappers/GlobTypeMapperCache.php +++ b/src/Mappers/GlobTypeMapperCache.php @@ -55,7 +55,7 @@ public function registerAnnotations(ReflectionClass|string $sourceClass, GlobAnn foreach ($globAnnotationsCache->getFactories() as $methodName => [$inputName, $inputClassName, $isDefault, $declaringClass]) { if ($isDefault) { if ($inputClassName !== null && isset($this->mapClassToFactory[$inputClassName])) { - throw DuplicateMappingException::createForFactory($inputClassName, $this->mapClassToFactory[$inputClassName][0], $this->mapClassToFactory[$inputClassName][1], $sourceClass, $methodName); + throw DuplicateMappingException::createForFactory($inputClassName, $this->mapClassToFactory[$inputClassName][0], $this->mapClassToFactory[$inputClassName][1], $className, $methodName); } } else { // If this is not the default factory, let's not map the class name to the factory. diff --git a/src/Mappers/Root/RootTypeMapperFactoryContext.php b/src/Mappers/Root/RootTypeMapperFactoryContext.php index 513fa25171..7786d6fdcf 100644 --- a/src/Mappers/Root/RootTypeMapperFactoryContext.php +++ b/src/Mappers/Root/RootTypeMapperFactoryContext.php @@ -7,6 +7,7 @@ use Psr\Container\ContainerInterface; use Psr\SimpleCache\CacheInterface; use TheCodingMachine\GraphQLite\AnnotationReader; +use TheCodingMachine\GraphQLite\Cache\ClassBoundCache; use TheCodingMachine\GraphQLite\Discovery\Cache\ClassFinderComputedCache; use TheCodingMachine\GraphQLite\Discovery\ClassFinder; use TheCodingMachine\GraphQLite\Mappers\RecursiveTypeMapperInterface; @@ -30,6 +31,7 @@ public function __construct( private readonly CacheInterface $cache, private readonly ClassFinder $classFinder, private readonly ClassFinderComputedCache $classFinderComputedCache, + private readonly ClassBoundCache $classBoundCache, ) { } @@ -77,4 +79,9 @@ public function getClassFinderComputedCache(): ClassFinderComputedCache { return $this->classFinderComputedCache; } + + public function getClassBoundCache(): ClassBoundCache + { + return $this->classBoundCache; + } } diff --git a/src/Reflection/DocBlock/CachedDocBlockContextFactory.php b/src/Reflection/DocBlock/CachedDocBlockContextFactory.php index ae90d9139b..e6c792189e 100644 --- a/src/Reflection/DocBlock/CachedDocBlockContextFactory.php +++ b/src/Reflection/DocBlock/CachedDocBlockContextFactory.php @@ -6,7 +6,9 @@ use phpDocumentor\Reflection\Types\Context; use ReflectionClass; -use Reflector; +use ReflectionClassConstant; +use ReflectionMethod; +use ReflectionProperty; use TheCodingMachine\GraphQLite\Cache\ClassBoundCache; class CachedDocBlockContextFactory implements DocBlockContextFactory @@ -18,7 +20,7 @@ public function __construct( { } - public function createFromReflector(Reflector $reflector): Context + public function createFromReflector(ReflectionClass|ReflectionMethod|ReflectionProperty|ReflectionClassConstant $reflector): Context { $reflector = $reflector instanceof ReflectionClass ? $reflector : $reflector->getDeclaringClass(); diff --git a/src/Reflection/DocBlock/CachedDocBlockFactory.php b/src/Reflection/DocBlock/CachedDocBlockFactory.php index 45e0673402..c4b802c1fb 100644 --- a/src/Reflection/DocBlock/CachedDocBlockFactory.php +++ b/src/Reflection/DocBlock/CachedDocBlockFactory.php @@ -6,7 +6,9 @@ use phpDocumentor\Reflection\DocBlock; use ReflectionClass; -use Reflector; +use ReflectionClassConstant; +use ReflectionMethod; +use ReflectionProperty; use TheCodingMachine\GraphQLite\Cache\ClassBoundCache; use function md5; @@ -23,7 +25,7 @@ public function __construct( { } - public function createFromReflector(Reflector $reflector): DocBlock + public function createFromReflector(ReflectionClass|ReflectionMethod|ReflectionProperty|ReflectionClassConstant $reflector): DocBlock { $class = $reflector instanceof ReflectionClass ? $reflector : $reflector->getDeclaringClass(); diff --git a/src/Reflection/DocBlock/DocBlockContextFactory.php b/src/Reflection/DocBlock/DocBlockContextFactory.php index ab8b0153a3..5e023e1bdd 100644 --- a/src/Reflection/DocBlock/DocBlockContextFactory.php +++ b/src/Reflection/DocBlock/DocBlockContextFactory.php @@ -5,9 +5,12 @@ namespace TheCodingMachine\GraphQLite\Reflection\DocBlock; use phpDocumentor\Reflection\Types\Context; -use Reflector; +use ReflectionClass; +use ReflectionClassConstant; +use ReflectionMethod; +use ReflectionProperty; interface DocBlockContextFactory { - public function createFromReflector(Reflector $reflector): Context; + public function createFromReflector(ReflectionClass|ReflectionMethod|ReflectionProperty|ReflectionClassConstant $reflector): Context; } diff --git a/src/Reflection/DocBlock/DocBlockFactory.php b/src/Reflection/DocBlock/DocBlockFactory.php index 4cfd5994cf..5ed7ededaa 100644 --- a/src/Reflection/DocBlock/DocBlockFactory.php +++ b/src/Reflection/DocBlock/DocBlockFactory.php @@ -5,9 +5,12 @@ namespace TheCodingMachine\GraphQLite\Reflection\DocBlock; use phpDocumentor\Reflection\DocBlock; -use Reflector; +use ReflectionClass; +use ReflectionClassConstant; +use ReflectionMethod; +use ReflectionProperty; interface DocBlockFactory { - public function createFromReflector(Reflector $reflector): DocBlock; + public function createFromReflector(ReflectionClass|ReflectionMethod|ReflectionProperty|ReflectionClassConstant $reflector): DocBlock; } diff --git a/src/Reflection/DocBlock/PhpDocumentorDocBlockContextFactory.php b/src/Reflection/DocBlock/PhpDocumentorDocBlockContextFactory.php index 1e532d7f1a..ff443c7a95 100644 --- a/src/Reflection/DocBlock/PhpDocumentorDocBlockContextFactory.php +++ b/src/Reflection/DocBlock/PhpDocumentorDocBlockContextFactory.php @@ -6,7 +6,10 @@ use phpDocumentor\Reflection\Types\Context; use phpDocumentor\Reflection\Types\ContextFactory as ContextFactoryConcrete; -use Reflector; +use ReflectionClass; +use ReflectionClassConstant; +use ReflectionMethod; +use ReflectionProperty; class PhpDocumentorDocBlockContextFactory implements DocBlockContextFactory { @@ -16,7 +19,7 @@ public function __construct( { } - public function createFromReflector(Reflector $reflector): Context + public function createFromReflector(ReflectionClass|ReflectionMethod|ReflectionProperty|ReflectionClassConstant $reflector): Context { return $this->contextFactory->createFromReflector($reflector); } diff --git a/src/Reflection/DocBlock/PhpDocumentorDocBlockFactory.php b/src/Reflection/DocBlock/PhpDocumentorDocBlockFactory.php index 8f82227489..eba3fd25ba 100644 --- a/src/Reflection/DocBlock/PhpDocumentorDocBlockFactory.php +++ b/src/Reflection/DocBlock/PhpDocumentorDocBlockFactory.php @@ -5,19 +5,22 @@ namespace TheCodingMachine\GraphQLite\Reflection\DocBlock; use phpDocumentor\Reflection\DocBlock; -use phpDocumentor\Reflection\DocBlockFactory as DocBlockFactoryConcrete; -use Reflector; +use phpDocumentor\Reflection\DocBlockFactoryInterface; +use ReflectionClass; +use ReflectionClassConstant; +use ReflectionMethod; +use ReflectionProperty; class PhpDocumentorDocBlockFactory implements DocBlockFactory { public function __construct( - private readonly DocBlockFactoryConcrete $docBlockFactory, + private readonly DocBlockFactoryInterface $docBlockFactory, private readonly DocBlockContextFactory $docBlockContextFactory, ) { } - public function createFromReflector(Reflector $reflector): DocBlock + public function createFromReflector(ReflectionClass|ReflectionMethod|ReflectionProperty|ReflectionClassConstant $reflector): DocBlock { $docblock = $reflector->getDocComment() ?: '/** */'; $context = $this->docBlockContextFactory->createFromReflector($reflector); diff --git a/src/SchemaFactory.php b/src/SchemaFactory.php index 70d68deef6..2f152cc22d 100644 --- a/src/SchemaFactory.php +++ b/src/SchemaFactory.php @@ -116,7 +116,7 @@ class SchemaFactory private NamingStrategyInterface|null $namingStrategy = null; - private FinderInterface|null $finder = null; + private ClassFinder|FinderInterface|null $finder = null; private SchemaConfig|null $schemaConfig = null; @@ -275,7 +275,7 @@ public function setSchemaConfig(SchemaConfig $schemaConfig): self return $this; } - public function setFinder(FinderInterface $finder): self + public function setFinder(ClassFinder|FinderInterface $finder): self { $this->finder = $finder; @@ -405,8 +405,9 @@ public function createSchema(): Schema $recursiveTypeMapper, $this->container, $namespacedCache, - $classFinder, - $classFinderComputedCache, + classFinder: $classFinder, + classFinderComputedCache: $classFinderComputedCache, + classBoundCache: $classBoundCache, ); $reversedRootTypeMapperFactories = array_reverse($this->rootTypeMapperFactories); @@ -528,6 +529,10 @@ classBoundCache: $classBoundCache, private function createClassFinder(): ClassFinder { + if ($this->finder instanceof ClassFinder) { + return $this->finder; + } + // When no namespaces are specified, class finder uses all available namespaces to discover classes. // While this is technically okay, it doesn't follow SchemaFactory's semantics that allow it's // users to manually specify classes (see SchemaFactory::testCreateSchemaOnlyWithFactories()), @@ -541,7 +546,9 @@ private function createClassFinder(): ClassFinder // Because this finder may be iterated more than once, we need to make // sure that the filesystem is only hit once in the lifetime of the application, // as that may be expensive for larger projects or non-native filesystems. - $finder = $finder->withFileFinder(new CachedFileFinder(new DefaultFileFinder(), new ArrayAdapter())); + if ($finder instanceof ComposerFinder) { + $finder = $finder->withFileFinder(new CachedFileFinder(new DefaultFileFinder(), new ArrayAdapter())); + } foreach ($this->namespaces as $namespace) { $finder = $finder->inNamespace($namespace); diff --git a/tests/RootTypeMapperFactoryContextTest.php b/tests/RootTypeMapperFactoryContextTest.php index 2e8739a333..a4d99fae71 100644 --- a/tests/RootTypeMapperFactoryContextTest.php +++ b/tests/RootTypeMapperFactoryContextTest.php @@ -6,6 +6,7 @@ use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\Cache\Psr16Cache; use Symfony\Component\Cache\Simple\ArrayCache; +use TheCodingMachine\GraphQLite\Cache\HardClassBoundCache; use TheCodingMachine\GraphQLite\Containers\EmptyContainer; use TheCodingMachine\GraphQLite\Mappers\Root\RootTypeMapperFactoryContext; use TheCodingMachine\GraphQLite\Utils\Namespaces\NS; @@ -20,6 +21,8 @@ public function testContext(): void $container = new EmptyContainer(); $arrayCache = new Psr16Cache(new ArrayAdapter()); $classFinder = $this->getClassFinder('namespace'); + $classFinderComputedCache = $this->getClassFinderComputedCache(); + $classBoundCache = new HardClassBoundCache($arrayCache); $context = new RootTypeMapperFactoryContext( $this->getAnnotationReader(), @@ -30,7 +33,8 @@ public function testContext(): void $container, $arrayCache, $classFinder, - self::GLOB_TTL_SECONDS + $classFinderComputedCache, + $classBoundCache, ); $this->assertSame($this->getAnnotationReader(), $context->getAnnotationReader()); @@ -41,7 +45,7 @@ public function testContext(): void $this->assertSame($container, $context->getContainer()); $this->assertSame($arrayCache, $context->getCache()); $this->assertSame($classFinder, $context->getClassFinder()); - $this->assertSame(self::GLOB_TTL_SECONDS, $context->getGlobTTL()); - $this->assertNull($context->getMapTTL()); + $this->assertSame($classFinderComputedCache, $context->getClassFinderComputedCache()); + $this->assertSame($classBoundCache, $context->getClassBoundCache()); } } From e0880c49d3bcbb0f94e9f2f767a58de962ab8f53 Mon Sep 17 00:00:00 2001 From: Oleksandr Prypkhan Date: Tue, 27 Aug 2024 21:55:19 +0300 Subject: [PATCH 16/28] Fix all failing tests --- src/AggregateControllerQueryProvider.php | 32 +++++++++-------- tests/FieldsBuilderTest.php | 10 ++++-- tests/Mappers/ClassFinderTypeMapperTest.php | 14 ++++---- tests/Mappers/Parameters/TypeMapperTest.php | 1 + .../Root/MyCLabsEnumTypeMapperTest.php | 2 +- .../Mappers/StaticClassListTypeMapperTest.php | 31 ---------------- .../Reflection/CachedDocBlockFactoryTest.php | 36 +++++++++++++------ 7 files changed, 59 insertions(+), 67 deletions(-) delete mode 100644 tests/Mappers/StaticClassListTypeMapperTest.php diff --git a/src/AggregateControllerQueryProvider.php b/src/AggregateControllerQueryProvider.php index 432e8623a7..909e2a9566 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 a640956102..d080a8f0da 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 cb5831e500..1657c271c0 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 f3f4fd374e..00ce2aef18 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 ecd711c945..8663d4f8a9 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 c77beb3929..0000000000 --- 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 47168df8a8..d58f575938 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); } } From 647277547df6e6f91cd10bfad5afdc7212b8a119 Mon Sep 17 00:00:00 2001 From: Oleksandr Prypkhan Date: Tue, 27 Aug 2024 22:00:51 +0300 Subject: [PATCH 17/28] Fix one failing test on CI --- src/AggregateControllerQueryProvider.php | 13 +++++++++---- src/Reflection/DocBlock/DocBlockFactory.php | 3 +++ tests/Reflection/CachedDocBlockFactoryTest.php | 4 ++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/AggregateControllerQueryProvider.php b/src/AggregateControllerQueryProvider.php index 909e2a9566..41a3d07027 100644 --- a/src/AggregateControllerQueryProvider.php +++ b/src/AggregateControllerQueryProvider.php @@ -14,6 +14,7 @@ use function array_values; use function assert; use function count; +use function sort; /** * A query provider that looks into all controllers of your application to fetch queries. @@ -90,7 +91,8 @@ private function flattenList(array $list): array // We have an issue, let's detect the duplicate $queriesByName = []; - $duplicate = null; + $duplicateClasses = null; + $duplicateQueryName = null; foreach ($list as $class => $queries) { foreach ($queries as $query => $field) { @@ -102,12 +104,15 @@ private function flattenList(array $list): array continue; } - $duplicate = [$duplicatedClass, $class, $query]; + $duplicateClasses = [$duplicatedClass, $class]; + $duplicateQueryName = $query; } } - assert($duplicate !== null); + assert($duplicateClasses !== null && $duplicateQueryName !== null); - throw DuplicateMappingException::createForQueryInTwoControllers($duplicate[0], $duplicate[1], $duplicate[2]); + sort($duplicateClasses); + + throw DuplicateMappingException::createForQueryInTwoControllers($duplicateClasses[0], $duplicateClasses[1], $duplicateQueryName); } } diff --git a/src/Reflection/DocBlock/DocBlockFactory.php b/src/Reflection/DocBlock/DocBlockFactory.php index 5ed7ededaa..413cf2d475 100644 --- a/src/Reflection/DocBlock/DocBlockFactory.php +++ b/src/Reflection/DocBlock/DocBlockFactory.php @@ -12,5 +12,8 @@ interface DocBlockFactory { + /** + * Fetches a DocBlock object from a ReflectionMethod + */ public function createFromReflector(ReflectionClass|ReflectionMethod|ReflectionProperty|ReflectionClassConstant $reflector): DocBlock; } diff --git a/tests/Reflection/CachedDocBlockFactoryTest.php b/tests/Reflection/CachedDocBlockFactoryTest.php index d58f575938..8438a41fd1 100644 --- a/tests/Reflection/CachedDocBlockFactoryTest.php +++ b/tests/Reflection/CachedDocBlockFactoryTest.php @@ -28,10 +28,10 @@ public function testGetDocBlock(): void ) ); - $refMethod = new ReflectionMethod(AnnotationReader::class, 'getMethodAnnotation'); + $refMethod = new ReflectionMethod(DocBlockFactory::class, 'createFromReflector'); $docBlock = $cachedDocBlockFactory->createFromReflector($refMethod); - $this->assertSame('Returns a method annotation and handles correctly errors.', $docBlock->getSummary()); + $this->assertSame('Fetches a DocBlock object from a ReflectionMethod', $docBlock->getSummary()); $docBlock2 = $cachedDocBlockFactory->createFromReflector($refMethod); $this->assertSame($docBlock2, $docBlock); From d20853c6897f1e634553f85543516ab7f4ea7534 Mon Sep 17 00:00:00 2001 From: Oleksandr Prypkhan Date: Tue, 27 Aug 2024 22:04:29 +0300 Subject: [PATCH 18/28] Fix one failing test on CI, again --- tests/Reflection/CachedDocBlockFactoryTest.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/Reflection/CachedDocBlockFactoryTest.php b/tests/Reflection/CachedDocBlockFactoryTest.php index 8438a41fd1..76c2e16223 100644 --- a/tests/Reflection/CachedDocBlockFactoryTest.php +++ b/tests/Reflection/CachedDocBlockFactoryTest.php @@ -8,7 +8,6 @@ use ReflectionMethod; use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\Cache\Psr16Cache; -use TheCodingMachine\GraphQLite\AnnotationReader; use TheCodingMachine\GraphQLite\Cache\HardClassBoundCache; use TheCodingMachine\GraphQLite\Reflection\DocBlock\CachedDocBlockFactory; use TheCodingMachine\GraphQLite\Reflection\DocBlock\PhpDocumentorDocBlockContextFactory; @@ -28,7 +27,7 @@ public function testGetDocBlock(): void ) ); - $refMethod = new ReflectionMethod(DocBlockFactory::class, 'createFromReflector'); + $refMethod = new ReflectionMethod(DocBlock\DocBlockFactory::class, 'createFromReflector'); $docBlock = $cachedDocBlockFactory->createFromReflector($refMethod); $this->assertSame('Fetches a DocBlock object from a ReflectionMethod', $docBlock->getSummary()); From 294c7333ed8c9e51ac7885146f353d85868460f2 Mon Sep 17 00:00:00 2001 From: Oleksandr Prypkhan Date: Tue, 27 Aug 2024 22:16:05 +0300 Subject: [PATCH 19/28] Fix --prefer-lowest tests --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 5a5f5e6067..6063ab0c19 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,7 @@ "myclabs/php-enum": "^1.6.6", "php-coveralls/php-coveralls": "^2.1", "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.9", + "phpstan/phpstan": "^1.11", "phpunit/phpunit": "^10.1 || ^11.0", "symfony/var-dumper": "^5.4 || ^6.0 || ^7", "thecodingmachine/phpstan-strict-rules": "^1.0" From 5830002b8ad5f75ab06ccc61fe39007cec97b257 Mon Sep 17 00:00:00 2001 From: Oleksandr Prypkhan Date: Mon, 2 Sep 2024 16:49:08 +0300 Subject: [PATCH 20/28] Some simplifications and tests --- examples/no-framework/index.php | 3 +- src/Cache/ClassBoundCache.php | 7 +- .../{ClassSnapshot.php => FilesSnapshot.php} | 45 +++++++---- src/Cache/HardClassBoundCache.php | 36 --------- ...dCache.php => SnapshotClassBoundCache.php} | 10 ++- ...p => SnapshotClassFinderComputedCache.php} | 10 +-- src/SchemaFactory.php | 13 ++-- tests/AbstractQueryProvider.php | 5 +- tests/Cache/FilesSnapshotTest.php | 75 +++++++++++++++++++ tests/Cache/SnapshotClassBoundCacheTest.php | 59 +++++++++++++++ tests/FactoryContextTest.php | 5 +- tests/FieldsBuilderTest.php | 1 - tests/Integration/IntegrationTestCase.php | 5 +- .../Reflection/CachedDocBlockFactoryTest.php | 7 +- tests/RootTypeMapperFactoryContextTest.php | 7 +- 15 files changed, 206 insertions(+), 82 deletions(-) rename src/Cache/{ClassSnapshot.php => FilesSnapshot.php} (50%) delete mode 100644 src/Cache/HardClassBoundCache.php rename src/Cache/{FileModificationClassBoundCache.php => SnapshotClassBoundCache.php} (67%) rename src/Discovery/Cache/{FileModificationClassFinderComputedCache.php => SnapshotClassFinderComputedCache.php} (91%) create mode 100644 tests/Cache/FilesSnapshotTest.php create mode 100644 tests/Cache/SnapshotClassBoundCacheTest.php diff --git a/examples/no-framework/index.php b/examples/no-framework/index.php index b8f43ee18b..3db0b0fe20 100644 --- a/examples/no-framework/index.php +++ b/examples/no-framework/index.php @@ -24,8 +24,7 @@ ]); $factory = new SchemaFactory($cache, $container); -$factory->addControllerNamespace('App\\Controllers') - ->addTypeNamespace('App'); +$factory->addNamespace('App'); $schema = $factory->createSchema(); diff --git a/src/Cache/ClassBoundCache.php b/src/Cache/ClassBoundCache.php index 4e88b10e68..fc812754fa 100644 --- a/src/Cache/ClassBoundCache.php +++ b/src/Cache/ClassBoundCache.php @@ -9,7 +9,6 @@ interface ClassBoundCache { /** - * @param string $key An optional key to differentiate between cache items attached to the same class. * @param callable(): TReturn $resolver * * @return TReturn @@ -18,8 +17,8 @@ interface ClassBoundCache */ public function get( ReflectionClass $reflectionClass, - callable $resolver, - string $key = '', - bool $useInheritance = false, + callable $resolver, + string $key, + bool $withInheritance = false, ): mixed; } diff --git a/src/Cache/ClassSnapshot.php b/src/Cache/FilesSnapshot.php similarity index 50% rename from src/Cache/ClassSnapshot.php rename to src/Cache/FilesSnapshot.php index 83f686f05f..36038548c4 100644 --- a/src/Cache/ClassSnapshot.php +++ b/src/Cache/FilesSnapshot.php @@ -9,24 +9,43 @@ use function array_merge; use function Safe\filemtime; -class ClassSnapshot +class FilesSnapshot { /** @param array $dependencies */ - public function __construct( + private function __construct( private readonly array $dependencies, ) { } - public static function fromReflection(ReflectionClass $class, bool $useInheritance = false): self + /** + * @param list $files + */ + public static function for(array $files): self { - return new self( - self::dependencies($class, $useInheritance), + $dependencies = []; + + foreach (array_unique($files) as $file) { + $dependencies[$file] = filemtime($file); + } + + return new self($dependencies); + } + + public static function forClass(ReflectionClass $class, bool $withInheritance = false): self + { + return self::for( + self::dependencies($class, $withInheritance), ); } - /** @return array */ - private static function dependencies(ReflectionClass $class, bool $useInheritance = false): array + public static function alwaysUnchanged(): self + { + return new self([]); + } + + /** @return list */ + private static function dependencies(ReflectionClass $class, bool $withInheritance = false): array { $filename = $class->getFileName(); @@ -35,22 +54,22 @@ private static function dependencies(ReflectionClass $class, bool $useInheritanc return []; } - $files = [$filename => filemtime($filename)]; + $files = [$filename]; - if (! $useInheritance) { - return []; + if (! $withInheritance) { + return $files; } if ($class->getParentClass() !== false) { - $files = array_merge($files, self::dependencies($class->getParentClass())); + $files = [...$files, ...self::dependencies($class->getParentClass(), $withInheritance)]; } foreach ($class->getTraits() as $trait) { - $files = array_merge($files, self::dependencies($trait)); + $files = [...$files, ...self::dependencies($trait, $withInheritance)]; } foreach ($class->getInterfaces() as $interface) { - $files = array_merge($files, self::dependencies($interface)); + $files = [...$files, ...self::dependencies($interface, $withInheritance)]; } return $files; diff --git a/src/Cache/HardClassBoundCache.php b/src/Cache/HardClassBoundCache.php deleted file mode 100644 index abc31cd086..0000000000 --- a/src/Cache/HardClassBoundCache.php +++ /dev/null @@ -1,36 +0,0 @@ -getName() . '__' . $key; - $cacheKey = str_replace(['\\', '{', '}', '(', ')', '/', '@', ':'], '_', $cacheKey); - - $item = $this->cache->get($cacheKey); - - if ($item !== null) { - return $item; - } - - $item = $resolver(); - - $this->cache->set($cacheKey, $item); - - return $item; - } -} diff --git a/src/Cache/FileModificationClassBoundCache.php b/src/Cache/SnapshotClassBoundCache.php similarity index 67% rename from src/Cache/FileModificationClassBoundCache.php rename to src/Cache/SnapshotClassBoundCache.php index 0638709c3b..6774f1eceb 100644 --- a/src/Cache/FileModificationClassBoundCache.php +++ b/src/Cache/SnapshotClassBoundCache.php @@ -9,14 +9,18 @@ use function str_replace; -class FileModificationClassBoundCache implements ClassBoundCache +class SnapshotClassBoundCache implements ClassBoundCache { + /** + * @param callable(ReflectionClass, bool $withInheritance): FilesSnapshot $filesSnapshotFactory + */ public function __construct( private readonly CacheInterface $cache, + private readonly mixed $filesSnapshotFactory, ) { } - public function get(ReflectionClass $reflectionClass, callable $resolver, string $key = '', bool $useInheritance = false): mixed + public function get(ReflectionClass $reflectionClass, callable $resolver, string $key = '', bool $withInheritance = false): mixed { $cacheKey = $reflectionClass->getName() . '__' . $key; $cacheKey = str_replace(['\\', '{', '}', '(', ')', '/', '@', ':'], '_', $cacheKey); @@ -29,7 +33,7 @@ public function get(ReflectionClass $reflectionClass, callable $resolver, string $item = [ 'data' => $resolver(), - 'snapshot' => ClassSnapshot::fromReflection($reflectionClass), + 'snapshot' => ($this->filesSnapshotFactory)($reflectionClass, $withInheritance), ]; $this->cache->set($cacheKey, $item); diff --git a/src/Discovery/Cache/FileModificationClassFinderComputedCache.php b/src/Discovery/Cache/SnapshotClassFinderComputedCache.php similarity index 91% rename from src/Discovery/Cache/FileModificationClassFinderComputedCache.php rename to src/Discovery/Cache/SnapshotClassFinderComputedCache.php index fb8b7cb24b..68279a02eb 100644 --- a/src/Discovery/Cache/FileModificationClassFinderComputedCache.php +++ b/src/Discovery/Cache/SnapshotClassFinderComputedCache.php @@ -6,7 +6,7 @@ use Psr\SimpleCache\CacheInterface; use ReflectionClass; -use TheCodingMachine\GraphQLite\Cache\ClassSnapshot; +use TheCodingMachine\GraphQLite\Cache\FilesSnapshot; use TheCodingMachine\GraphQLite\Discovery\ClassFinder; use function Safe\filemtime; @@ -27,7 +27,7 @@ * - if no cache exists, it iterates over the whole class finder and returns all reflection that match the filter * - if cache does exist, it only iterates over changed classes */ -class FileModificationClassFinderComputedCache implements ClassFinderComputedCache +class SnapshotClassFinderComputedCache implements ClassFinderComputedCache { public function __construct( private readonly CacheInterface $cache, @@ -78,7 +78,7 @@ private function entries( $changed = false; $classFinder = $classFinder->withPathFilter(static function (string $filename) use (&$entries, &$result, &$changed, $previousEntries) { - /** @var array{ data: TEntry, dependencies: ClassSnapshot, matching: bool } $entry */ + /** @var array{ data: TEntry, dependencies: FilesSnapshot, matching: bool } $entry */ $entry = $previousEntries[$filename] ?? null; // If there's no entry in cache for this filename (new file or previously uncached), @@ -90,7 +90,7 @@ private function entries( // So to avoid iterating over these files again, we'll mark them as non-matching. // If they are matching, it'll be overwritten in the `foreach` loop below. $entries[$filename] = [ - 'dependencies' => new ClassSnapshot([$filename => filemtime($filename)]), + 'dependencies' => FilesSnapshot::for([$filename]), 'matching' => false, ]; @@ -113,7 +113,7 @@ private function entries( $result[$filename] = $map($classReflection); $entries[$filename] = [ - 'dependencies' => ClassSnapshot::fromReflection($classReflection), + 'dependencies' => FilesSnapshot::forClass($classReflection, true), 'data' => $result[$filename], 'matching' => true, ]; diff --git a/src/SchemaFactory.php b/src/SchemaFactory.php index 2f152cc22d..1d24095acc 100644 --- a/src/SchemaFactory.php +++ b/src/SchemaFactory.php @@ -19,9 +19,9 @@ use Symfony\Component\Cache\Adapter\Psr16Adapter; use Symfony\Component\ExpressionLanguage\ExpressionLanguage; use TheCodingMachine\GraphQLite\Cache\ClassBoundCache; -use TheCodingMachine\GraphQLite\Cache\FileModificationClassBoundCache; -use TheCodingMachine\GraphQLite\Cache\HardClassBoundCache; -use TheCodingMachine\GraphQLite\Discovery\Cache\FileModificationClassFinderComputedCache; +use TheCodingMachine\GraphQLite\Cache\FilesSnapshot; +use TheCodingMachine\GraphQLite\Cache\SnapshotClassBoundCache; +use TheCodingMachine\GraphQLite\Discovery\Cache\SnapshotClassFinderComputedCache; use TheCodingMachine\GraphQLite\Discovery\Cache\HardClassFinderComputedCache; use TheCodingMachine\GraphQLite\Discovery\ClassFinder; use TheCodingMachine\GraphQLite\Discovery\KcsClassFinder; @@ -351,13 +351,16 @@ public function createSchema(): Schema $authorizationService = $this->authorizationService ?: new FailAuthorizationService(); $typeResolver = new TypeResolver(); $namespacedCache = new NamespacedCache($this->cache); - $classBoundCache = $this->classBoundCache ?: ($this->devMode ? new FileModificationClassBoundCache($this->cache) : new HardClassBoundCache($this->cache)); + $classBoundCache = $this->classBoundCache ?: new SnapshotClassBoundCache( + $this->cache, + $this->devMode ? FilesSnapshot::forClass(...) : FilesSnapshot::alwaysUnchanged(...), + ); [$docBlockFactory, $docBlockContextFactory] = $this->createDocBlockFactory($classBoundCache); $namingStrategy = $this->namingStrategy ?: new NamingStrategy(); $typeRegistry = new TypeRegistry(); $classFinder = $this->createClassFinder(); $classFinderComputedCache = $this->devMode ? - new FileModificationClassFinderComputedCache($this->cache) : + new SnapshotClassFinderComputedCache($this->cache) : new HardClassFinderComputedCache($this->cache); $expressionLanguage = $this->expressionLanguage ?: new ExpressionLanguage($symfonyCache); diff --git a/tests/AbstractQueryProvider.php b/tests/AbstractQueryProvider.php index b1fddda296..3598d2d03a 100644 --- a/tests/AbstractQueryProvider.php +++ b/tests/AbstractQueryProvider.php @@ -22,7 +22,8 @@ use Symfony\Component\Cache\Psr16Cache; use Symfony\Component\ExpressionLanguage\ExpressionLanguage; use TheCodingMachine\GraphQLite\Cache\ClassBoundCache; -use TheCodingMachine\GraphQLite\Cache\HardClassBoundCache; +use TheCodingMachine\GraphQLite\Cache\FilesSnapshot; +use TheCodingMachine\GraphQLite\Cache\SnapshotClassBoundCache; use TheCodingMachine\GraphQLite\Containers\BasicAutoWiringContainer; use TheCodingMachine\GraphQLite\Containers\EmptyContainer; use TheCodingMachine\GraphQLite\Containers\LazyContainer; @@ -303,7 +304,7 @@ private function getClassBoundCache(): ClassBoundCache $arrayAdapter->setLogger(new ExceptionLogger()); $psr16Cache = new Psr16Cache($arrayAdapter); - return new HardClassBoundCache($psr16Cache); + return new SnapshotClassBoundCache($psr16Cache, FilesSnapshot::alwaysUnchanged()); } protected function buildFieldsBuilder(): FieldsBuilder diff --git a/tests/Cache/FilesSnapshotTest.php b/tests/Cache/FilesSnapshotTest.php new file mode 100644 index 0000000000..6f22e5863a --- /dev/null +++ b/tests/Cache/FilesSnapshotTest.php @@ -0,0 +1,75 @@ +changed()); + + // Make sure it serializes properly + /** @var FilesSnapshot $snapshot */ + $snapshot = unserialize(serialize($snapshot)); + + self::assertFalse($snapshot->changed()); + + $this->touch($fooReflection->getFileName()); + + self::assertTrue($snapshot->changed()); + } + + public function testDoesNotTrackChangesInSuperTypesWithoutUsingInheritance(): void + { + $fooReflection = new \ReflectionClass(FooType::class); + $snapshot = FilesSnapshot::forClass($fooReflection); + + self::assertFalse($snapshot->changed()); + + $this->touch($fooReflection->getParentClass()->getFileName()); + + self::assertFalse($snapshot->changed()); + } + + public function testTracksChangesInSuperTypesUsingInheritance(): void + { + $fooReflection = new \ReflectionClass(FooType::class); + $snapshot = FilesSnapshot::forClass($fooReflection, true); + + self::assertFalse($snapshot->changed()); + + $this->touch($fooReflection->getParentClass()->getFileName()); + + self::assertTrue($snapshot->changed()); + } + + public function testTracksChangesInFile(): void + { + $fileName = (new \ReflectionClass(FooType::class))->getFileName(); + $snapshot = FilesSnapshot::for([$fileName]); + + self::assertFalse($snapshot->changed()); + + $this->touch($fileName); + + self::assertTrue($snapshot->changed()); + } + + private function touch(string $fileName): void + { + \Safe\touch($fileName, \Safe\filemtime($fileName) + 1); + clearstatcache(); + } +} \ No newline at end of file diff --git a/tests/Cache/SnapshotClassBoundCacheTest.php b/tests/Cache/SnapshotClassBoundCacheTest.php new file mode 100644 index 0000000000..8e242cec98 --- /dev/null +++ b/tests/Cache/SnapshotClassBoundCacheTest.php @@ -0,0 +1,59 @@ +get($fooReflection, fn () => 'foo_key', 'key', true); + + self::assertSame('foo_key', $fooKeyResult); + self::assertSame('foo_key', $classBoundCache->get($fooReflection, fn () => self::fail('should not be called'), 'key', true)); + + $fooDifferentKeyResult = $classBoundCache->get($fooReflection, fn () => 'foo_different_key', 'different_key', true); + + self::assertSame('foo_different_key', $fooDifferentKeyResult); + self::assertSame('foo_different_key', $classBoundCache->get($fooReflection, fn () => self::fail('should not be called'), 'different_key', true)); + + $barReflection = new \ReflectionClass(NoTypeAnnotation::class); + $barKeyResult = $classBoundCache->get($barReflection, fn () => 'bar_key', 'key', true); + + self::assertSame('bar_key', $barKeyResult); + self::assertSame('bar_key', $classBoundCache->get($barReflection, fn () => self::fail('should not be called'), 'key', true)); + + self::assertCount(3, $arrayCache->getValues()); + + $this->touch($fooReflection->getParentClass()->getFileName()); + + self::assertSame( + 'foo_key_updated', + $classBoundCache->get($fooReflection, fn () => 'foo_key_updated', 'key', true) + ); + } + + private function touch(string $fileName): void + { + \Safe\touch($fileName, \Safe\filemtime($fileName) + 1); + clearstatcache(); + } +} \ No newline at end of file diff --git a/tests/FactoryContextTest.php b/tests/FactoryContextTest.php index 76b3f644f7..01066bbc3c 100644 --- a/tests/FactoryContextTest.php +++ b/tests/FactoryContextTest.php @@ -4,7 +4,8 @@ use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\Cache\Psr16Cache; -use TheCodingMachine\GraphQLite\Cache\HardClassBoundCache; +use TheCodingMachine\GraphQLite\Cache\FilesSnapshot; +use TheCodingMachine\GraphQLite\Cache\SnapshotClassBoundCache; use TheCodingMachine\GraphQLite\Containers\EmptyContainer; use TheCodingMachine\GraphQLite\Discovery\Cache\HardClassFinderComputedCache; use TheCodingMachine\GraphQLite\Discovery\StaticClassFinder; @@ -21,7 +22,7 @@ public function testContext(): void $arrayCache = new Psr16Cache(new ArrayAdapter()); $classFinder = new StaticClassFinder([]); $classFinderComputedCache = new HardClassFinderComputedCache($arrayCache); - $classBoundCache = new HardClassBoundCache($arrayCache); + $classBoundCache = new SnapshotClassBoundCache($arrayCache, FilesSnapshot::alwaysUnchanged()); $validator = new Validator(); $context = new FactoryContext( diff --git a/tests/FieldsBuilderTest.php b/tests/FieldsBuilderTest.php index d080a8f0da..382c6575dd 100644 --- a/tests/FieldsBuilderTest.php +++ b/tests/FieldsBuilderTest.php @@ -22,7 +22,6 @@ 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; diff --git a/tests/Integration/IntegrationTestCase.php b/tests/Integration/IntegrationTestCase.php index b9b9b005e4..0a4911bd98 100644 --- a/tests/Integration/IntegrationTestCase.php +++ b/tests/Integration/IntegrationTestCase.php @@ -16,7 +16,8 @@ use TheCodingMachine\GraphQLite\AggregateQueryProvider; use TheCodingMachine\GraphQLite\AnnotationReader; use TheCodingMachine\GraphQLite\Cache\ClassBoundCache; -use TheCodingMachine\GraphQLite\Cache\HardClassBoundCache; +use TheCodingMachine\GraphQLite\Cache\FilesSnapshot; +use TheCodingMachine\GraphQLite\Cache\SnapshotClassBoundCache; use TheCodingMachine\GraphQLite\Containers\BasicAutoWiringContainer; use TheCodingMachine\GraphQLite\Containers\EmptyContainer; use TheCodingMachine\GraphQLite\Containers\LazyContainer; @@ -284,7 +285,7 @@ public function createContainer(array $overloadedServices = []): ContainerInterf $arrayAdapter->setLogger(new ExceptionLogger()); $psr16Cache = new Psr16Cache($arrayAdapter); - return new HardClassBoundCache($psr16Cache); + return new SnapshotClassBoundCache($psr16Cache, FilesSnapshot::alwaysUnchanged()); }, DocBlockFactory::class => static function (ContainerInterface $container) { return new CachedDocBlockFactory( diff --git a/tests/Reflection/CachedDocBlockFactoryTest.php b/tests/Reflection/CachedDocBlockFactoryTest.php index 76c2e16223..3eb9b88149 100644 --- a/tests/Reflection/CachedDocBlockFactoryTest.php +++ b/tests/Reflection/CachedDocBlockFactoryTest.php @@ -8,7 +8,8 @@ use ReflectionMethod; use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\Cache\Psr16Cache; -use TheCodingMachine\GraphQLite\Cache\HardClassBoundCache; +use TheCodingMachine\GraphQLite\Cache\FilesSnapshot; +use TheCodingMachine\GraphQLite\Cache\SnapshotClassBoundCache; use TheCodingMachine\GraphQLite\Reflection\DocBlock\CachedDocBlockFactory; use TheCodingMachine\GraphQLite\Reflection\DocBlock\PhpDocumentorDocBlockContextFactory; use TheCodingMachine\GraphQLite\Reflection\DocBlock\PhpDocumentorDocBlockFactory; @@ -20,7 +21,7 @@ public function testGetDocBlock(): void { $arrayCache = new Psr16Cache(new ArrayAdapter(storeSerialized: false)); $cachedDocBlockFactory = new CachedDocBlockFactory( - new HardClassBoundCache($arrayCache), + new SnapshotClassBoundCache($arrayCache, FilesSnapshot::alwaysUnchanged()), new PhpDocumentorDocBlockFactory( DocBlockFactory::createInstance(), new PhpDocumentorDocBlockContextFactory(new ContextFactory()), @@ -35,7 +36,7 @@ public function testGetDocBlock(): void $this->assertSame($docBlock2, $docBlock); $newCachedDocBlockFactory = new CachedDocBlockFactory( - new HardClassBoundCache($arrayCache), + new SnapshotClassBoundCache($arrayCache, FilesSnapshot::alwaysUnchanged()), new PhpDocumentorDocBlockFactory( DocBlockFactory::createInstance(), new PhpDocumentorDocBlockContextFactory(new ContextFactory()), diff --git a/tests/RootTypeMapperFactoryContextTest.php b/tests/RootTypeMapperFactoryContextTest.php index a4d99fae71..16dedea04c 100644 --- a/tests/RootTypeMapperFactoryContextTest.php +++ b/tests/RootTypeMapperFactoryContextTest.php @@ -5,11 +5,10 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\Cache\Psr16Cache; -use Symfony\Component\Cache\Simple\ArrayCache; -use TheCodingMachine\GraphQLite\Cache\HardClassBoundCache; +use TheCodingMachine\GraphQLite\Cache\FilesSnapshot; +use TheCodingMachine\GraphQLite\Cache\SnapshotClassBoundCache; use TheCodingMachine\GraphQLite\Containers\EmptyContainer; use TheCodingMachine\GraphQLite\Mappers\Root\RootTypeMapperFactoryContext; -use TheCodingMachine\GraphQLite\Utils\Namespaces\NS; class RootTypeMapperFactoryContextTest extends AbstractQueryProvider { @@ -22,7 +21,7 @@ public function testContext(): void $arrayCache = new Psr16Cache(new ArrayAdapter()); $classFinder = $this->getClassFinder('namespace'); $classFinderComputedCache = $this->getClassFinderComputedCache(); - $classBoundCache = new HardClassBoundCache($arrayCache); + $classBoundCache = new SnapshotClassBoundCache($arrayCache, FilesSnapshot::alwaysUnchanged()); $context = new RootTypeMapperFactoryContext( $this->getAnnotationReader(), From 87a992b83c2e1ed45fbf4ec0642a44d556cc3c3d Mon Sep 17 00:00:00 2001 From: Oleksandr Prypkhan Date: Mon, 2 Sep 2024 21:38:53 +0300 Subject: [PATCH 21/28] Simplify cached doc blocks --- src/FieldsBuilder.php | 18 +++++----- src/Mappers/Parameters/TypeHandler.php | 2 +- src/Mappers/Root/EnumTypeMapper.php | 4 +-- .../DocBlock/CachedDocBlockContextFactory.php | 33 ------------------- .../DocBlock/CachedDocBlockFactory.php | 20 +++++++++-- .../DocBlock/DocBlockContextFactory.php | 16 --------- src/Reflection/DocBlock/DocBlockFactory.php | 8 ++++- .../PhpDocumentorDocBlockContextFactory.php | 26 --------------- .../DocBlock/PhpDocumentorDocBlockFactory.php | 29 +++++++++++++--- src/SchemaFactory.php | 27 +++------------ tests/AbstractQueryProvider.php | 20 ++--------- tests/FactoryContextTest.php | 2 +- tests/FieldsBuilderTest.php | 3 -- tests/Integration/IntegrationTestCase.php | 20 ++--------- tests/Mappers/Parameters/TypeMapperTest.php | 12 +++---- .../Reflection/CachedDocBlockFactoryTest.php | 26 +++++---------- tests/RootTypeMapperFactoryContextTest.php | 2 +- 17 files changed, 85 insertions(+), 183 deletions(-) delete mode 100644 src/Reflection/DocBlock/CachedDocBlockContextFactory.php delete mode 100644 src/Reflection/DocBlock/DocBlockContextFactory.php delete mode 100644 src/Reflection/DocBlock/PhpDocumentorDocBlockContextFactory.php diff --git a/src/FieldsBuilder.php b/src/FieldsBuilder.php index 3708c3426d..d8ab99cc6b 100644 --- a/src/FieldsBuilder.php +++ b/src/FieldsBuilder.php @@ -46,7 +46,6 @@ use TheCodingMachine\GraphQLite\Parameters\InputTypeParameterInterface; use TheCodingMachine\GraphQLite\Parameters\ParameterInterface; use TheCodingMachine\GraphQLite\Parameters\PrefetchDataParameter; -use TheCodingMachine\GraphQLite\Reflection\DocBlock\DocBlockContextFactory; use TheCodingMachine\GraphQLite\Reflection\DocBlock\DocBlockFactory; use TheCodingMachine\GraphQLite\Types\ArgumentResolver; use TheCodingMachine\GraphQLite\Types\MutableObjectType; @@ -86,7 +85,6 @@ public function __construct( private readonly ArgumentResolver $argumentResolver, private readonly TypeResolver $typeResolver, private readonly DocBlockFactory $docBlockFactory, - private readonly DocBlockContextFactory $docBlockContextFactory, private readonly NamingStrategyInterface $namingStrategy, private readonly RootTypeMapperInterface $rootTypeMapper, private readonly ParameterMiddlewareInterface $parameterMapper, @@ -311,7 +309,7 @@ public function getSelfFields(string $className, string|null $typeName = null): */ public function getParameters(ReflectionMethod $refMethod, int $skip = 0): array { - $docBlockObj = $this->docBlockFactory->createFromReflector($refMethod); + $docBlockObj = $this->docBlockFactory->create($refMethod); //$docBlockComment = $docBlockObj->getSummary()."\n".$docBlockObj->getDescription()->render(); $parameters = array_slice($refMethod->getParameters(), $skip); @@ -457,7 +455,7 @@ private function getFieldsByMethodAnnotations( $description = $queryAnnotation->getDescription(); } - $docBlockObj = $this->docBlockFactory->createFromReflector($refMethod); + $docBlockObj = $this->docBlockFactory->create($refMethod); $name = $queryAnnotation->getName() ?: $this->namingStrategy->getFieldNameFromMethodName($methodName); @@ -567,7 +565,7 @@ private function getFieldsByPropertyAnnotations( $description = $queryAnnotation->getDescription(); } - $docBlock = $this->docBlockFactory->createFromReflector($refProperty); + $docBlock = $this->docBlockFactory->create($refProperty); $name = $queryAnnotation->getName() ?: $refProperty->getName(); @@ -686,7 +684,7 @@ private function getQueryFieldsFromSourceFields( throw FieldNotFoundException::wrapWithCallerInfo($e, $refClass->getName()); } - $docBlockObj = $this->docBlockFactory->createFromReflector($refMethod); + $docBlockObj = $this->docBlockFactory->create($refMethod); $docBlockComment = rtrim($docBlockObj->getSummary() . "\n" . $docBlockObj->getDescription()->render()); $deprecated = $docBlockObj->getTagsByName('deprecated'); @@ -822,7 +820,7 @@ private function resolvePhpType( { $typeResolver = new \phpDocumentor\Reflection\TypeResolver(); - $context = $this->docBlockContextFactory->createFromReflector($refClass); + $context = $this->docBlockFactory->createContext($refClass); $phpdocType = $typeResolver->resolve($phpTypeStr, $context); assert($phpdocType !== null); @@ -971,7 +969,7 @@ private function getPrefetchParameter( $prefetchParameters = $prefetchRefMethod->getParameters(); array_shift($prefetchParameters); - $prefetchDocBlockObj = $this->docBlockFactory->createFromReflector($prefetchRefMethod); + $prefetchDocBlockObj = $this->docBlockFactory->create($prefetchRefMethod); $prefetchArgs = $this->mapParameters($prefetchParameters, $prefetchDocBlockObj); return new PrefetchDataParameter( @@ -1027,7 +1025,7 @@ private function getInputFieldsByMethodAnnotations( continue; } - $docBlockObj = $this->docBlockFactory->createFromReflector($refMethod); + $docBlockObj = $this->docBlockFactory->create($refMethod); $methodName = $refMethod->getName(); if (! str_starts_with($methodName, 'set')) { continue; @@ -1124,7 +1122,7 @@ private function getInputFieldsByPropertyAnnotations( $fields = []; $annotations = $this->annotationReader->getPropertyAnnotations($refProperty, $annotationName); - $docBlock = $this->docBlockFactory->createFromReflector($refProperty); + $docBlock = $this->docBlockFactory->create($refProperty); foreach ($annotations as $annotation) { $description = null; diff --git a/src/Mappers/Parameters/TypeHandler.php b/src/Mappers/Parameters/TypeHandler.php index fc68dad311..b4b8d098ee 100644 --- a/src/Mappers/Parameters/TypeHandler.php +++ b/src/Mappers/Parameters/TypeHandler.php @@ -135,7 +135,7 @@ private function getDocBlockPropertyType(DocBlock $docBlock, ReflectionProperty return null; } - $docBlock = $this->docBlockFactory->createFromReflector($refConstructor); + $docBlock = $this->docBlockFactory->create($refConstructor); $paramTags = $docBlock->getTagsByName('param'); foreach ($paramTags as $paramTag) { if (! $paramTag instanceof Param) { diff --git a/src/Mappers/Root/EnumTypeMapper.php b/src/Mappers/Root/EnumTypeMapper.php index 38b5b60356..64d4696b9c 100644 --- a/src/Mappers/Root/EnumTypeMapper.php +++ b/src/Mappers/Root/EnumTypeMapper.php @@ -125,7 +125,7 @@ private function mapByClassName(string $enumClass): EnumType|null (string) $reflectionEnum->getBackingType() === 'string'; $enumDescription = $this->docBlockFactory - ->createFromReflector($reflectionEnum) + ->create($reflectionEnum) ->getSummary() ?: null; /** @var array $enumCaseDescriptions */ @@ -134,7 +134,7 @@ private function mapByClassName(string $enumClass): EnumType|null $enumCaseDeprecationReasons = []; foreach ($reflectionEnum->getCases() as $reflectionEnumCase) { - $docBlock = $this->docBlockFactory->createFromReflector($reflectionEnumCase); + $docBlock = $this->docBlockFactory->create($reflectionEnumCase); $enumCaseDescriptions[$reflectionEnumCase->getName()] = $docBlock->getSummary() ?: null; $deprecation = $docBlock->getTagsByName('deprecated')[0] ?? null; diff --git a/src/Reflection/DocBlock/CachedDocBlockContextFactory.php b/src/Reflection/DocBlock/CachedDocBlockContextFactory.php deleted file mode 100644 index e6c792189e..0000000000 --- a/src/Reflection/DocBlock/CachedDocBlockContextFactory.php +++ /dev/null @@ -1,33 +0,0 @@ -getDeclaringClass(); - - return $this->classBoundCache->get( - $reflector, - fn () => $this->contextFactory->createFromReflector($reflector), - 'reflection.docBlockContext', - ); - } -} diff --git a/src/Reflection/DocBlock/CachedDocBlockFactory.php b/src/Reflection/DocBlock/CachedDocBlockFactory.php index c4b802c1fb..41b4761022 100644 --- a/src/Reflection/DocBlock/CachedDocBlockFactory.php +++ b/src/Reflection/DocBlock/CachedDocBlockFactory.php @@ -5,6 +5,7 @@ namespace TheCodingMachine\GraphQLite\Reflection\DocBlock; use phpDocumentor\Reflection\DocBlock; +use phpDocumentor\Reflection\Types\Context; use ReflectionClass; use ReflectionClassConstant; use ReflectionMethod; @@ -25,14 +26,27 @@ public function __construct( { } - public function createFromReflector(ReflectionClass|ReflectionMethod|ReflectionProperty|ReflectionClassConstant $reflector): DocBlock - { + public function create( + ReflectionClass|ReflectionMethod|ReflectionProperty|ReflectionClassConstant $reflector, + Context $context = null, + ): DocBlock { $class = $reflector instanceof ReflectionClass ? $reflector : $reflector->getDeclaringClass(); return $this->classBoundCache->get( $class, - fn () => $this->docBlockFactory->createFromReflector($reflector), + fn () => $this->docBlockFactory->create($reflector, $context ?? $this->createContext($class)), 'reflection.docBlock.' . md5($reflector::class . '.' . $reflector->getName()), ); } + + public function createContext(ReflectionClass|ReflectionMethod|ReflectionProperty|ReflectionClassConstant $reflector): Context + { + $reflector = $reflector instanceof ReflectionClass ? $reflector : $reflector->getDeclaringClass(); + + return $this->classBoundCache->get( + $reflector, + fn () => $this->docBlockFactory->createContext($reflector), + 'reflection.docBlockContext', + ); + } } diff --git a/src/Reflection/DocBlock/DocBlockContextFactory.php b/src/Reflection/DocBlock/DocBlockContextFactory.php deleted file mode 100644 index 5e023e1bdd..0000000000 --- a/src/Reflection/DocBlock/DocBlockContextFactory.php +++ /dev/null @@ -1,16 +0,0 @@ -contextFactory->createFromReflector($reflector); - } -} diff --git a/src/Reflection/DocBlock/PhpDocumentorDocBlockFactory.php b/src/Reflection/DocBlock/PhpDocumentorDocBlockFactory.php index eba3fd25ba..656e1723ed 100644 --- a/src/Reflection/DocBlock/PhpDocumentorDocBlockFactory.php +++ b/src/Reflection/DocBlock/PhpDocumentorDocBlockFactory.php @@ -5,7 +5,10 @@ namespace TheCodingMachine\GraphQLite\Reflection\DocBlock; use phpDocumentor\Reflection\DocBlock; +use phpDocumentor\Reflection\DocBlockFactory as DocBlockFactoryConcrete; use phpDocumentor\Reflection\DocBlockFactoryInterface; +use phpDocumentor\Reflection\Types\Context; +use phpDocumentor\Reflection\Types\ContextFactory; use ReflectionClass; use ReflectionClassConstant; use ReflectionMethod; @@ -15,16 +18,34 @@ class PhpDocumentorDocBlockFactory implements DocBlockFactory { public function __construct( private readonly DocBlockFactoryInterface $docBlockFactory, - private readonly DocBlockContextFactory $docBlockContextFactory, + private readonly ContextFactory $contextFactory, ) { } - public function createFromReflector(ReflectionClass|ReflectionMethod|ReflectionProperty|ReflectionClassConstant $reflector): DocBlock + public static function default(): self + { + return new self( + DocBlockFactoryConcrete::createInstance(), + new ContextFactory(), + ); + } + + public function create( + ReflectionClass|ReflectionMethod|ReflectionProperty|ReflectionClassConstant $reflector, + Context $context = null, + ): DocBlock { $docblock = $reflector->getDocComment() ?: '/** */'; - $context = $this->docBlockContextFactory->createFromReflector($reflector); - return $this->docBlockFactory->create($docblock, $context); + return $this->docBlockFactory->create( + $docblock, + $context ?? $this->createContext($reflector), + ); + } + + public function createContext(ReflectionClass|ReflectionMethod|ReflectionProperty|ReflectionClassConstant $reflector): Context + { + return $this->contextFactory->createFromReflector($reflector); } } diff --git a/src/SchemaFactory.php b/src/SchemaFactory.php index 1d24095acc..dd7b9bc346 100644 --- a/src/SchemaFactory.php +++ b/src/SchemaFactory.php @@ -58,10 +58,7 @@ use TheCodingMachine\GraphQLite\Middlewares\InputFieldMiddlewarePipe; use TheCodingMachine\GraphQLite\Middlewares\SecurityFieldMiddleware; use TheCodingMachine\GraphQLite\Middlewares\SecurityInputFieldMiddleware; -use TheCodingMachine\GraphQLite\Reflection\DocBlock\CachedDocBlockContextFactory; use TheCodingMachine\GraphQLite\Reflection\DocBlock\CachedDocBlockFactory; -use TheCodingMachine\GraphQLite\Reflection\DocBlock\DocBlockContextFactory; -use TheCodingMachine\GraphQLite\Reflection\DocBlock\PhpDocumentorDocBlockContextFactory; use TheCodingMachine\GraphQLite\Reflection\DocBlock\PhpDocumentorDocBlockFactory; use TheCodingMachine\GraphQLite\Security\AuthenticationServiceInterface; use TheCodingMachine\GraphQLite\Security\AuthorizationServiceInterface; @@ -355,7 +352,10 @@ public function createSchema(): Schema $this->cache, $this->devMode ? FilesSnapshot::forClass(...) : FilesSnapshot::alwaysUnchanged(...), ); - [$docBlockFactory, $docBlockContextFactory] = $this->createDocBlockFactory($classBoundCache); + $docBlockFactory = new CachedDocBlockFactory( + $classBoundCache, + PhpDocumentorDocBlockFactory::default(), + ); $namingStrategy = $this->namingStrategy ?: new NamingStrategy(); $typeRegistry = new TypeRegistry(); $classFinder = $this->createClassFinder(); @@ -433,7 +433,6 @@ classBoundCache: $classBoundCache, $argumentResolver, $typeResolver, $docBlockFactory, - $docBlockContextFactory, $namingStrategy, $topRootTypeMapper, $parameterMiddlewarePipe, @@ -559,22 +558,4 @@ private function createClassFinder(): ClassFinder return new KcsClassFinder($finder); } - - /** @return array{ Reflection\DocBlock\DocBlockFactory, DocBlockContextFactory } */ - private function createDocBlockFactory(ClassBoundCache $classBoundCache): array - { - $docBlockContextFactory = new CachedDocBlockContextFactory( - $classBoundCache, - new PhpDocumentorDocBlockContextFactory(new ContextFactory()), - ); - $docBlockFactory = new CachedDocBlockFactory( - $classBoundCache, - new PhpDocumentorDocBlockFactory( - DocBlockFactory::createInstance(), - $docBlockContextFactory, - ), - ); - - return [$docBlockFactory, $docBlockContextFactory]; - } } diff --git a/tests/AbstractQueryProvider.php b/tests/AbstractQueryProvider.php index 3598d2d03a..8e8c09fe72 100644 --- a/tests/AbstractQueryProvider.php +++ b/tests/AbstractQueryProvider.php @@ -14,7 +14,6 @@ use Kcs\ClassFinder\FileFinder\DefaultFileFinder; use Kcs\ClassFinder\Finder\ComposerFinder; use phpDocumentor\Reflection\TypeResolver as PhpDocumentorTypeResolver; -use phpDocumentor\Reflection\Types\ContextFactory; use PHPUnit\Framework\TestCase; use Psr\Container\ContainerInterface; use Symfony\Component\Cache\Adapter\ArrayAdapter; @@ -56,11 +55,8 @@ use TheCodingMachine\GraphQLite\Middlewares\FieldMiddlewarePipe; use TheCodingMachine\GraphQLite\Middlewares\InputFieldMiddlewarePipe; use TheCodingMachine\GraphQLite\Middlewares\SecurityFieldMiddleware; -use TheCodingMachine\GraphQLite\Reflection\DocBlock\CachedDocBlockContextFactory; use TheCodingMachine\GraphQLite\Reflection\DocBlock\CachedDocBlockFactory; -use TheCodingMachine\GraphQLite\Reflection\DocBlock\DocBlockContextFactory; use TheCodingMachine\GraphQLite\Reflection\DocBlock\DocBlockFactory; -use TheCodingMachine\GraphQLite\Reflection\DocBlock\PhpDocumentorDocBlockContextFactory; use TheCodingMachine\GraphQLite\Reflection\DocBlock\PhpDocumentorDocBlockFactory; use TheCodingMachine\GraphQLite\Security\SecurityExpressionLanguageProvider; use TheCodingMachine\GraphQLite\Security\VoidAuthenticationService; @@ -283,18 +279,7 @@ protected function getDocBlockFactory(): DocBlockFactory { return new CachedDocBlockFactory( $this->getClassBoundCache(), - new PhpDocumentorDocBlockFactory( - \phpDocumentor\Reflection\DocBlockFactory::createInstance(), - $this->getDocBlockContextFactory(), - ) - ); - } - - protected function getDocBlockContextFactory(): DocBlockContextFactory - { - return new CachedDocBlockContextFactory( - $this->getClassBoundCache(), - new PhpDocumentorDocBlockContextFactory(new ContextFactory()) + PhpDocumentorDocBlockFactory::default(), ); } @@ -304,7 +289,7 @@ private function getClassBoundCache(): ClassBoundCache $arrayAdapter->setLogger(new ExceptionLogger()); $psr16Cache = new Psr16Cache($arrayAdapter); - return new SnapshotClassBoundCache($psr16Cache, FilesSnapshot::alwaysUnchanged()); + return new SnapshotClassBoundCache($psr16Cache, FilesSnapshot::alwaysUnchanged(...)); } protected function buildFieldsBuilder(): FieldsBuilder @@ -341,7 +326,6 @@ protected function buildFieldsBuilder(): FieldsBuilder $this->getArgumentResolver(), $this->getTypeResolver(), $this->getDocBlockFactory(), - $this->getDocBlockContextFactory(), new NamingStrategy(), $this->buildRootTypeMapper(), $parameterMiddlewarePipe, diff --git a/tests/FactoryContextTest.php b/tests/FactoryContextTest.php index 01066bbc3c..3ab131851a 100644 --- a/tests/FactoryContextTest.php +++ b/tests/FactoryContextTest.php @@ -22,7 +22,7 @@ public function testContext(): void $arrayCache = new Psr16Cache(new ArrayAdapter()); $classFinder = new StaticClassFinder([]); $classFinderComputedCache = new HardClassFinderComputedCache($arrayCache); - $classBoundCache = new SnapshotClassBoundCache($arrayCache, FilesSnapshot::alwaysUnchanged()); + $classBoundCache = new SnapshotClassBoundCache($arrayCache, FilesSnapshot::alwaysUnchanged(...)); $validator = new Validator(); $context = new FactoryContext( diff --git a/tests/FieldsBuilderTest.php b/tests/FieldsBuilderTest.php index 382c6575dd..0cd11d0d49 100644 --- a/tests/FieldsBuilderTest.php +++ b/tests/FieldsBuilderTest.php @@ -354,7 +354,6 @@ public function getUser(): object|null $this->getArgumentResolver(), $this->getTypeResolver(), $this->getDocBlockFactory(), - $this->getDocBlockContextFactory(), new NamingStrategy(), $this->getRootTypeMapper(), $this->getParameterMiddlewarePipe(), @@ -386,7 +385,6 @@ public function isAllowed(string $right, $subject = null): bool $this->getArgumentResolver(), $this->getTypeResolver(), $this->getDocBlockFactory(), - $this->getDocBlockContextFactory(), new NamingStrategy(), $this->getRootTypeMapper(), $this->getParameterMiddlewarePipe(), @@ -448,7 +446,6 @@ public function testFromSourceFieldsInterface(): void $this->getArgumentResolver(), $this->getTypeResolver(), $this->getDocBlockFactory(), - $this->getDocBlockContextFactory(), new NamingStrategy(), $this->getRootTypeMapper(), $this->getParameterMiddlewarePipe(), diff --git a/tests/Integration/IntegrationTestCase.php b/tests/Integration/IntegrationTestCase.php index 0a4911bd98..200247f56e 100644 --- a/tests/Integration/IntegrationTestCase.php +++ b/tests/Integration/IntegrationTestCase.php @@ -5,7 +5,6 @@ use GraphQL\Error\DebugFlag; use GraphQL\Executor\ExecutionResult; use Kcs\ClassFinder\Finder\ComposerFinder; -use phpDocumentor\Reflection\Types\ContextFactory; use PHPUnit\Framework\TestCase; use Psr\Container\ContainerInterface; use stdClass; @@ -65,11 +64,8 @@ use TheCodingMachine\GraphQLite\NamingStrategyInterface; use TheCodingMachine\GraphQLite\ParameterizedCallableResolver; use TheCodingMachine\GraphQLite\QueryProviderInterface; -use TheCodingMachine\GraphQLite\Reflection\DocBlock\CachedDocBlockContextFactory; use TheCodingMachine\GraphQLite\Reflection\DocBlock\CachedDocBlockFactory; -use TheCodingMachine\GraphQLite\Reflection\DocBlock\DocBlockContextFactory; use TheCodingMachine\GraphQLite\Reflection\DocBlock\DocBlockFactory; -use TheCodingMachine\GraphQLite\Reflection\DocBlock\PhpDocumentorDocBlockContextFactory; use TheCodingMachine\GraphQLite\Reflection\DocBlock\PhpDocumentorDocBlockFactory; use TheCodingMachine\GraphQLite\Schema; use TheCodingMachine\GraphQLite\Security\AuthenticationServiceInterface; @@ -141,7 +137,6 @@ public function createContainer(array $overloadedServices = []): ContainerInterf $container->get(ArgumentResolver::class), $container->get(TypeResolver::class), $container->get(DocBlockFactory::class), - $container->get(DocBlockContextFactory::class), $container->get(NamingStrategyInterface::class), $container->get(RootTypeMapperInterface::class), $parameterMiddlewarePipe, @@ -285,23 +280,12 @@ public function createContainer(array $overloadedServices = []): ContainerInterf $arrayAdapter->setLogger(new ExceptionLogger()); $psr16Cache = new Psr16Cache($arrayAdapter); - return new SnapshotClassBoundCache($psr16Cache, FilesSnapshot::alwaysUnchanged()); + return new SnapshotClassBoundCache($psr16Cache, FilesSnapshot::alwaysUnchanged(...)); }, DocBlockFactory::class => static function (ContainerInterface $container) { return new CachedDocBlockFactory( $container->get(ClassBoundCache::class), - new PhpDocumentorDocBlockFactory( - \phpDocumentor\Reflection\DocBlockFactory::createInstance(), - $container->get(DocBlockContextFactory::class), - ) - ); - }, - DocBlockContextFactory::class => static function (ContainerInterface $container) { - return new CachedDocBlockContextFactory( - $container->get(ClassBoundCache::class), - new PhpDocumentorDocBlockContextFactory( - new ContextFactory(), - ) + PhpDocumentorDocBlockFactory::default(), ); }, RootTypeMapperInterface::class => static function (ContainerInterface $container) { diff --git a/tests/Mappers/Parameters/TypeMapperTest.php b/tests/Mappers/Parameters/TypeMapperTest.php index 00ce2aef18..b0c6c09ea3 100644 --- a/tests/Mappers/Parameters/TypeMapperTest.php +++ b/tests/Mappers/Parameters/TypeMapperTest.php @@ -35,7 +35,7 @@ public function testMapScalarUnionException(): void ); $refMethod = new ReflectionMethod($this, 'dummy'); - $docBlockObj = $docBlockFactory->createFromReflector($refMethod); + $docBlockObj = $docBlockFactory->create($refMethod); $this->expectException(CannotMapTypeException::class); $this->expectExceptionMessage('For return type of TheCodingMachine\GraphQLite\Mappers\Parameters\TypeMapperTest::dummy, in GraphQL, you can only use union types between objects. These types cannot be used in union types: String!, Int!'); @@ -54,7 +54,7 @@ public function testMapObjectUnionWorks(): void ); $refMethod = new ReflectionMethod(UnionOutputType::class, 'objectUnion'); - $docBlockObj = $docBlockFactory->createFromReflector($refMethod); + $docBlockObj = $docBlockFactory->create($refMethod); $gqType = $typeMapper->mapReturnType($refMethod, $docBlockObj); $this->assertInstanceOf(NonNull::class, $gqType); @@ -79,7 +79,7 @@ public function testMapObjectNullableUnionWorks(): void ); $refMethod = new ReflectionMethod(UnionOutputType::class, 'nullableObjectUnion'); - $docBlockObj = $docBlockFactory->createFromReflector($refMethod); + $docBlockObj = $docBlockFactory->create($refMethod); $gqType = $typeMapper->mapReturnType($refMethod, $docBlockObj); $this->assertNotInstanceOf(NonNull::class, $gqType); @@ -105,7 +105,7 @@ public function testHideParameter(): void $refMethod = new ReflectionMethod($this, 'withDefaultValue'); $refParameter = $refMethod->getParameters()[0]; - $docBlockObj = $docBlockFactory->createFromReflector($refMethod); + $docBlockObj = $docBlockFactory->create($refMethod); $annotations = $this->getAnnotationReader()->getParameterAnnotationsPerParameter([$refParameter])['foo']; $param = $typeMapper->mapParameter($refParameter, $docBlockObj, null, $annotations); @@ -128,7 +128,7 @@ public function testParameterWithDescription(): void ); $refMethod = new ReflectionMethod($this, 'withParamDescription'); - $docBlockObj = $docBlockFactory->createFromReflector($refMethod); + $docBlockObj = $docBlockFactory->create($refMethod); $refParameter = $refMethod->getParameters()[0]; $parameter = $typeMapper->mapParameter($refParameter, $docBlockObj, null, $this->getAnnotationReader()->getParameterAnnotationsPerParameter([$refParameter])['foo']); @@ -150,7 +150,7 @@ public function testHideParameterException(): void $refMethod = new ReflectionMethod($this, 'withoutDefaultValue'); $refParameter = $refMethod->getParameters()[0]; - $docBlockObj = $docBlockFactory->createFromReflector($refMethod); + $docBlockObj = $docBlockFactory->create($refMethod); $annotations = $this->getAnnotationReader()->getParameterAnnotationsPerParameter([$refParameter])['foo']; $this->expectException(CannotHideParameterRuntimeException::class); diff --git a/tests/Reflection/CachedDocBlockFactoryTest.php b/tests/Reflection/CachedDocBlockFactoryTest.php index 3eb9b88149..ad1053e9e6 100644 --- a/tests/Reflection/CachedDocBlockFactoryTest.php +++ b/tests/Reflection/CachedDocBlockFactoryTest.php @@ -2,8 +2,6 @@ namespace TheCodingMachine\GraphQLite\Reflection; -use phpDocumentor\Reflection\DocBlockFactory; -use phpDocumentor\Reflection\Types\ContextFactory; use PHPUnit\Framework\TestCase; use ReflectionMethod; use Symfony\Component\Cache\Adapter\ArrayAdapter; @@ -11,7 +9,7 @@ use TheCodingMachine\GraphQLite\Cache\FilesSnapshot; use TheCodingMachine\GraphQLite\Cache\SnapshotClassBoundCache; use TheCodingMachine\GraphQLite\Reflection\DocBlock\CachedDocBlockFactory; -use TheCodingMachine\GraphQLite\Reflection\DocBlock\PhpDocumentorDocBlockContextFactory; +use TheCodingMachine\GraphQLite\Reflection\DocBlock\DocBlockFactory; use TheCodingMachine\GraphQLite\Reflection\DocBlock\PhpDocumentorDocBlockFactory; class CachedDocBlockFactoryTest extends TestCase @@ -21,28 +19,22 @@ public function testGetDocBlock(): void { $arrayCache = new Psr16Cache(new ArrayAdapter(storeSerialized: false)); $cachedDocBlockFactory = new CachedDocBlockFactory( - new SnapshotClassBoundCache($arrayCache, FilesSnapshot::alwaysUnchanged()), - new PhpDocumentorDocBlockFactory( - DocBlockFactory::createInstance(), - new PhpDocumentorDocBlockContextFactory(new ContextFactory()), - ) + new SnapshotClassBoundCache($arrayCache, FilesSnapshot::alwaysUnchanged(...)), + PhpDocumentorDocBlockFactory::default(), ); - $refMethod = new ReflectionMethod(DocBlock\DocBlockFactory::class, 'createFromReflector'); + $refMethod = new ReflectionMethod(DocBlockFactory::class, 'create'); - $docBlock = $cachedDocBlockFactory->createFromReflector($refMethod); + $docBlock = $cachedDocBlockFactory->create($refMethod); $this->assertSame('Fetches a DocBlock object from a ReflectionMethod', $docBlock->getSummary()); - $docBlock2 = $cachedDocBlockFactory->createFromReflector($refMethod); + $docBlock2 = $cachedDocBlockFactory->create($refMethod); $this->assertSame($docBlock2, $docBlock); $newCachedDocBlockFactory = new CachedDocBlockFactory( - new SnapshotClassBoundCache($arrayCache, FilesSnapshot::alwaysUnchanged()), - new PhpDocumentorDocBlockFactory( - DocBlockFactory::createInstance(), - new PhpDocumentorDocBlockContextFactory(new ContextFactory()), - ) + new SnapshotClassBoundCache($arrayCache, FilesSnapshot::alwaysUnchanged(...)), + PhpDocumentorDocBlockFactory::default(), ); - $docBlock3 = $newCachedDocBlockFactory->createFromReflector($refMethod); + $docBlock3 = $newCachedDocBlockFactory->create($refMethod); $this->assertEquals($docBlock3, $docBlock); } } diff --git a/tests/RootTypeMapperFactoryContextTest.php b/tests/RootTypeMapperFactoryContextTest.php index 16dedea04c..97d56d1d3c 100644 --- a/tests/RootTypeMapperFactoryContextTest.php +++ b/tests/RootTypeMapperFactoryContextTest.php @@ -21,7 +21,7 @@ public function testContext(): void $arrayCache = new Psr16Cache(new ArrayAdapter()); $classFinder = $this->getClassFinder('namespace'); $classFinderComputedCache = $this->getClassFinderComputedCache(); - $classBoundCache = new SnapshotClassBoundCache($arrayCache, FilesSnapshot::alwaysUnchanged()); + $classBoundCache = new SnapshotClassBoundCache($arrayCache, FilesSnapshot::alwaysUnchanged(...)); $context = new RootTypeMapperFactoryContext( $this->getAnnotationReader(), From 8caa5c171db552ecb37d8e3da8963edf02ad753e Mon Sep 17 00:00:00 2001 From: Oleksandr Prypkhan Date: Mon, 2 Sep 2024 21:39:40 +0300 Subject: [PATCH 22/28] Simplify cached doc blocks --- src/Cache/ClassBoundCache.php | 6 +++--- src/Cache/FilesSnapshot.php | 6 ++---- src/Cache/SnapshotClassBoundCache.php | 4 +--- src/Discovery/Cache/SnapshotClassFinderComputedCache.php | 2 -- src/Reflection/DocBlock/CachedDocBlockFactory.php | 2 +- src/Reflection/DocBlock/DocBlockFactory.php | 2 +- src/Reflection/DocBlock/PhpDocumentorDocBlockFactory.php | 2 +- src/SchemaFactory.php | 4 +--- 8 files changed, 10 insertions(+), 18 deletions(-) diff --git a/src/Cache/ClassBoundCache.php b/src/Cache/ClassBoundCache.php index fc812754fa..6e9d20f66b 100644 --- a/src/Cache/ClassBoundCache.php +++ b/src/Cache/ClassBoundCache.php @@ -17,8 +17,8 @@ interface ClassBoundCache */ public function get( ReflectionClass $reflectionClass, - callable $resolver, - string $key, - bool $withInheritance = false, + callable $resolver, + string $key, + bool $withInheritance = false, ): mixed; } diff --git a/src/Cache/FilesSnapshot.php b/src/Cache/FilesSnapshot.php index 36038548c4..7ac70861ac 100644 --- a/src/Cache/FilesSnapshot.php +++ b/src/Cache/FilesSnapshot.php @@ -6,7 +6,7 @@ use ReflectionClass; -use function array_merge; +use function array_unique; use function Safe\filemtime; class FilesSnapshot @@ -18,9 +18,7 @@ private function __construct( { } - /** - * @param list $files - */ + /** @param list $files */ public static function for(array $files): self { $dependencies = []; diff --git a/src/Cache/SnapshotClassBoundCache.php b/src/Cache/SnapshotClassBoundCache.php index 6774f1eceb..62f2a329d4 100644 --- a/src/Cache/SnapshotClassBoundCache.php +++ b/src/Cache/SnapshotClassBoundCache.php @@ -11,9 +11,7 @@ class SnapshotClassBoundCache implements ClassBoundCache { - /** - * @param callable(ReflectionClass, bool $withInheritance): FilesSnapshot $filesSnapshotFactory - */ + /** @param callable(ReflectionClass, bool $withInheritance): FilesSnapshot $filesSnapshotFactory */ public function __construct( private readonly CacheInterface $cache, private readonly mixed $filesSnapshotFactory, diff --git a/src/Discovery/Cache/SnapshotClassFinderComputedCache.php b/src/Discovery/Cache/SnapshotClassFinderComputedCache.php index 68279a02eb..3e4b9d2306 100644 --- a/src/Discovery/Cache/SnapshotClassFinderComputedCache.php +++ b/src/Discovery/Cache/SnapshotClassFinderComputedCache.php @@ -9,8 +9,6 @@ use TheCodingMachine\GraphQLite\Cache\FilesSnapshot; use TheCodingMachine\GraphQLite\Discovery\ClassFinder; -use function Safe\filemtime; - /** * Provides cache for a {@see ClassFinder} based on a {@see filemtime()}. * diff --git a/src/Reflection/DocBlock/CachedDocBlockFactory.php b/src/Reflection/DocBlock/CachedDocBlockFactory.php index 41b4761022..b6fb1a8a09 100644 --- a/src/Reflection/DocBlock/CachedDocBlockFactory.php +++ b/src/Reflection/DocBlock/CachedDocBlockFactory.php @@ -28,7 +28,7 @@ public function __construct( public function create( ReflectionClass|ReflectionMethod|ReflectionProperty|ReflectionClassConstant $reflector, - Context $context = null, + Context|null $context = null, ): DocBlock { $class = $reflector instanceof ReflectionClass ? $reflector : $reflector->getDeclaringClass(); diff --git a/src/Reflection/DocBlock/DocBlockFactory.php b/src/Reflection/DocBlock/DocBlockFactory.php index a9785e3a34..5a0009a5f3 100644 --- a/src/Reflection/DocBlock/DocBlockFactory.php +++ b/src/Reflection/DocBlock/DocBlockFactory.php @@ -18,7 +18,7 @@ interface DocBlockFactory */ public function create( ReflectionClass|ReflectionMethod|ReflectionProperty|ReflectionClassConstant $reflector, - Context $context = null, + Context|null $context = null, ): DocBlock; public function createContext(ReflectionClass|ReflectionMethod|ReflectionProperty|ReflectionClassConstant $reflector): Context; diff --git a/src/Reflection/DocBlock/PhpDocumentorDocBlockFactory.php b/src/Reflection/DocBlock/PhpDocumentorDocBlockFactory.php index 656e1723ed..5bbe5297dd 100644 --- a/src/Reflection/DocBlock/PhpDocumentorDocBlockFactory.php +++ b/src/Reflection/DocBlock/PhpDocumentorDocBlockFactory.php @@ -33,7 +33,7 @@ public static function default(): self public function create( ReflectionClass|ReflectionMethod|ReflectionProperty|ReflectionClassConstant $reflector, - Context $context = null, + Context|null $context = null, ): DocBlock { $docblock = $reflector->getDocComment() ?: '/** */'; diff --git a/src/SchemaFactory.php b/src/SchemaFactory.php index dd7b9bc346..d697d2adf1 100644 --- a/src/SchemaFactory.php +++ b/src/SchemaFactory.php @@ -11,8 +11,6 @@ use Kcs\ClassFinder\Finder\FinderInterface; use MyCLabs\Enum\Enum; use PackageVersions\Versions; -use phpDocumentor\Reflection\DocBlockFactory; -use phpDocumentor\Reflection\Types\ContextFactory; use Psr\Container\ContainerInterface; use Psr\SimpleCache\CacheInterface; use Symfony\Component\Cache\Adapter\ArrayAdapter; @@ -21,8 +19,8 @@ use TheCodingMachine\GraphQLite\Cache\ClassBoundCache; use TheCodingMachine\GraphQLite\Cache\FilesSnapshot; use TheCodingMachine\GraphQLite\Cache\SnapshotClassBoundCache; -use TheCodingMachine\GraphQLite\Discovery\Cache\SnapshotClassFinderComputedCache; use TheCodingMachine\GraphQLite\Discovery\Cache\HardClassFinderComputedCache; +use TheCodingMachine\GraphQLite\Discovery\Cache\SnapshotClassFinderComputedCache; use TheCodingMachine\GraphQLite\Discovery\ClassFinder; use TheCodingMachine\GraphQLite\Discovery\KcsClassFinder; use TheCodingMachine\GraphQLite\Discovery\StaticClassFinder; From 0adf9d9ef7abd1bbb77e3a143b5a5e24607035ed Mon Sep 17 00:00:00 2001 From: Oleksandr Prypkhan Date: Mon, 2 Sep 2024 21:57:40 +0300 Subject: [PATCH 23/28] More tests for doc block factories --- .../CachedDocBlockFactoryTest.php | 32 +++++++++++-- .../PhpDocumentorDocBlockFactoryTest.php | 47 +++++++++++++++++++ 2 files changed, 74 insertions(+), 5 deletions(-) rename tests/Reflection/{ => DocBlock}/CachedDocBlockFactoryTest.php (51%) create mode 100644 tests/Reflection/DocBlock/PhpDocumentorDocBlockFactoryTest.php diff --git a/tests/Reflection/CachedDocBlockFactoryTest.php b/tests/Reflection/DocBlock/CachedDocBlockFactoryTest.php similarity index 51% rename from tests/Reflection/CachedDocBlockFactoryTest.php rename to tests/Reflection/DocBlock/CachedDocBlockFactoryTest.php index ad1053e9e6..f1b13ae077 100644 --- a/tests/Reflection/CachedDocBlockFactoryTest.php +++ b/tests/Reflection/DocBlock/CachedDocBlockFactoryTest.php @@ -1,21 +1,20 @@ create($refMethod); $this->assertEquals($docBlock3, $docBlock); } + + public function testCreatesContext(): void + { + $arrayCache = new Psr16Cache(new ArrayAdapter(storeSerialized: false)); + $cachedDocBlockFactory = new CachedDocBlockFactory( + new SnapshotClassBoundCache($arrayCache, FilesSnapshot::alwaysUnchanged(...)), + PhpDocumentorDocBlockFactory::default(), + ); + + $refMethod = new ReflectionMethod(DocBlockFactory::class, 'create'); + + $docBlock = $cachedDocBlockFactory->createContext($refMethod); + $this->assertSame('TheCodingMachine\GraphQLite\Reflection\DocBlock', $docBlock->getNamespace()); + $docBlock2 = $cachedDocBlockFactory->createContext($refMethod); + $this->assertSame($docBlock2, $docBlock); + + $newCachedDocBlockFactory = new CachedDocBlockFactory( + new SnapshotClassBoundCache($arrayCache, FilesSnapshot::alwaysUnchanged(...)), + PhpDocumentorDocBlockFactory::default(), + ); + $docBlock3 = $newCachedDocBlockFactory->createContext($refMethod); + $this->assertEquals($docBlock3, $docBlock); + } } diff --git a/tests/Reflection/DocBlock/PhpDocumentorDocBlockFactoryTest.php b/tests/Reflection/DocBlock/PhpDocumentorDocBlockFactoryTest.php new file mode 100644 index 0000000000..d8ad70f36b --- /dev/null +++ b/tests/Reflection/DocBlock/PhpDocumentorDocBlockFactoryTest.php @@ -0,0 +1,47 @@ +create($refMethod); + + $this->assertCount(1, $docBlock->getTagsByName('param')); + + /** @var Param $paramTag */ + $paramTag = $docBlock->getTagsByName('param')[0]; + + $this->assertEquals( + new Array_( + new Object_(new Fqsen('\\' . TestObject::class)) + ), + $paramTag->getType(), + ); + } + + public function testCreatesContext(): void + { + $docBlockFactory = PhpDocumentorDocBlockFactory::default(); + + $refMethod = (new ReflectionMethod(TestController::class, 'test')); + $context = $docBlockFactory->createContext($refMethod); + + $this->assertSame('TheCodingMachine\GraphQLite\Fixtures', $context->getNamespace()); + } +} From df0ee5809b7e161acd0987aef0759874b1b8bfec Mon Sep 17 00:00:00 2001 From: Oleksandr Prypkhan Date: Mon, 2 Sep 2024 22:55:24 +0300 Subject: [PATCH 24/28] Tests for the Discovery namespace --- src/Discovery/StaticClassFinder.php | 2 +- tests/Cache/FilesSnapshotTest.php | 6 +- tests/Cache/SnapshotClassBoundCacheTest.php | 15 ++-- .../HardClassFinderComputedCacheTest.php | 58 ++++++++++++ .../SnapshotClassFinderComputedCacheTest.php | 89 +++++++++++++++++++ tests/Discovery/KcsClassFinderTest.php | 53 +++++++++++ tests/Discovery/StaticClassFinderTest.php | 42 +++++++++ 7 files changed, 254 insertions(+), 11 deletions(-) create mode 100644 tests/Discovery/Cache/HardClassFinderComputedCacheTest.php create mode 100644 tests/Discovery/Cache/SnapshotClassFinderComputedCacheTest.php create mode 100644 tests/Discovery/KcsClassFinderTest.php create mode 100644 tests/Discovery/StaticClassFinderTest.php diff --git a/src/Discovery/StaticClassFinder.php b/src/Discovery/StaticClassFinder.php index 2f46daf8f9..7eedb617b0 100644 --- a/src/Discovery/StaticClassFinder.php +++ b/src/Discovery/StaticClassFinder.php @@ -12,7 +12,7 @@ class StaticClassFinder implements ClassFinder /** @var (callable(string): bool)|null */ private mixed $pathFilter = null; - /** @param array $classes */ + /** @param list $classes */ public function __construct( private readonly array $classes, ) diff --git a/tests/Cache/FilesSnapshotTest.php b/tests/Cache/FilesSnapshotTest.php index 6f22e5863a..f45aa2c75e 100644 --- a/tests/Cache/FilesSnapshotTest.php +++ b/tests/Cache/FilesSnapshotTest.php @@ -5,9 +5,11 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\TestWith; use PHPUnit\Framework\TestCase; -use TheCodingMachine\GraphQLite\Cache\FilesSnapshot; use TheCodingMachine\GraphQLite\Fixtures\Types\FooType; +use function Safe\touch; +use function Safe\filemtime; + #[CoversClass(FilesSnapshot::class)] class FilesSnapshotTest extends TestCase { @@ -69,7 +71,7 @@ public function testTracksChangesInFile(): void private function touch(string $fileName): void { - \Safe\touch($fileName, \Safe\filemtime($fileName) + 1); + touch($fileName, filemtime($fileName) + 1); clearstatcache(); } } \ No newline at end of file diff --git a/tests/Cache/SnapshotClassBoundCacheTest.php b/tests/Cache/SnapshotClassBoundCacheTest.php index 8e242cec98..d9e9390e74 100644 --- a/tests/Cache/SnapshotClassBoundCacheTest.php +++ b/tests/Cache/SnapshotClassBoundCacheTest.php @@ -5,14 +5,13 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use Symfony\Component\Cache\Adapter\ArrayAdapter; -use Symfony\Component\Cache\Adapter\Psr16Adapter; use Symfony\Component\Cache\Psr16Cache; -use TheCodingMachine\GraphQLite\Cache\FilesSnapshot; -use TheCodingMachine\GraphQLite\Cache\SnapshotClassBoundCache; -use TheCodingMachine\GraphQLite\Fixtures\Types\FooExtendType; use TheCodingMachine\GraphQLite\Fixtures\Types\FooType; use TheCodingMachine\GraphQLite\Fixtures\Types\NoTypeAnnotation; +use function Safe\touch; +use function Safe\filemtime; + #[CoversClass(SnapshotClassBoundCache::class)] class SnapshotClassBoundCacheTest extends TestCase { @@ -28,18 +27,18 @@ public function testFirstGetsItemFromResolverThenFromCache(): void $fooKeyResult = $classBoundCache->get($fooReflection, fn () => 'foo_key', 'key', true); self::assertSame('foo_key', $fooKeyResult); - self::assertSame('foo_key', $classBoundCache->get($fooReflection, fn () => self::fail('should not be called'), 'key', true)); + self::assertSame('foo_key', $classBoundCache->get($fooReflection, fn () => self::fail('Should not be called.'), 'key', true)); $fooDifferentKeyResult = $classBoundCache->get($fooReflection, fn () => 'foo_different_key', 'different_key', true); self::assertSame('foo_different_key', $fooDifferentKeyResult); - self::assertSame('foo_different_key', $classBoundCache->get($fooReflection, fn () => self::fail('should not be called'), 'different_key', true)); + self::assertSame('foo_different_key', $classBoundCache->get($fooReflection, fn () => self::fail('Should not be called.'), 'different_key', true)); $barReflection = new \ReflectionClass(NoTypeAnnotation::class); $barKeyResult = $classBoundCache->get($barReflection, fn () => 'bar_key', 'key', true); self::assertSame('bar_key', $barKeyResult); - self::assertSame('bar_key', $classBoundCache->get($barReflection, fn () => self::fail('should not be called'), 'key', true)); + self::assertSame('bar_key', $classBoundCache->get($barReflection, fn () => self::fail('Should not be called.'), 'key', true)); self::assertCount(3, $arrayCache->getValues()); @@ -53,7 +52,7 @@ public function testFirstGetsItemFromResolverThenFromCache(): void private function touch(string $fileName): void { - \Safe\touch($fileName, \Safe\filemtime($fileName) + 1); + touch($fileName, filemtime($fileName) + 1); clearstatcache(); } } \ No newline at end of file diff --git a/tests/Discovery/Cache/HardClassFinderComputedCacheTest.php b/tests/Discovery/Cache/HardClassFinderComputedCacheTest.php new file mode 100644 index 0000000000..407550a0b3 --- /dev/null +++ b/tests/Discovery/Cache/HardClassFinderComputedCacheTest.php @@ -0,0 +1,58 @@ +setLogger(new ExceptionLogger()); + $cache = new Psr16Cache($arrayAdapter); + + $classFinderComputedCache = new HardClassFinderComputedCache($cache); + + [$result] = $classFinderComputedCache->compute( + new StaticClassFinder([ + FooType::class, + FooExtendType::class, + TestType::class, + ]), + 'key', + fn (\ReflectionClass $reflection) => $reflection->getShortName(), + fn (array $entries) => [array_values($entries)], + ); + + $this->assertSame([ + 'FooType', + 'FooExtendType', + 'TestType', + ], $result); + + // Even though the class finder and both functions have changed - the result should still be cached. + // This is useful in production, where code and file structure doesn't change. + [$result] = $classFinderComputedCache->compute( + new StaticClassFinder([]), + 'key', + fn (\ReflectionClass $reflection) => self::fail('Should not be called.'), + fn (array $entries) => self::fail('Should not be called.'), + ); + + $this->assertSame([ + 'FooType', + 'FooExtendType', + 'TestType', + ], $result); + } +} \ No newline at end of file diff --git a/tests/Discovery/Cache/SnapshotClassFinderComputedCacheTest.php b/tests/Discovery/Cache/SnapshotClassFinderComputedCacheTest.php new file mode 100644 index 0000000000..bbf50389d1 --- /dev/null +++ b/tests/Discovery/Cache/SnapshotClassFinderComputedCacheTest.php @@ -0,0 +1,89 @@ +setLogger(new ExceptionLogger()); + $cache = new Psr16Cache($arrayAdapter); + + $classFinderComputedCache = new SnapshotClassFinderComputedCache($cache); + + [$result] = $classFinderComputedCache->compute( + new StaticClassFinder([ + FooType::class, + FooExtendType::class, + TestType::class, + ]), + 'key', + fn (\ReflectionClass $reflection) => $reflection->getShortName(), + fn (array $entries) => [array_values($entries)], + ); + + $this->assertSame([ + 'FooType', + 'FooExtendType', + 'TestType', + ], $result); + + [$result] = $classFinderComputedCache->compute( + new StaticClassFinder([ + FooType::class, + FooExtendType::class, + TestType::class, + ]), + 'key', + fn (\ReflectionClass $reflection) => self::fail('Should not be called.'), + fn (array $entries) => [array_values($entries)], + ); + + $this->assertSame([ + 'FooType', + 'FooExtendType', + 'TestType', + ], $result); + + $this->touch((new \ReflectionClass(FooType::class))->getFileName()); + + [$result] = $classFinderComputedCache->compute( + new StaticClassFinder([ + FooType::class, + TestType::class, + EnumType::class, + ]), + 'key', + fn (\ReflectionClass $reflection) => $reflection->getShortName() . ' Modified', + fn (array $entries) => [array_values($entries)], + ); + + $this->assertSame([ + 'FooType Modified', + 'TestType', + 'EnumType Modified', + ], $result); + } + + private function touch(string $fileName): void + { + touch($fileName, filemtime($fileName) + 1); + clearstatcache(); + } +} \ No newline at end of file diff --git a/tests/Discovery/KcsClassFinderTest.php b/tests/Discovery/KcsClassFinderTest.php new file mode 100644 index 0000000000..589308236c --- /dev/null +++ b/tests/Discovery/KcsClassFinderTest.php @@ -0,0 +1,53 @@ +inNamespace('TheCodingMachine\GraphQLite\Fixtures\Types') + ); + + $finderWithPath = $finder->withPathFilter(fn (string $path) => str_contains($path, 'FooExtendType.php')); + + $this->assertFoundClasses([ + TestFactory::class, + GetterSetterType::class, + FooType::class, + MagicGetterSetterType::class, + FooExtendType::class, + NoTypeAnnotation::class, + AbstractFooType::class, + EnumType::class, + ], $finder); + + $this->assertFoundClasses([ + FooExtendType::class, + ], $finderWithPath); + } + + private function assertFoundClasses(array $expectedClasses, ClassFinder $classFinder): void + { + $result = iterator_to_array($classFinder); + + $this->assertContainsOnlyInstancesOf(\ReflectionClass::class, $result); + $this->assertEqualsCanonicalizing($expectedClasses, array_keys($result)); + } +} \ No newline at end of file diff --git a/tests/Discovery/StaticClassFinderTest.php b/tests/Discovery/StaticClassFinderTest.php new file mode 100644 index 0000000000..51b6a00501 --- /dev/null +++ b/tests/Discovery/StaticClassFinderTest.php @@ -0,0 +1,42 @@ +withPathFilter(fn (string $path) => str_contains($path, 'FooExtendType.php')); + + $this->assertFoundClasses([ + FooType::class, + TestType::class, + FooExtendType::class, + ], $finder); + + $this->assertFoundClasses([ + FooExtendType::class, + ], $finderWithPath); + } + + private function assertFoundClasses(array $expectedClasses, ClassFinder $classFinder): void + { + $result = iterator_to_array($classFinder); + + $this->assertContainsOnlyInstancesOf(\ReflectionClass::class, $result); + $this->assertEqualsCanonicalizing($expectedClasses, array_keys($result)); + } +} \ No newline at end of file From dd5ba836f740dc86bd468b15ba88cf470d17ee47 Mon Sep 17 00:00:00 2001 From: Oleksandr Prypkhan Date: Thu, 12 Sep 2024 10:33:50 +0300 Subject: [PATCH 25/28] Fix the docs build on CI and broken links --- .github/workflows/doc_generation.yml | 2 +- website/docs/input-types.mdx | 2 +- website/versioned_docs/version-6.0/input-types.mdx | 2 +- website/versioned_docs/version-6.1/input-types.mdx | 2 +- website/versioned_docs/version-7.0.0/input-types.mdx | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/doc_generation.yml b/.github/workflows/doc_generation.yml index e2851c911b..4c73a105b5 100644 --- a/.github/workflows/doc_generation.yml +++ b/.github/workflows/doc_generation.yml @@ -24,7 +24,7 @@ jobs: - name: "Setup NodeJS" uses: actions/setup-node@v4 with: - node-version: '16.x' + node-version: '20.x' - name: "Yarn install" run: yarn install diff --git a/website/docs/input-types.mdx b/website/docs/input-types.mdx index c0efa59e87..9b5be40278 100644 --- a/website/docs/input-types.mdx +++ b/website/docs/input-types.mdx @@ -55,7 +55,7 @@ You are running into this error because GraphQLite does not know how to handle t In GraphQL, an object passed in parameter of a query or mutation (or any field) is called an **Input Type**. -There are two ways for declaring that type, in GraphQLite: using the [`#[Input]` attribute](input-attribute) or a [Factory method](factory). +There are two ways for declaring that type, in GraphQLite: using the [`#[Input]` attribute](#input-attribute) or a [Factory method](#factory). ## #\[Input\] Attribute diff --git a/website/versioned_docs/version-6.0/input-types.mdx b/website/versioned_docs/version-6.0/input-types.mdx index f2c62afd40..c5737d9176 100644 --- a/website/versioned_docs/version-6.0/input-types.mdx +++ b/website/versioned_docs/version-6.0/input-types.mdx @@ -109,7 +109,7 @@ You are running into this error because GraphQLite does not know how to handle t In GraphQL, an object passed in parameter of a query or mutation (or any field) is called an **Input Type**. -There are two ways for declaring that type, in GraphQLite: using the [`#[Input]` attribute](input-attribute) or a [Factory method](factory). +There are two ways for declaring that type, in GraphQLite: using the [`#[Input]` attribute](#input-attribute) or a [Factory method](#factory). ## #\[Input\] Attribute diff --git a/website/versioned_docs/version-6.1/input-types.mdx b/website/versioned_docs/version-6.1/input-types.mdx index 06754d97a1..48369cda90 100644 --- a/website/versioned_docs/version-6.1/input-types.mdx +++ b/website/versioned_docs/version-6.1/input-types.mdx @@ -59,7 +59,7 @@ You are running into this error because GraphQLite does not know how to handle t In GraphQL, an object passed in parameter of a query or mutation (or any field) is called an **Input Type**. -There are two ways for declaring that type, in GraphQLite: using the [`#[Input]` attribute](input-attribute) or a [Factory method](factory). +There are two ways for declaring that type, in GraphQLite: using the [`#[Input]` attribute](#input-attribute) or a [Factory method](#factory). ## #\[Input\] Attribute diff --git a/website/versioned_docs/version-7.0.0/input-types.mdx b/website/versioned_docs/version-7.0.0/input-types.mdx index f2c62afd40..c5737d9176 100644 --- a/website/versioned_docs/version-7.0.0/input-types.mdx +++ b/website/versioned_docs/version-7.0.0/input-types.mdx @@ -109,7 +109,7 @@ You are running into this error because GraphQLite does not know how to handle t In GraphQL, an object passed in parameter of a query or mutation (or any field) is called an **Input Type**. -There are two ways for declaring that type, in GraphQLite: using the [`#[Input]` attribute](input-attribute) or a [Factory method](factory). +There are two ways for declaring that type, in GraphQLite: using the [`#[Input]` attribute](#input-attribute) or a [Factory method](#factory). ## #\[Input\] Attribute From b251a7e5c44ac9354786061c05e1047358fc9fc0 Mon Sep 17 00:00:00 2001 From: Oleksandr Prypkhan Date: Thu, 12 Sep 2024 11:01:07 +0300 Subject: [PATCH 26/28] Add a changelog entry --- website/docs/CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/website/docs/CHANGELOG.md b/website/docs/CHANGELOG.md index fc3f57dba6..1afe9e4785 100644 --- a/website/docs/CHANGELOG.md +++ b/website/docs/CHANGELOG.md @@ -4,6 +4,23 @@ title: Changelog sidebar_label: Changelog --- +## 7.1.0 + +### Breaking Changes + +- #698 Removes some methods and classes, namely: + - Combined `SchemaFactory::addControllerNamespace()` and `SchemaFactory::addTypeNamespace()` into one `SchemaFactory::addNamespace()` + - Removed `SchemaFactory::setGlobTTL()`, `FactoryContext::get*TTL()` and `RootTypeMapperFactoryContext::get*TTL()` as GraphQLite no longer uses TTLs to invalidate caches + - Removed `StaticClassListTypeMapper` in favor of `ClassFinderTypeMapper` used with `StaticClassFinder` + - Renamed `GlobTypeMapper` to `ClassFinderTypeMapper` + - Renamed `SchemaFactory::setClassBoundCacheContractFactory()` to `SchemaFactory::setClassBoundCache()`, + `FactoryContext::getClassBoundCacheContractFactory()` to `FactoryContext::getClassBoundCache()` and changed their signatures + - Removed `RootTypeMapperFactoryContext::getTypeNamespaces()` in favor of `RootTypeMapperFactoryContext::getClassFinder()` + +### Improvements + +- #698 Performance optimizations and caching in development environments (`devMode()`). @oprypkhantc + ## 7.0.0 ### Breaking Changes From 44cc773428a0e41072971b83d48669cdba067f1f Mon Sep 17 00:00:00 2001 From: Oleksandr Prypkhan Date: Thu, 12 Sep 2024 18:32:23 +0300 Subject: [PATCH 27/28] Deprecate setGlobTTL() instead of removing it --- src/SchemaFactory.php | 13 +++++++++++++ website/docs/CHANGELOG.md | 5 +++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/SchemaFactory.php b/src/SchemaFactory.php index d697d2adf1..5cd1150048 100644 --- a/src/SchemaFactory.php +++ b/src/SchemaFactory.php @@ -277,6 +277,19 @@ public function setFinder(ClassFinder|FinderInterface $finder): self return $this; } + /** + * @deprecated setGlobTTL(null) or setGlobTTL(0) is equivalent to prodMode(), and any other values are equivalent to devMode() + */ + public function setGlobTTL(int|null $globTTL): self + { + trigger_error( + 'Using SchemaFactory::setGlobTTL() is deprecated in favor of SchemaFactory::devMode() and SchemaFactory::prodMode().', + E_USER_DEPRECATED, + ); + + return $globTTL ? $this->devMode() : $this->prodMode(); + } + /** * Set a custom class bound cache. By default in dev mode it looks at file modification times. */ diff --git a/website/docs/CHANGELOG.md b/website/docs/CHANGELOG.md index 1afe9e4785..bb83ceb4a0 100644 --- a/website/docs/CHANGELOG.md +++ b/website/docs/CHANGELOG.md @@ -9,8 +9,9 @@ sidebar_label: Changelog ### Breaking Changes - #698 Removes some methods and classes, namely: - - Combined `SchemaFactory::addControllerNamespace()` and `SchemaFactory::addTypeNamespace()` into one `SchemaFactory::addNamespace()` - - Removed `SchemaFactory::setGlobTTL()`, `FactoryContext::get*TTL()` and `RootTypeMapperFactoryContext::get*TTL()` as GraphQLite no longer uses TTLs to invalidate caches + - Deprecated `SchemaFactory::addControllerNamespace()` and `SchemaFactory::addTypeNamespace()` in favor of `SchemaFactory::addNamespace()` + - Deprecated `SchemaFactory::setGlobTTL()` in favor of `SchemaFactory::devMode()` and `SchemaFactory::prodMode()` + - Removed `FactoryContext::get*TTL()` and `RootTypeMapperFactoryContext::get*TTL()` as GraphQLite no longer uses TTLs to invalidate caches - Removed `StaticClassListTypeMapper` in favor of `ClassFinderTypeMapper` used with `StaticClassFinder` - Renamed `GlobTypeMapper` to `ClassFinderTypeMapper` - Renamed `SchemaFactory::setClassBoundCacheContractFactory()` to `SchemaFactory::setClassBoundCache()`, From 2bc9ecf02305138f7c5fe96ce6409b8357a42e51 Mon Sep 17 00:00:00 2001 From: Oleksandr Prypkhan Date: Fri, 13 Sep 2024 15:12:31 +0300 Subject: [PATCH 28/28] Code style --- src/SchemaFactory.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/SchemaFactory.php b/src/SchemaFactory.php index 5cd1150048..3991341b3f 100644 --- a/src/SchemaFactory.php +++ b/src/SchemaFactory.php @@ -277,9 +277,7 @@ public function setFinder(ClassFinder|FinderInterface $finder): self return $this; } - /** - * @deprecated setGlobTTL(null) or setGlobTTL(0) is equivalent to prodMode(), and any other values are equivalent to devMode() - */ + /** @deprecated setGlobTTL(null) or setGlobTTL(0) is equivalent to prodMode(), and any other values are equivalent to devMode() */ public function setGlobTTL(int|null $globTTL): self { trigger_error(