diff --git a/DependencyInjection/Compiler/CacheCompatibilityPass.php b/DependencyInjection/Compiler/CacheCompatibilityPass.php index 3e4489f86..83b62d8fc 100644 --- a/DependencyInjection/Compiler/CacheCompatibilityPass.php +++ b/DependencyInjection/Compiler/CacheCompatibilityPass.php @@ -9,9 +9,11 @@ use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; use function array_keys; +use function assert; use function is_a; use function trigger_deprecation; @@ -29,44 +31,80 @@ public function process(ContainerBuilder $container): void { foreach (array_keys($container->findTaggedServiceIds(self::CONFIGURATION_TAG)) as $id) { foreach ($container->getDefinition($id)->getMethodCalls() as $methodCall) { + if ($methodCall[0] === 'setSecondLevelCacheConfiguration') { + $this->updateSecondLevelCache($container, $methodCall[1][0]); + continue; + } + if (! isset(self::CACHE_METHODS_PSR6_SUPPORT_MAP[$methodCall[0]])) { continue; } $aliasId = (string) $methodCall[1][0]; $definitionId = (string) $container->getAlias($aliasId); - $definition = $container->getDefinition($definitionId); $shouldBePsr6 = self::CACHE_METHODS_PSR6_SUPPORT_MAP[$methodCall[0]]; - while (! $definition->getClass() && $definition instanceof ChildDefinition) { - $definition = $container->findDefinition($definition->getParent()); - } + $this->wrapIfNecessary($container, $aliasId, $definitionId, $shouldBePsr6); + } + } + } - if ($shouldBePsr6 === is_a($definition->getClass(), CacheItemPoolInterface::class, true)) { - continue; - } + private function updateSecondLevelCache(ContainerBuilder $container, Definition $slcConfigDefinition): void + { + foreach ($slcConfigDefinition->getMethodCalls() as $methodCall) { + if ($methodCall[0] !== 'setCacheFactory') { + continue; + } - $targetClass = CacheProvider::class; - $targetFactory = DoctrineProvider::class; + $factoryDefinition = $methodCall[1][0]; + assert($factoryDefinition instanceof Definition); + $aliasId = (string) $factoryDefinition->getArgument(1); + $this->wrapIfNecessary($container, $aliasId, (string) $container->getAlias($aliasId), false); + break; + } + } - if ($shouldBePsr6) { - $targetClass = CacheItemPoolInterface::class; - $targetFactory = CacheAdapter::class; + private function createCompatibilityLayerDefinition(ContainerBuilder $container, string $definitionId, bool $shouldBePsr6): ?Definition + { + $definition = $container->getDefinition($definitionId); - trigger_deprecation( - 'doctrine/doctrine-bundle', - '2.4', - 'Configuring doctrine/cache is deprecated. Please update the cache service "%s" to use a PSR-6 cache.', - $definitionId - ); - } + while (! $definition->getClass() && $definition instanceof ChildDefinition) { + $definition = $container->findDefinition($definition->getParent()); + } - $compatibilityLayerId = $definitionId . '.compatibility_layer'; - $container->setAlias($aliasId, $compatibilityLayerId); - $container->register($compatibilityLayerId, $targetClass) - ->setFactory([$targetFactory, 'wrap']) - ->addArgument(new Reference($definitionId)); - } + if ($shouldBePsr6 === is_a($definition->getClass(), CacheItemPoolInterface::class, true)) { + return null; + } + + $targetClass = CacheProvider::class; + $targetFactory = DoctrineProvider::class; + + if ($shouldBePsr6) { + $targetClass = CacheItemPoolInterface::class; + $targetFactory = CacheAdapter::class; + + trigger_deprecation( + 'doctrine/doctrine-bundle', + '2.4', + 'Configuring doctrine/cache is deprecated. Please update the cache service "%s" to use a PSR-6 cache.', + $definitionId + ); } + + return (new Definition($targetClass)) + ->setFactory([$targetFactory, 'wrap']) + ->addArgument(new Reference($definitionId)); + } + + private function wrapIfNecessary(ContainerBuilder $container, string $aliasId, string $definitionId, bool $shouldBePsr6): void + { + $compatibilityLayer = $this->createCompatibilityLayerDefinition($container, $definitionId, $shouldBePsr6); + if ($compatibilityLayer === null) { + return; + } + + $compatibilityLayerId = $definitionId . '.compatibility_layer'; + $container->setAlias($aliasId, $compatibilityLayerId); + $container->setDefinition($compatibilityLayerId, $compatibilityLayer); } } diff --git a/Tests/DependencyInjection/AbstractDoctrineExtensionTest.php b/Tests/DependencyInjection/AbstractDoctrineExtensionTest.php index a6634341d..674efeaa1 100644 --- a/Tests/DependencyInjection/AbstractDoctrineExtensionTest.php +++ b/Tests/DependencyInjection/AbstractDoctrineExtensionTest.php @@ -14,6 +14,7 @@ use Doctrine\DBAL\Connections\MasterSlaveConnection; use Doctrine\DBAL\Connections\PrimaryReadReplicaConnection; use Doctrine\DBAL\DriverManager; +use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManagerInterface; use Generator; use InvalidArgumentException; @@ -681,6 +682,120 @@ public function testSetQuoteStrategy(): void $this->assertDICDefinitionMethodCallOnce($def2, 'setQuoteStrategy', [0 => new Reference('doctrine.orm.quote_strategy.ansi')]); } + /** + * @dataProvider cacheConfigProvider + * @group legacy + */ + public function testCacheConfig(?string $expectedClass, string $entityManagerName, ?string $cacheGetter): void + { + if (! interface_exists(EntityManagerInterface::class)) { + self::markTestSkipped('This test requires ORM'); + } + + $container = $this->loadContainer('orm_caches'); + + $entityManagerId = sprintf('doctrine.orm.%s_entity_manager', $entityManagerName); + + $em = $container->get($entityManagerId); + assert($em instanceof EntityManager); + + $this->assertInstanceOf(EntityManagerInterface::class, $em); + + if ($cacheGetter === null) { + return; + } + + $configuration = $em->getConfiguration(); + $cache = $configuration->$cacheGetter(); + + if ($expectedClass === null) { + $this->assertNull($cache); + } else { + $this->assertInstanceOf($expectedClass, $cache); + } + } + + public static function cacheConfigProvider(): Generator + { + yield 'metadata_cache_none' => [ + 'expectedClass' => PhpArrayAdapter::class, + 'entityManagerName' => 'metadata_cache_none', + 'cacheGetter' => 'getMetadataCache', + ]; + + yield 'metadata_cache_pool' => [ + 'expectedClass' => ArrayAdapter::class, + 'entityManagerName' => 'metadata_cache_pool', + 'cacheGetter' => 'getMetadataCache', + ]; + + yield 'metadata_cache_service_psr6' => [ + 'expectedClass' => ArrayAdapter::class, + 'entityManagerName' => 'metadata_cache_service_psr6', + 'cacheGetter' => 'getMetadataCache', + ]; + + yield 'metadata_cache_service_doctrine' => [ + 'expectedClass' => ArrayAdapter::class, + 'entityManagerName' => 'metadata_cache_service_doctrine', + 'cacheGetter' => 'getMetadataCache', + ]; + + yield 'query_cache_pool' => [ + 'expectedClass' => DoctrineProvider::class, + 'entityManagerName' => 'query_cache_pool', + 'cacheGetter' => 'getQueryCacheImpl', + ]; + + yield 'query_cache_service_psr6' => [ + 'expectedClass' => DoctrineProvider::class, + 'entityManagerName' => 'query_cache_service_psr6', + 'cacheGetter' => 'getQueryCacheImpl', + ]; + + yield 'query_cache_service_doctrine' => [ + 'expectedClass' => DoctrineProvider::class, + 'entityManagerName' => 'query_cache_service_doctrine', + 'cacheGetter' => 'getQueryCacheImpl', + ]; + + yield 'result_cache_pool' => [ + 'expectedClass' => DoctrineProvider::class, + 'entityManagerName' => 'result_cache_pool', + 'cacheGetter' => 'getResultCacheImpl', + ]; + + yield 'result_cache_service_psr6' => [ + 'expectedClass' => DoctrineProvider::class, + 'entityManagerName' => 'result_cache_service_psr6', + 'cacheGetter' => 'getResultCacheImpl', + ]; + + yield 'result_cache_service_doctrine' => [ + 'expectedClass' => DoctrineProvider::class, + 'entityManagerName' => 'result_cache_service_doctrine', + 'cacheGetter' => 'getResultCacheImpl', + ]; + + yield 'second_level_cache_pool' => [ + 'expectedClass' => null, + 'entityManagerName' => 'second_level_cache_pool', + 'cacheGetter' => null, + ]; + + yield 'second_level_cache_service_psr6' => [ + 'expectedClass' => null, + 'entityManagerName' => 'second_level_cache_service_psr6', + 'cacheGetter' => null, + ]; + + yield 'second_level_cache_service_doctrine' => [ + 'expectedClass' => null, + 'entityManagerName' => 'second_level_cache_service_doctrine', + 'cacheGetter' => null, + ]; + } + public function testSecondLevelCache(): void { if (! interface_exists(EntityManagerInterface::class)) { @@ -723,7 +838,7 @@ public function testSecondLevelCache(): void $this->assertDICDefinitionClass($myEntityRegionDef, '%doctrine.orm.second_level_cache.default_region.class%'); $this->assertDICDefinitionClass($loggerChainDef, '%doctrine.orm.second_level_cache.logger_chain.class%'); $this->assertDICDefinitionClass($loggerStatisticsDef, '%doctrine.orm.second_level_cache.logger_statistics.class%'); - $this->assertDICDefinitionClass($cacheDriverDef, ArrayAdapter::class); + $this->assertDICDefinitionClass($cacheDriverDef, CacheProvider::class); $this->assertDICDefinitionMethodCallOnce($configDef, 'setSecondLevelCacheConfiguration'); $this->assertDICDefinitionMethodCallCount($slcFactoryDef, 'setRegion', [], 3); $this->assertDICDefinitionMethodCallCount($loggerChainDef, 'setLogger', [], 3); @@ -1269,7 +1384,6 @@ private function loadContainer( ): ContainerBuilder { $container = $this->getContainer($bundles); $container->registerExtension(new DoctrineExtension()); - $container->addCompilerPass(new CacheCompatibilityPass()); $this->loadFromFile($container, $fixture); diff --git a/Tests/DependencyInjection/DoctrineExtensionTest.php b/Tests/DependencyInjection/DoctrineExtensionTest.php index fb387eaa7..f0dece540 100644 --- a/Tests/DependencyInjection/DoctrineExtensionTest.php +++ b/Tests/DependencyInjection/DoctrineExtensionTest.php @@ -1081,6 +1081,9 @@ public function testShardManager(): void $this->assertEquals($managerClass, $bazManagerDef->getClass()); } + // Disabled to prevent changing the comment below to a single-line annotation + // phpcs:disable SlevomatCodingStandard.Commenting.RequireOneLineDocComment.MultiLineDocComment + /** * @requires PHP 8 */ @@ -1118,6 +1121,8 @@ public function testAsEntityListenerAttribute() $this->assertSame([$expected], $definition->getTag('doctrine.orm.entity_listener')); } + // phpcs:enable + /** @param list $bundles */ private function getContainer(array $bundles = ['YamlBundle'], string $vendor = ''): ContainerBuilder { diff --git a/Tests/DependencyInjection/Fixtures/config/xml/orm_caches.xml b/Tests/DependencyInjection/Fixtures/config/xml/orm_caches.xml new file mode 100644 index 000000000..dbe5b80d4 --- /dev/null +++ b/Tests/DependencyInjection/Fixtures/config/xml/orm_caches.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/DependencyInjection/Fixtures/config/yml/orm_caches.yml b/Tests/DependencyInjection/Fixtures/config/yml/orm_caches.yml new file mode 100644 index 000000000..987388247 --- /dev/null +++ b/Tests/DependencyInjection/Fixtures/config/yml/orm_caches.yml @@ -0,0 +1,76 @@ +services: + cache.psr6: + class: Symfony\Component\Cache\Adapter\ArrayAdapter + cache.doctrine: + class: Doctrine\Common\Cache\Psr6\DoctrineProvider + factory: [Doctrine\Common\Cache\Psr6\DoctrineProvider, wrap] + arguments: ["@cache.psr6"] + +doctrine: + dbal: + default_connection: default + connections: + default: + dbname: db + + orm: + default_entity_manager: metadata_cache_none + entity_managers: + metadata_cache_none: ~ + metadata_cache_pool: + metadata_cache_driver: + type: pool + pool: cache.psr6 + metadata_cache_service_psr6: + metadata_cache_driver: + type: service + id: cache.psr6 + metadata_cache_service_doctrine: + metadata_cache_driver: + type: service + id: cache.doctrine + + query_cache_pool: + query_cache_driver: + type: pool + pool: cache.psr6 + query_cache_service_psr6: + query_cache_driver: + type: service + id: cache.psr6 + query_cache_service_doctrine: + query_cache_driver: + type: service + id: cache.doctrine + + result_cache_pool: + result_cache_driver: + type: pool + pool: cache.psr6 + result_cache_service_psr6: + result_cache_driver: + type: service + id: cache.psr6 + result_cache_service_doctrine: + result_cache_driver: + type: service + id: cache.doctrine + + second_level_cache_pool: + second_level_cache: + enabled: true + region_cache_driver: + type: pool + pool: cache.psr6 + second_level_cache_service_psr6: + second_level_cache: + enabled: true + region_cache_driver: + type: service + id: cache.psr6 + second_level_cache_service_doctrine: + second_level_cache: + enabled: true + region_cache_driver: + type: service + id: cache.doctrine