Skip to content

Commit

Permalink
Fix compatibility layer for cache services (#1352)
Browse files Browse the repository at this point in the history
Co-authored-by: Gabriel Ostrolucký <[email protected]>
  • Loading branch information
alcaeus and ostrolucky authored Jun 1, 2021
1 parent 00648cb commit e4ba833
Show file tree
Hide file tree
Showing 12 changed files with 241 additions and 96 deletions.
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

0 comments on commit e4ba833

Please sign in to comment.