Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix compatibility layer for cache services #1352

Merged
merged 8 commits into from
Jun 1, 2021
67 changes: 67 additions & 0 deletions DependencyInjection/Compiler/CacheCompatibilityPass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler;

use Doctrine\Common\Cache\CacheProvider;
use Doctrine\Common\Cache\Psr6\CacheAdapter;
use Doctrine\Common\Cache\Psr6\DoctrineProvider;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;

use function array_keys;
use function is_a;
use function trigger_deprecation;

/** @internal */
final class CacheCompatibilityPass implements CompilerPassInterface
{
private const CONFIGURATION_TAG = 'doctrine.orm.configuration';
private const CACHE_METHODS_PSR6_SUPPORT_MAP = [
'setMetadataCache' => true,
'setQueryCacheImpl' => false,
'setResultCacheImpl' => false,
];

public function process(ContainerBuilder $container): void
{
foreach (array_keys($container->findTaggedServiceIds(self::CONFIGURATION_TAG)) as $id) {
foreach ($container->getDefinition($id)->getMethodCalls() as $methodCall) {
if (! isset(self::CACHE_METHODS_PSR6_SUPPORT_MAP[$methodCall[0]])) {
continue;
}

$aliasId = (string) $methodCall[1][0];
$definitionId = (string) $container->getAlias($aliasId);
$isPsr6 = is_a($container->getDefinition($definitionId)->getClass(), CacheItemPoolInterface::class, true);
$shouldBePsr6 = self::CACHE_METHODS_PSR6_SUPPORT_MAP[$methodCall[0]];

if ($shouldBePsr6 === $isPsr6) {
continue;
}

$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
);
}

$compatibilityLayerId = $definitionId . '.compatibility_layer';
$container->setAlias($aliasId, $compatibilityLayerId);
$container->register($compatibilityLayerId, $targetClass)
->setFactory([$targetFactory, 'wrap'])
->addArgument(new Reference($definitionId));
}
}
}
}
5 changes: 3 additions & 2 deletions DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -736,7 +736,6 @@ private function getOrmCacheDriverNode(string $name): ArrayNodeDefinition
$node = $treeBuilder->getRootNode();

$node
->addDefaultsIfNotSet()
->beforeNormalization()
->ifString()
->then(static function ($v): array {
Expand All @@ -751,9 +750,11 @@ private function getOrmCacheDriverNode(string $name): ArrayNodeDefinition

if ($name === 'metadata_cache_driver') {
$node->setDeprecated(...$this->getDeprecationMsg(
'The "metadata_cache_driver" configuration key is deprecated. PHP Array cache is now automatically registered when %kernel.debug% is false.',
'The "metadata_cache_driver" configuration key is deprecated. Remove the configuration to have the cache created automatically.',
'2.3'
));
} else {
$node->addDefaultsIfNotSet();
}

return $node;
Expand Down
91 changes: 29 additions & 62 deletions DependencyInjection/DoctrineExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\ServiceRepositoryCompilerPass;
use Doctrine\Bundle\DoctrineBundle\EventSubscriber\EventSubscriberInterface;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepositoryInterface;
use Doctrine\Common\Cache\CacheProvider;
use Doctrine\Common\Cache\Psr6\CacheAdapter;
use Doctrine\Common\Cache\Psr6\DoctrineProvider;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Connections\MasterSlaveConnection;
use Doctrine\DBAL\Connections\PrimaryReadReplicaConnection;
Expand All @@ -26,7 +23,6 @@
use Doctrine\ORM\Proxy\Autoloader;
use Doctrine\ORM\UnitOfWork;
use LogicException;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Bridge\Doctrine\DependencyInjection\AbstractDoctrineExtension;
use Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator;
use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator;
Expand Down Expand Up @@ -56,7 +52,6 @@
use function array_keys;
use function class_exists;
use function interface_exists;
use function is_a;
use function method_exists;
use function reset;
use function sprintf;
Expand Down Expand Up @@ -883,21 +878,17 @@ protected function getMappingResourceExtension(): string
/**
* {@inheritDoc}
*/
protected function loadCacheDriver($cacheName, $objectManagerName, array $cacheDriver, ContainerBuilder $container, bool $usePsr6 = false): string
protected function loadCacheDriver($cacheName, $objectManagerName, array $cacheDriver, ContainerBuilder $container): string
{
$aliasId = $this->getObjectManagerElementName(sprintf('%s_%s', $objectManagerName, $cacheName));
$isPsr6 = null;

switch ($cacheDriver['type'] ?? 'pool') {
case 'service':
$serviceId = $cacheDriver['id'];

break;

case 'pool':
$pool = $cacheDriver['pool'] ?? $this->createArrayAdapterCachePool($container, $objectManagerName, $cacheName);
$serviceId = $pool;
$isPsr6 = true;
$serviceId = $cacheDriver['pool'] ?? $this->createArrayAdapterCachePool($container, $objectManagerName, $cacheName);
break;

default:
Expand All @@ -909,32 +900,6 @@ protected function loadCacheDriver($cacheName, $objectManagerName, array $cacheD
));
}

if ($isPsr6 === null) {
$definition = $container->getDefinition($serviceId);
$isPsr6 = is_a($definition->getClass(), CacheItemPoolInterface::class, true);
}

$cacheName = str_replace('_cache', '', $cacheName);

// Create a wrapper as required
if ($isPsr6 && ! $usePsr6) {
$wrappedServiceId = sprintf('doctrine.orm.cache.provider.%s.%s', $objectManagerName, $cacheName);

$definition = $container->register($wrappedServiceId, CacheProvider::class);
$definition->setFactory([DoctrineProvider::class, 'wrap']);
$definition->addArgument(new Reference($serviceId));

$serviceId = $wrappedServiceId;
} elseif (! $isPsr6 && $usePsr6) {
$wrappedServiceId = sprintf('cache.doctrine.orm.adapter.%s.%s', $objectManagerName, $cacheName);

$definition = $container->register($wrappedServiceId, CacheItemPoolInterface::class);
$definition->setFactory([CacheAdapter::class, 'wrap']);
$definition->addArgument(new Reference($serviceId));

$serviceId = $wrappedServiceId;
}

$container->setAlias($aliasId, new Alias($serviceId, false));

return $aliasId;
Expand All @@ -947,15 +912,36 @@ protected function loadCacheDriver($cacheName, $objectManagerName, array $cacheD
*/
protected function loadOrmCacheDrivers(array $entityManager, ContainerBuilder $container)
{
$this->loadCacheDriver('metadata_cache', $entityManager['name'], $entityManager['metadata_cache_driver'], $container, true);
$this->loadCacheDriver('result_cache', $entityManager['name'], $entityManager['result_cache_driver'], $container, false);
$this->loadCacheDriver('query_cache', $entityManager['name'], $entityManager['query_cache_driver'], $container, false);
if (isset($entityManager['metadata_cache_driver'])) {
$this->loadCacheDriver('metadata_cache', $entityManager['name'], $entityManager['metadata_cache_driver'], $container);
} else {
$this->createMetadataCache($entityManager['name'], $container);
}

if ($container->getParameter('kernel.debug')) {
return;
$this->loadCacheDriver('result_cache', $entityManager['name'], $entityManager['result_cache_driver'], $container);
$this->loadCacheDriver('query_cache', $entityManager['name'], $entityManager['query_cache_driver'], $container);
}

private function createMetadataCache(string $objectManagerName, ContainerBuilder $container): void
{
$aliasId = $this->getObjectManagerElementName(sprintf('%s_%s', $objectManagerName, 'metadata_cache'));
$cacheId = sprintf('cache.doctrine.orm.%s.%s', $objectManagerName, 'metadata');

$cache = new Definition(ArrayAdapter::class);

if (! $container->getParameter('kernel.debug')) {
$phpArrayFile = '%kernel.cache_dir%' . sprintf('/doctrine/orm/%s_metadata.php', $objectManagerName);
$cacheWarmerServiceId = $this->getObjectManagerElementName(sprintf('%s_%s', $objectManagerName, 'metadata_cache_warmer'));

$container->register($cacheWarmerServiceId, DoctrineMetadataCacheWarmer::class)
->setArguments([new Reference(sprintf('doctrine.orm.%s_entity_manager', $objectManagerName)), $phpArrayFile])
->addTag('kernel.cache_warmer', ['priority' => 1000]); // priority should be higher than ProxyCacheWarmer

$cache = new Definition(PhpArrayAdapter::class, [$phpArrayFile, $cache]);
}

$this->registerMetadataPhpArrayCaching($entityManager['name'], $container);
$container->setDefinition($cacheId, $cache);
$container->setAlias($aliasId, $cacheId);
}

/**
Expand Down Expand Up @@ -1062,23 +1048,4 @@ private function createArrayAdapterCachePool(ContainerBuilder $container, string

return $id;
}

private function registerMetadataPhpArrayCaching(string $entityManagerName, ContainerBuilder $container): void
{
$metadataCacheAlias = $this->getObjectManagerElementName($entityManagerName . '_metadata_cache');
$decoratedMetadataCacheServiceId = (string) $container->getAlias($metadataCacheAlias);
$phpArrayCacheDecoratorServiceId = $decoratedMetadataCacheServiceId . '.php_array';
$phpArrayFile = '%kernel.cache_dir%' . sprintf('/doctrine/orm/%s_metadata.php', $entityManagerName);
$cacheWarmerServiceId = $this->getObjectManagerElementName($entityManagerName . '_metadata_cache_warmer');

$container->register($cacheWarmerServiceId, DoctrineMetadataCacheWarmer::class)
->setArguments([new Reference(sprintf('doctrine.orm.%s_entity_manager', $entityManagerName)), $phpArrayFile])
->addTag('kernel.cache_warmer', ['priority' => 1000]); // priority should be higher than ProxyCacheWarmer

$container->setAlias($metadataCacheAlias, $phpArrayCacheDecoratorServiceId);

$container->register($phpArrayCacheDecoratorServiceId, PhpArrayAdapter::class)
->addArgument($phpArrayFile)
->addArgument(new Reference($decoratedMetadataCacheServiceId));
}
}
2 changes: 2 additions & 0 deletions DoctrineBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Doctrine\Bundle\DoctrineBundle;

use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\CacheCompatibilityPass;
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\CacheSchemaSubscriberPass;
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\DbalSchemaFilterPass;
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\EntityListenerPass;
Expand Down Expand Up @@ -48,6 +49,7 @@ public function build(ContainerBuilder $container)
}
}

$container->addCompilerPass(new CacheCompatibilityPass());
$container->addCompilerPass(new DoctrineValidationPass('orm'));
$container->addCompilerPass(new EntityListenerPass());
$container->addCompilerPass(new ServiceRepositoryCompilerPass());
Expand Down
13 changes: 10 additions & 3 deletions Tests/DependencyInjection/AbstractDoctrineExtensionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
namespace Doctrine\Bundle\DoctrineBundle\Tests\DependencyInjection;

use Doctrine\Bundle\DoctrineBundle\Dbal\BlacklistSchemaAssetFilter;
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\CacheCompatibilityPass;
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\DbalSchemaFilterPass;
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\EntityListenerPass;
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\WellKnownSchemaFilterPass;
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\DoctrineExtension;
use Doctrine\Common\Cache\CacheProvider;
use Doctrine\Common\Cache\Psr6\DoctrineProvider;
use Doctrine\DBAL\Configuration;
use Doctrine\DBAL\Connections\MasterSlaveConnection;
Expand Down Expand Up @@ -445,12 +447,14 @@ public function testLoadMultipleConnections(): void
$this->assertEquals(PhpArrayAdapter::class, $definition->getClass());

$definition = $container->getDefinition((string) $container->getAlias('doctrine.orm.em1_query_cache'));
$this->assertEquals(CacheProvider::class, $definition->getClass());
$this->assertEquals([DoctrineProvider::class, 'wrap'], $definition->getFactory());
$arguments = $definition->getArguments();
$this->assertInstanceOf(Reference::class, $arguments[0]);
$this->assertEquals('cache.doctrine.orm.em1.query', (string) $arguments[0]);

$definition = $container->getDefinition((string) $container->getAlias('doctrine.orm.em1_result_cache'));
$this->assertEquals(CacheProvider::class, $definition->getClass());
$this->assertEquals([DoctrineProvider::class, 'wrap'], $definition->getFactory());
$arguments = $definition->getArguments();
$this->assertInstanceOf(Reference::class, $arguments[0]);
Expand Down Expand Up @@ -731,7 +735,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->assertEquals([DoctrineProvider::class, 'wrap'], $cacheDriverDef->getFactory());
$this->assertDICDefinitionClass($cacheDriverDef, ArrayAdapter::class);
$this->assertDICDefinitionMethodCallOnce($configDef, 'setSecondLevelCacheConfiguration');
$this->assertDICDefinitionMethodCallCount($slcFactoryDef, 'setRegion', [], 3);
$this->assertDICDefinitionMethodCallCount($loggerChainDef, 'setLogger', [], 3);
Expand Down Expand Up @@ -1277,6 +1281,7 @@ private function loadContainer(
): ContainerBuilder {
$container = $this->getContainer($bundles);
$container->registerExtension(new DoctrineExtension());
$container->addCompilerPass(new CacheCompatibilityPass());

$this->loadFromFile($container, $fixture);

Expand Down Expand Up @@ -1450,8 +1455,10 @@ private function assertDICDefinitionNoMethodCall(

private function compileContainer(ContainerBuilder $container): void
{
$container->getCompilerPassConfig()->setOptimizationPasses([new ResolveChildDefinitionsPass()]);
$container->getCompilerPassConfig()->setRemovingPasses([]);
$passConfig = $container->getCompilerPassConfig();
$passConfig->setOptimizationPasses([new ResolveChildDefinitionsPass()]);
$passConfig->setRemovingPasses([]);
$passConfig->addPass(new CacheCompatibilityPass());
$container->compile();
}
}
Expand Down
86 changes: 86 additions & 0 deletions Tests/DependencyInjection/Compiler/CacheCompatibilityPassTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php

namespace Doctrine\Bundle\DoctrineBundle\Tests\DependencyInjection\Compiler;

use Doctrine\Bundle\DoctrineBundle\Tests\DependencyInjection\Fixtures\TestKernel;
use Doctrine\Bundle\DoctrineBundle\Tests\TestCase;
use Doctrine\Common\Cache\Psr6\DoctrineProvider;
use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;

class CacheCompatibilityPassTest extends TestCase
{
use ExpectDeprecationTrait;

public function testLegacyCacheConfigUsingServiceDefinedByApplication(): void
{
$this->expectNotToPerformAssertions();
(new class () extends TestKernel {
public function registerContainerConfiguration(LoaderInterface $loader): void
{
parent::registerContainerConfiguration($loader);
$loader->load(static function (ContainerBuilder $containerBuilder): void {
$containerBuilder->loadFromExtension(
'doctrine',
['orm' => ['query_cache_driver' => ['type' => 'service', 'id' => 'custom_cache_service']]]
);
$containerBuilder->setDefinition(
'custom_cache_service',
(new Definition(DoctrineProvider::class))
->setArguments([new Definition(ArrayAdapter::class)])
->setFactory([DoctrineProvider::class, 'wrap'])
);
});
}
})->boot();
}

/** @group legacy */
public function testMetadataCacheConfigUsingPsr6ServiceDefinedByApplication(): void
{
$this->expectDeprecation('%aThe "metadata_cache_driver" configuration key is deprecated.%a');
(new class (false) extends TestKernel {
public function registerContainerConfiguration(LoaderInterface $loader): void
{
parent::registerContainerConfiguration($loader);
$loader->load(static function (ContainerBuilder $containerBuilder): void {
$containerBuilder->loadFromExtension(
'doctrine',
['orm' => ['metadata_cache_driver' => ['type' => 'service', 'id' => 'custom_cache_service']]]
);
$containerBuilder->setDefinition(
'custom_cache_service',
new Definition(ArrayAdapter::class)
);
});
}
})->boot();
}

/** @group legacy */
public function testMetdataCacheConfigUsingNonPsr6ServiceDefinedByApplication(): void
{
$this->expectDeprecation('Since doctrine/doctrine-bundle 2.4: Configuring doctrine/cache is deprecated. Please update the cache service "custom_cache_service" to use a PSR-6 cache.');
(new class (false) extends TestKernel {
public function registerContainerConfiguration(LoaderInterface $loader): void
{
parent::registerContainerConfiguration($loader);
$loader->load(static function (ContainerBuilder $containerBuilder): void {
$containerBuilder->loadFromExtension(
'doctrine',
['orm' => ['metadata_cache_driver' => ['type' => 'service', 'id' => 'custom_cache_service']]]
);
$containerBuilder->setDefinition(
'custom_cache_service',
(new Definition(DoctrineProvider::class))
->setArguments([new Definition(ArrayAdapter::class)])
->setFactory([DoctrineProvider::class, 'wrap'])
);
});
}
})->boot();
}
}
Loading