From 90564cbd4d1bdc9d4c33dc7b64a71732f5425a10 Mon Sep 17 00:00:00 2001 From: Renaud Date: Tue, 27 Oct 2026 16:30:56 +0100 Subject: [PATCH] fix(doctrine): keep the resource metadata factory inside the metadata cache --- .../Resources/config/doctrine_mongodb_odm.php | 2 +- .../Bundle/Resources/config/doctrine_orm.php | 2 +- .../DoctrineMetadataFactoryCacheTest.php | 68 +++++++++++++++++++ 3 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 tests/Functional/DoctrineMetadataFactoryCacheTest.php diff --git a/src/Symfony/Bundle/Resources/config/doctrine_mongodb_odm.php b/src/Symfony/Bundle/Resources/config/doctrine_mongodb_odm.php index 65b99d8fe8c..a3bdd224acb 100644 --- a/src/Symfony/Bundle/Resources/config/doctrine_mongodb_odm.php +++ b/src/Symfony/Bundle/Resources/config/doctrine_mongodb_odm.php @@ -221,7 +221,7 @@ $services->alias('api_platform.state.item_provider', 'ApiPlatform\Doctrine\Odm\State\ItemProvider'); $services->set('api_platform.doctrine.odm.metadata.resource.metadata_collection_factory', DoctrineMongoDbOdmResourceCollectionMetadataFactory::class) - ->decorate('api_platform.metadata.resource.metadata_collection_factory', null, -50) + ->decorate('api_platform.metadata.resource.metadata_collection_factory', null, -5) ->args([ service('doctrine_mongodb'), service('api_platform.doctrine.odm.metadata.resource.metadata_collection_factory.inner'), diff --git a/src/Symfony/Bundle/Resources/config/doctrine_orm.php b/src/Symfony/Bundle/Resources/config/doctrine_orm.php index c46ea32f5f8..eda6f76ae0a 100644 --- a/src/Symfony/Bundle/Resources/config/doctrine_orm.php +++ b/src/Symfony/Bundle/Resources/config/doctrine_orm.php @@ -251,7 +251,7 @@ ->args([[]]); $services->set('api_platform.doctrine.orm.metadata.resource.metadata_collection_factory', DoctrineOrmResourceCollectionMetadataFactory::class) - ->decorate('api_platform.metadata.resource.metadata_collection_factory', null, -50) + ->decorate('api_platform.metadata.resource.metadata_collection_factory', null, -5) ->args([ service('doctrine'), service('api_platform.doctrine.orm.metadata.resource.metadata_collection_factory.inner'), diff --git a/tests/Functional/DoctrineMetadataFactoryCacheTest.php b/tests/Functional/DoctrineMetadataFactoryCacheTest.php new file mode 100644 index 00000000000..dac426ecebb --- /dev/null +++ b/tests/Functional/DoctrineMetadataFactoryCacheTest.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Tests\Functional; + +use ApiPlatform\Doctrine\Odm\Metadata\Resource\DoctrineMongoDbOdmResourceCollectionMetadataFactory; +use ApiPlatform\Doctrine\Orm\Metadata\Resource\DoctrineOrmResourceCollectionMetadataFactory; +use ApiPlatform\Metadata\Resource\Factory\CachedResourceMetadataCollectionFactory; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\Symfony\Bundle\Test\ApiTestCase; +use ApiPlatform\Tests\SetupClassResourcesTrait; + +/** + * The Doctrine resource metadata collection factory must be decorated by the + * metadata cache, otherwise it runs again on every ResourceMetadataCollectionFactoryInterface::create() call. + */ +final class DoctrineMetadataFactoryCacheTest extends ApiTestCase +{ + use SetupClassResourcesTrait; + + protected static ?bool $alwaysBootKernel = false; + + /** + * @return class-string[] + */ + public static function getResources(): array + { + return []; + } + + public function testDoctrineFactoryIsInsideTheMetadataCache(): void + { + self::bootKernel(); + + $factory = self::getContainer()->get('api_platform.metadata.resource.metadata_collection_factory'); + $this->assertInstanceOf(CachedResourceMetadataCollectionFactory::class, $factory, 'The metadata cache must be the outermost factory.'); + + $doctrineFactories = [DoctrineOrmResourceCollectionMetadataFactory::class, DoctrineMongoDbOdmResourceCollectionMetadataFactory::class]; + $decorated = []; + while (null !== ($factory = $this->getDecorated($factory))) { + $decorated[] = $factory::class; + } + + $this->assertNotEmpty(array_intersect($doctrineFactories, $decorated), 'The Doctrine metadata factory must be decorated by the metadata cache.'); + } + + private function getDecorated(ResourceMetadataCollectionFactoryInterface $factory): ?ResourceMetadataCollectionFactoryInterface + { + foreach ((new \ReflectionObject($factory))->getProperties() as $property) { + $value = $property->getValue($factory); + if ($value instanceof ResourceMetadataCollectionFactoryInterface) { + return $value; + } + } + + return null; + } +}