From ad3d8cc6b766dc1e8e420694abe563ee7b75104e Mon Sep 17 00:00:00 2001 From: David Maicher Date: Tue, 25 May 2021 20:18:08 +0200 Subject: [PATCH] fix DoctrineBundle 2.4 + ORM 2.9 compatibility --- CHANGELOG.md | 4 +- composer.json | 86 ++++++++------ .../DAMADoctrineTestBundle.php | 4 +- .../DAMADoctrineTestExtension.php | 3 + .../DoctrineTestCompilerPass.php | 38 +++++- .../Doctrine/Cache/Psr6StaticArrayCache.php | 108 ++++++++++++++++++ .../DoctrineTestCompilerPassTest.php | 91 +++++++++++---- .../Cache/Psr6StaticArrayCacheTest.php | 29 +++++ 8 files changed, 299 insertions(+), 64 deletions(-) create mode 100644 src/DAMA/DoctrineTestBundle/Doctrine/Cache/Psr6StaticArrayCache.php create mode 100644 tests/DAMA/DoctrineTestBundle/Doctrine/Cache/Psr6StaticArrayCacheTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 5df69bc..6f02bb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). -## [x.x.x] +## [6.6.0] ### Changed - dropped support for unmaintained Symfony versions 3.4 and 5.1 +- fixed compatibility with DoctrineBundle 2.4+ and ORM 2.9+ by introducing PSR-6 compatible cache +- added dependency on `symfony/cache` ## [6.5.0] ### Changed diff --git a/composer.json b/composer.json index 8b9d8a6..f499d31 100644 --- a/composer.json +++ b/composer.json @@ -1,40 +1,52 @@ { - "name": "dama/doctrine-test-bundle", - "description": "Symfony bundle to isolate doctrine database tests and improve test performance", - "keywords": ["symfony", "doctrine", "tests", "isolation", "performance"], - "type": "symfony-bundle", - "license": "MIT", - "authors": [ - { - "name": "David Maicher", - "email": "mail@dmaicher.de" + "name": "dama/doctrine-test-bundle", + "description": "Symfony bundle to isolate doctrine database tests and improve test performance", + "keywords": [ + "symfony", + "doctrine", + "tests", + "isolation", + "performance" + ], + "type": "symfony-bundle", + "license": "MIT", + "authors": [ + { + "name": "David Maicher", + "email": "mail@dmaicher.de" + } + ], + "require": { + "php": "^7.1 || ^8.0", + "doctrine/dbal": "^2.9.3 || ^3.0", + "doctrine/doctrine-bundle": "^1.11 || ^2.0", + "symfony/cache": "^4.4 || ^5.2", + "symfony/framework-bundle": "^4.4 || ^5.2" + }, + "require-dev": { + "behat/behat": "^3.0", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "symfony/yaml": "^4.4 || ^5.2", + "symfony/phpunit-bridge": "^5.2", + "symfony/process": "^4.4 || ^5.2", + "phpstan/phpstan": "^0.12.85" + }, + "autoload": { + "psr-4": { + "DAMA\\DoctrineTestBundle\\": "src/DAMA/DoctrineTestBundle" + } + }, + "autoload-dev": { + "psr-4": { + "Tests\\": "tests/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "7.0.x-dev" + } + }, + "config": { + "sort-packages": true } - ], - "require": { - "php": "^7.1 || ^8.0", - "symfony/framework-bundle": "^4.4 || ^5.2", - "doctrine/dbal": "^2.9.3 || ^3.0", - "doctrine/doctrine-bundle": "^1.11 || ^2.0" - }, - "require-dev": { - "behat/behat": "^3.0", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", - "symfony/yaml": "^4.4 || ^5.2", - "symfony/phpunit-bridge": "^5.2", - "symfony/process": "^4.4 || ^5.2", - "phpstan/phpstan": "^0.12.85" - }, - "autoload": { - "psr-4": { - "DAMA\\DoctrineTestBundle\\": "src/DAMA/DoctrineTestBundle" - } - }, - "autoload-dev": { - "psr-4": { "Tests\\": "tests/" } - }, - "extra": { - "branch-alias": { - "dev-master": "7.0.x-dev" - } - } } diff --git a/src/DAMA/DoctrineTestBundle/DAMADoctrineTestBundle.php b/src/DAMA/DoctrineTestBundle/DAMADoctrineTestBundle.php index 7d2cb46..9a4a450 100644 --- a/src/DAMA/DoctrineTestBundle/DAMADoctrineTestBundle.php +++ b/src/DAMA/DoctrineTestBundle/DAMADoctrineTestBundle.php @@ -3,6 +3,7 @@ namespace DAMA\DoctrineTestBundle; use DAMA\DoctrineTestBundle\DependencyInjection\DoctrineTestCompilerPass; +use Symfony\Component\DependencyInjection\Compiler\PassConfig; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; @@ -14,6 +15,7 @@ class DAMADoctrineTestBundle extends Bundle public function build(ContainerBuilder $container): void { parent::build($container); - $container->addCompilerPass(new DoctrineTestCompilerPass()); + // lower priority than CacheCompatibilityPass from DoctrineBundle + $container->addCompilerPass(new DoctrineTestCompilerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -1); } } diff --git a/src/DAMA/DoctrineTestBundle/DependencyInjection/DAMADoctrineTestExtension.php b/src/DAMA/DoctrineTestBundle/DependencyInjection/DAMADoctrineTestExtension.php index be6a91d..79112e5 100644 --- a/src/DAMA/DoctrineTestBundle/DependencyInjection/DAMADoctrineTestExtension.php +++ b/src/DAMA/DoctrineTestBundle/DependencyInjection/DAMADoctrineTestExtension.php @@ -18,6 +18,9 @@ public function load(array $configs, ContainerBuilder $container): void $this->processedConfig = $this->processConfiguration($configuration, $configs); } + /** + * @internal + */ public function getProcessedConfig(): array { return $this->processedConfig; diff --git a/src/DAMA/DoctrineTestBundle/DependencyInjection/DoctrineTestCompilerPass.php b/src/DAMA/DoctrineTestBundle/DependencyInjection/DoctrineTestCompilerPass.php index 29b2607..35e5d0c 100644 --- a/src/DAMA/DoctrineTestBundle/DependencyInjection/DoctrineTestCompilerPass.php +++ b/src/DAMA/DoctrineTestBundle/DependencyInjection/DoctrineTestCompilerPass.php @@ -2,8 +2,11 @@ namespace DAMA\DoctrineTestBundle\DependencyInjection; +use DAMA\DoctrineTestBundle\Doctrine\Cache\Psr6StaticArrayCache; use DAMA\DoctrineTestBundle\Doctrine\Cache\StaticArrayCache; use DAMA\DoctrineTestBundle\Doctrine\DBAL\StaticConnectionFactory; +use Doctrine\Common\Cache\Cache; +use Psr\Cache\CacheItemPoolInterface; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; @@ -53,12 +56,13 @@ public function process(ContainerBuilder $container): void foreach ($cacheNames as $cacheName) { $cacheServiceId = sprintf($cacheName, $name); - if ($container->hasAlias($cacheServiceId)) { - $container->removeAlias($cacheServiceId); + + if (!$container->has($cacheServiceId)) { + // might happen if ORM is not used + continue; } - $cache = new Definition(StaticArrayCache::class); - $cache->addMethodCall('setNamespace', [sha1($cacheServiceId)]); //make sure we have no key collisions - $container->setDefinition($cacheServiceId, $cache); + + $this->registerStaticCache($container, $container->findDefinition($cacheServiceId), $cacheServiceId); } } } @@ -80,4 +84,28 @@ private function addConnectionOptions(ContainerBuilder $container, string $name) $connectionOptions['dama.connection_name'] = $name; $connectionDefinition->replaceArgument(0, $connectionOptions); } + + private function registerStaticCache( + ContainerBuilder $container, + Definition $originalCacheServiceDefinition, + string $cacheServiceId + ): void { + $cache = new Definition(); + $namespace = sha1($cacheServiceId); + + if (is_a($originalCacheServiceDefinition->getClass(), CacheItemPoolInterface::class, true)) { + $cache->setClass(Psr6StaticArrayCache::class); + $cache->setArgument(0, $namespace); //make sure we have no key collisions + } elseif (is_a($originalCacheServiceDefinition->getClass(), Cache::class, true)) { + $cache->setClass(StaticArrayCache::class); + $cache->addMethodCall('setNamespace', [$namespace]); //make sure we have no key collisions + } else { + throw new \InvalidArgumentException(sprintf('Unsupported cache class "%s" found on service "%s".', $originalCacheServiceDefinition->getClass(), $cacheServiceId)); + } + + if ($container->hasAlias($cacheServiceId)) { + $container->removeAlias($cacheServiceId); + } + $container->setDefinition($cacheServiceId, $cache); + } } diff --git a/src/DAMA/DoctrineTestBundle/Doctrine/Cache/Psr6StaticArrayCache.php b/src/DAMA/DoctrineTestBundle/Doctrine/Cache/Psr6StaticArrayCache.php new file mode 100644 index 0000000..8201fed --- /dev/null +++ b/src/DAMA/DoctrineTestBundle/Doctrine/Cache/Psr6StaticArrayCache.php @@ -0,0 +1,108 @@ + + */ + private static $adaptersByNamespace; + + /** + * @var ArrayAdapter + */ + private $adapter; + + public function __construct(string $namespace) + { + if (!isset(self::$adaptersByNamespace[$namespace])) { + self::$adaptersByNamespace[$namespace] = new ArrayAdapter(0, false); + } + $this->adapter = self::$adaptersByNamespace[$namespace]; + } + + /** + * @internal + */ + public static function reset(): void + { + self::$adaptersByNamespace = []; + } + + /** + * {@inheritdoc} + */ + public function getItem($key) + { + return $this->adapter->getItem($key); + } + + /** + * {@inheritdoc} + */ + public function getItems(array $keys = []) + { + return $this->adapter->getItems($keys); + } + + /** + * {@inheritdoc} + */ + public function hasItem($key) + { + return $this->adapter->hasItem($key); + } + + /** + * {@inheritdoc} + */ + public function clear() + { + return $this->adapter->clear(); + } + + /** + * {@inheritdoc} + */ + public function deleteItem($key) + { + return $this->adapter->deleteItem($key); + } + + /** + * {@inheritdoc} + */ + public function deleteItems(array $keys) + { + return $this->adapter->deleteItems($keys); + } + + /** + * {@inheritdoc} + */ + public function save(CacheItemInterface $item) + { + return $this->adapter->save($item); + } + + /** + * {@inheritdoc} + */ + public function saveDeferred(CacheItemInterface $item) + { + return $this->adapter->saveDeferred($item); + } + + /** + * {@inheritdoc} + */ + public function commit() + { + return $this->adapter->commit(); + } +} diff --git a/tests/DAMA/DoctrineTestBundle/DependencyInjection/DoctrineTestCompilerPassTest.php b/tests/DAMA/DoctrineTestBundle/DependencyInjection/DoctrineTestCompilerPassTest.php index 39c6742..72bb035 100644 --- a/tests/DAMA/DoctrineTestBundle/DependencyInjection/DoctrineTestCompilerPassTest.php +++ b/tests/DAMA/DoctrineTestBundle/DependencyInjection/DoctrineTestCompilerPassTest.php @@ -4,15 +4,27 @@ use DAMA\DoctrineTestBundle\DependencyInjection\DAMADoctrineTestExtension; use DAMA\DoctrineTestBundle\DependencyInjection\DoctrineTestCompilerPass; +use DAMA\DoctrineTestBundle\Doctrine\Cache\Psr6StaticArrayCache; use DAMA\DoctrineTestBundle\Doctrine\Cache\StaticArrayCache; use Doctrine\Bundle\DoctrineBundle\ConnectionFactory; +use Doctrine\Common\Cache\ArrayCache; use Doctrine\DBAL\Connection; use PHPUnit\Framework\TestCase; +use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; class DoctrineTestCompilerPassTest extends TestCase { + private const CACHE_SERVICE_IDS = [ + 'doctrine.orm.a_metadata_cache', + 'doctrine.orm.b_metadata_cache', + 'doctrine.orm.c_metadata_cache', + 'doctrine.orm.a_query_cache', + 'doctrine.orm.b_query_cache', + 'doctrine.orm.c_query_cache', + ]; + /** * @dataProvider processDataProvider */ @@ -31,7 +43,7 @@ public function testProcess(array $config, callable $assertCallback, callable $e $containerBuilder->setDefinition('doctrine.dbal.connection_factory', new Definition(ConnectionFactory::class)); if ($expectationCallback !== null) { - $expectationCallback($this); + $expectationCallback($this, $containerBuilder); } (new DoctrineTestCompilerPass())->process($containerBuilder); @@ -41,12 +53,14 @@ public function testProcess(array $config, callable $assertCallback, callable $e public function processDataProvider(): \Generator { + $defaultConfig = [ + 'enable_static_connection' => true, + 'enable_static_meta_data_cache' => true, + 'enable_static_query_cache' => true, + ]; + yield 'default config' => [ - [ - 'enable_static_connection' => true, - 'enable_static_meta_data_cache' => true, - 'enable_static_query_cache' => true, - ], + $defaultConfig, function (ContainerBuilder $containerBuilder): void { $this->assertTrue($containerBuilder->hasDefinition('dama.doctrine.dbal.connection_factory')); $this->assertSame( @@ -54,21 +68,9 @@ function (ContainerBuilder $containerBuilder): void { $containerBuilder->getDefinition('dama.doctrine.dbal.connection_factory')->getDecoratedService()[0] ); - $cacheServiceIds = [ - 'doctrine.orm.a_metadata_cache', - 'doctrine.orm.b_metadata_cache', - 'doctrine.orm.c_metadata_cache', - 'doctrine.orm.a_query_cache', - 'doctrine.orm.b_metadata_cache', - 'doctrine.orm.c_metadata_cache', - ]; - - foreach ($cacheServiceIds as $id) { + foreach (self::CACHE_SERVICE_IDS as $id) { $this->assertFalse($containerBuilder->hasAlias($id)); - $this->assertEquals( - (new Definition(StaticArrayCache::class))->addMethodCall('setNamespace', [sha1($id)]), - $containerBuilder->getDefinition($id) - ); + $this->assertFalse($containerBuilder->hasDefinition($id)); } $this->assertSame([ @@ -132,5 +134,54 @@ function (TestCase $testCase): void { $testCase->expectExceptionMessage('Unknown doctrine dbal connection name(s): foo, bar.'); }, ]; + + yield 'legacy doctrine/cache ORM services' => [ + $defaultConfig, + function (ContainerBuilder $containerBuilder): void { + foreach (self::CACHE_SERVICE_IDS as $id) { + $this->assertFalse($containerBuilder->hasAlias($id)); + $this->assertEquals( + (new Definition(StaticArrayCache::class))->addMethodCall('setNamespace', [sha1($id)]), + $containerBuilder->getDefinition($id) + ); + } + }, + function (self $testCase, ContainerBuilder $containerBuilder): void { + foreach (self::CACHE_SERVICE_IDS as $id) { + $containerBuilder->register($id, ArrayCache::class); + } + }, + ]; + + yield 'psr6 ORM cache services' => [ + $defaultConfig, + function (ContainerBuilder $containerBuilder): void { + foreach (self::CACHE_SERVICE_IDS as $id) { + $this->assertFalse($containerBuilder->hasAlias($id)); + $this->assertEquals( + (new Definition(Psr6StaticArrayCache::class))->setArgument(0, sha1($id)), + $containerBuilder->getDefinition($id) + ); + } + }, + function (self $testCase, ContainerBuilder $containerBuilder): void { + foreach (self::CACHE_SERVICE_IDS as $id) { + $containerBuilder->register($id, ArrayAdapter::class); + } + }, + ]; + + yield 'invalid ORM cache services' => [ + $defaultConfig, + function (ContainerBuilder $containerBuilder): void { + }, + function (self $testCase, ContainerBuilder $containerBuilder): void { + $testCase->expectException(\InvalidArgumentException::class); + $testCase->expectExceptionMessage('Unsupported cache class "stdClass" found on service "doctrine.orm.a_metadata_cache"'); + foreach (self::CACHE_SERVICE_IDS as $id) { + $containerBuilder->register($id, \stdClass::class); + } + }, + ]; } } diff --git a/tests/DAMA/DoctrineTestBundle/Doctrine/Cache/Psr6StaticArrayCacheTest.php b/tests/DAMA/DoctrineTestBundle/Doctrine/Cache/Psr6StaticArrayCacheTest.php new file mode 100644 index 0000000..88e297a --- /dev/null +++ b/tests/DAMA/DoctrineTestBundle/Doctrine/Cache/Psr6StaticArrayCacheTest.php @@ -0,0 +1,29 @@ +getItem('one'); + $this->assertFalse($item->isHit()); + + $value = new \stdClass(); + $item->set($value); + $cache->save($item); + + $cache = new Psr6StaticArrayCache('bar'); + $this->assertFalse($cache->getItem('one')->isHit()); + + $cache = new Psr6StaticArrayCache('foo'); + $this->assertTrue($cache->getItem('one')->isHit()); + $this->assertSame($value, $cache->getItem('one')->get()); + } +}