From 7ba9c980b540eff1479b3b73a4d7e9e0528fbcea Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Tue, 16 Feb 2021 13:28:42 +0100 Subject: [PATCH 1/4] Introduce PSR-6 for metadata caching --- composer.json | 4 +- lib/Doctrine/ORM/Configuration.php | 42 ++++++++++++++++-- lib/Doctrine/ORM/EntityManager.php | 44 ++++++++++++++++++- .../Doctrine/Tests/ORM/ConfigurationTest.php | 26 +++++++++-- .../Functional/Locking/LockAgentWorker.php | 3 +- .../Doctrine/Tests/OrmFunctionalTestCase.php | 19 +++++--- tests/Doctrine/Tests/OrmTestCase.php | 18 ++++---- 7 files changed, 132 insertions(+), 24 deletions(-) diff --git a/composer.json b/composer.json index 4172468111a..b3300bee5c5 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "ext-pdo": "*", "composer/package-versions-deprecated": "^1.8", "doctrine/annotations": "^1.12", - "doctrine/cache": "^1.9.1", + "doctrine/cache": "^1.11.0", "doctrine/collections": "^1.5", "doctrine/common": "^3.0.3", "doctrine/dbal": "^2.13.0", @@ -30,6 +30,7 @@ "doctrine/instantiator": "^1.3", "doctrine/lexer": "^1.0", "doctrine/persistence": "^2.0", + "psr/cache": "^1 || ^2 || ^3", "symfony/console": "^3.0|^4.0|^5.0" }, "require-dev": { @@ -37,6 +38,7 @@ "phpstan/phpstan": "^0.12.83", "phpunit/phpunit": "^7.5|^8.5|^9.4", "squizlabs/php_codesniffer": "3.6.0", + "symfony/cache": "^5.2", "symfony/yaml": "^3.4|^4.0|^5.0", "vimeo/psalm": "4.7.0" }, diff --git a/lib/Doctrine/ORM/Configuration.php b/lib/Doctrine/ORM/Configuration.php index 5248358ec98..29852d97516 100644 --- a/lib/Doctrine/ORM/Configuration.php +++ b/lib/Doctrine/ORM/Configuration.php @@ -27,6 +27,7 @@ use Doctrine\Common\Cache\ArrayCache; use Doctrine\Common\Cache\Cache as CacheDriver; use Doctrine\Common\Proxy\AbstractProxyFactory; +use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\Cache\CacheConfiguration; use Doctrine\ORM\Mapping\ClassMetadataFactory; use Doctrine\ORM\Mapping\DefaultEntityListenerResolver; @@ -41,6 +42,7 @@ use Doctrine\ORM\Repository\RepositoryFactory; use Doctrine\Persistence\Mapping\Driver\MappingDriver; use Doctrine\Persistence\ObjectRepository; +use Psr\Cache\CacheItemPoolInterface; use ReflectionClass; use function strtolower; @@ -282,23 +284,51 @@ public function setHydrationCacheImpl(CacheDriver $cacheImpl) /** * Gets the cache driver implementation that is used for metadata caching. * + * @deprecated Deprecated in favor of getMetadataCache + * * @return CacheDriver|null */ public function getMetadataCacheImpl() { + Deprecation::trigger( + 'doctrine/orm', + 'https://github.com/doctrine/orm/issues/8650', + 'Method %s() is deprecated and will be removed in Doctrine ORM 3.0. Use getMetadataCache() instead.', + __METHOD__ + ); + return $this->_attributes['metadataCacheImpl'] ?? null; } /** * Sets the cache driver implementation that is used for metadata caching. * + * @deprecated Deprecated in favor of setMetadataCache + * * @return void */ public function setMetadataCacheImpl(CacheDriver $cacheImpl) { + Deprecation::trigger( + 'doctrine/orm', + 'https://github.com/doctrine/orm/issues/8650', + 'Method %s() is deprecated and will be removed in Doctrine ORM 3.0. Use setMetadataCache() instead.', + __METHOD__ + ); + $this->_attributes['metadataCacheImpl'] = $cacheImpl; } + public function getMetadataCache(): ?CacheItemPoolInterface + { + return $this->_attributes['metadataCache'] ?? null; + } + + public function setMetadataCache(CacheItemPoolInterface $cache): void + { + $this->_attributes['metadataCache'] = $cache; + } + /** * Adds a named DQL query to the configuration. * @@ -387,6 +417,14 @@ public function ensureProductionSettings() throw ORMException::queryCacheUsesNonPersistentCache($queryCacheImpl); } + if ($this->getAutoGenerateProxyClasses()) { + throw ORMException::proxyClassesAlwaysRegenerating(); + } + + if ($this->getMetadataCache()) { + return; + } + $metadataCacheImpl = $this->getMetadataCacheImpl(); if (! $metadataCacheImpl) { @@ -396,10 +434,6 @@ public function ensureProductionSettings() if ($metadataCacheImpl instanceof ArrayCache) { throw ORMException::metadataCacheUsesNonPersistentCache($metadataCacheImpl); } - - if ($this->getAutoGenerateProxyClasses()) { - throw ORMException::proxyClassesAlwaysRegenerating(); - } } /** diff --git a/lib/Doctrine/ORM/EntityManager.php b/lib/Doctrine/ORM/EntityManager.php index 41b877d08da..8489e35135c 100644 --- a/lib/Doctrine/ORM/EntityManager.php +++ b/lib/Doctrine/ORM/EntityManager.php @@ -21,6 +21,8 @@ namespace Doctrine\ORM; use BadMethodCallException; +use Doctrine\Common\Cache\Psr6\CacheAdapter; +use Doctrine\Common\Cache\Psr6\DoctrineProvider; use Doctrine\Common\EventManager; use Doctrine\Common\Util\ClassUtils; use Doctrine\DBAL\Connection; @@ -48,6 +50,7 @@ use function is_object; use function is_string; use function ltrim; +use function method_exists; use function sprintf; /** @@ -164,7 +167,8 @@ protected function __construct(Connection $conn, Configuration $config, EventMan $this->metadataFactory = new $metadataFactoryClassName(); $this->metadataFactory->setEntityManager($this); - $this->metadataFactory->setCacheDriver($this->config->getMetadataCacheImpl()); + + $this->configureMetadataCache(); $this->repositoryFactory = $config->getRepositoryFactory(); $this->unitOfWork = new UnitOfWork($this); @@ -986,4 +990,42 @@ private function checkLockRequirements(int $lockMode, ClassMetadata $class): voi } } } + + private function configureMetadataCache(): void + { + $metadataCache = $this->config->getMetadataCache(); + if (! $metadataCache) { + $this->configureLegacyMetadataCache(); + + return; + } + + // We have a PSR-6 compatible metadata factory. Use cache directly + if (method_exists($this->metadataFactory, 'setCache')) { + $this->metadataFactory->setCache($metadataCache); + + return; + } + + // Wrap PSR-6 cache to provide doctrine/cache interface + $this->metadataFactory->setCacheDriver(DoctrineProvider::wrap($metadataCache)); + } + + private function configureLegacyMetadataCache(): void + { + $metadataCache = $this->config->getMetadataCacheImpl(); + if (! $metadataCache) { + return; + } + + // Metadata factory is not PSR-6 compatible. Use cache directly + if (! method_exists($this->metadataFactory, 'setCache')) { + $this->metadataFactory->setCacheDriver($metadataCache); + + return; + } + + // Wrap doctrine/cache to provide PSR-6 interface + $this->metadataFactory->setCache(CacheAdapter::wrap($metadataCache)); + } } diff --git a/tests/Doctrine/Tests/ORM/ConfigurationTest.php b/tests/Doctrine/Tests/ORM/ConfigurationTest.php index 28c69a93ce8..24606e7bbf5 100644 --- a/tests/Doctrine/Tests/ORM/ConfigurationTest.php +++ b/tests/Doctrine/Tests/ORM/ConfigurationTest.php @@ -19,7 +19,9 @@ use Doctrine\Persistence\Mapping\Driver\MappingDriver; use Doctrine\Tests\Models\DDC753\DDC753CustomRepository; use PHPUnit\Framework\TestCase; +use Psr\Cache\CacheItemPoolInterface; use ReflectionClass; +use Symfony\Component\Cache\Adapter\ArrayAdapter; /** * Tests for the Configuration object @@ -131,6 +133,14 @@ public function testSetGetMetadataCacheImpl(): void $this->assertSame($queryCacheImpl, $this->configuration->getMetadataCacheImpl()); } + public function testSetGetMetadataCache(): void + { + $this->assertNull($this->configuration->getMetadataCache()); + $cache = $this->createMock(CacheItemPoolInterface::class); + $this->configuration->setMetadataCache($cache); + $this->assertSame($cache, $this->configuration->getMetadataCache()); + } + public function testAddGetNamedQuery(): void { $dql = 'SELECT u FROM User u'; @@ -182,7 +192,17 @@ public function testEnsureProductionSettings(): void $this->addToAssertionCount(1); } - public function testEnsureProductionSettingsQueryCache(): void + public function testEnsureProductionSettingsWithNewMetadataCache(): void + { + $this->setProductionSettings('metadata'); + $this->configuration->setMetadataCache(new ArrayAdapter()); + + $this->configuration->ensureProductionSettings(); + + $this->addToAssertionCount(1); + } + + public function testEnsureProductionSettingsMissingQueryCache(): void { $this->setProductionSettings('query'); @@ -192,7 +212,7 @@ public function testEnsureProductionSettingsQueryCache(): void $this->configuration->ensureProductionSettings(); } - public function testEnsureProductionSettingsMetadataCache(): void + public function testEnsureProductionSettingsMissingMetadataCache(): void { $this->setProductionSettings('metadata'); @@ -213,7 +233,7 @@ public function testEnsureProductionSettingsQueryArrayCache(): void $this->configuration->ensureProductionSettings(); } - public function testEnsureProductionSettingsMetadataArrayCache(): void + public function testEnsureProductionSettingsLegacyLegacyMetadataArrayCache(): void { $this->setProductionSettings(); $this->configuration->setMetadataCacheImpl(new ArrayCache()); diff --git a/tests/Doctrine/Tests/ORM/Functional/Locking/LockAgentWorker.php b/tests/Doctrine/Tests/ORM/Functional/Locking/LockAgentWorker.php index b87a685872f..991acdbfead 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Locking/LockAgentWorker.php +++ b/tests/Doctrine/Tests/ORM/Functional/Locking/LockAgentWorker.php @@ -13,6 +13,7 @@ use Doctrine\ORM\EntityManagerInterface; use GearmanWorker; use InvalidArgumentException; +use Symfony\Component\Cache\Adapter\ArrayAdapter; use function assert; use function is_array; @@ -121,9 +122,9 @@ protected function createEntityManager(Connection $conn): EntityManagerInterface $annotDriver = $config->newDefaultAnnotationDriver([__DIR__ . '/../../../Models/'], true); $config->setMetadataDriverImpl($annotDriver); + $config->setMetadataCache(new ArrayAdapter()); $cache = new ArrayCache(); - $config->setMetadataCacheImpl($cache); $config->setQueryCacheImpl($cache); $config->setSQLLogger(new EchoSQLLogger()); diff --git a/tests/Doctrine/Tests/OrmFunctionalTestCase.php b/tests/Doctrine/Tests/OrmFunctionalTestCase.php index 7611f271a6e..dbe8420bcff 100644 --- a/tests/Doctrine/Tests/OrmFunctionalTestCase.php +++ b/tests/Doctrine/Tests/OrmFunctionalTestCase.php @@ -21,7 +21,9 @@ use Exception; use PHPUnit\Framework\AssertionFailedError; use PHPUnit\Framework\Warning; +use Psr\Cache\CacheItemPoolInterface; use RuntimeException; +use Symfony\Component\Cache\Adapter\ArrayAdapter; use Throwable; use function array_map; @@ -50,9 +52,9 @@ abstract class OrmFunctionalTestCase extends OrmTestCase /** * The metadata cache shared between all functional tests. * - * @var Cache|null + * @var Cache|CacheItemPoolInterface|null */ - private static $_metadataCacheImpl = null; + private static $_metadataCache = null; /** * The query cache shared between all functional tests. @@ -699,11 +701,11 @@ protected function getEntityManager( // NOTE: Functional tests use their own shared metadata cache, because // the actual database platform used during execution has effect on some // metadata mapping behaviors (like the choice of the ID generation). - if (self::$_metadataCacheImpl === null) { + if (self::$_metadataCache === null) { if (isset($GLOBALS['DOCTRINE_CACHE_IMPL'])) { - self::$_metadataCacheImpl = new $GLOBALS['DOCTRINE_CACHE_IMPL'](); + self::$_metadataCache = new $GLOBALS['DOCTRINE_CACHE_IMPL'](); } else { - self::$_metadataCacheImpl = new ArrayCache(); + self::$_metadataCache = new ArrayAdapter(); } } @@ -717,7 +719,12 @@ protected function getEntityManager( //FIXME: two different configs! $conn and the created entity manager have // different configs. $config = new Configuration(); - $config->setMetadataCacheImpl(self::$_metadataCacheImpl); + if (self::$_metadataCache instanceof CacheItemPoolInterface) { + $config->setMetadataCache(self::$_metadataCache); + } else { + $config->setMetadataCacheImpl(self::$_metadataCache); + } + $config->setQueryCacheImpl(self::$_queryCacheImpl); $config->setProxyDir(__DIR__ . '/Proxies'); $config->setProxyNamespace('Doctrine\Tests\Proxies'); diff --git a/tests/Doctrine/Tests/OrmTestCase.php b/tests/Doctrine/Tests/OrmTestCase.php index 8bd3c8e5833..28e70423fbf 100644 --- a/tests/Doctrine/Tests/OrmTestCase.php +++ b/tests/Doctrine/Tests/OrmTestCase.php @@ -17,6 +17,8 @@ use Doctrine\ORM\Configuration; use Doctrine\ORM\Mapping\Driver\AnnotationDriver; use Doctrine\Tests\Mocks\EntityManagerMock; +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\Adapter\ArrayAdapter; use function is_array; use function realpath; @@ -29,9 +31,9 @@ abstract class OrmTestCase extends DoctrineTestCase /** * The metadata cache that is shared between all ORM tests (except functional tests). * - * @var Cache|null + * @var CacheItemPoolInterface|null */ - private static $_metadataCacheImpl = null; + private static $_metadataCache = null; /** * The query cache that is shared between all ORM tests (except functional tests). @@ -92,11 +94,11 @@ protected function getTestEntityManager( ): EntityManagerMock { $metadataCache = $withSharedMetadata ? self::getSharedMetadataCacheImpl() - : new ArrayCache(); + : new ArrayAdapter(); $config = new Configuration(); - $config->setMetadataCacheImpl($metadataCache); + $config->setMetadataCache($metadataCache); $config->setMetadataDriverImpl($config->newDefaultAnnotationDriver([], true)); $config->setQueryCacheImpl(self::getSharedQueryCacheImpl()); $config->setProxyDir(__DIR__ . '/Proxies'); @@ -142,13 +144,13 @@ protected function enableSecondLevelCache($log = true): void $this->isSecondLevelCacheLogEnabled = $log; } - private static function getSharedMetadataCacheImpl(): Cache + private static function getSharedMetadataCacheImpl(): ?CacheItemPoolInterface { - if (self::$_metadataCacheImpl === null) { - self::$_metadataCacheImpl = new ArrayCache(); + if (self::$_metadataCache === null) { + self::$_metadataCache = new ArrayAdapter(); } - return self::$_metadataCacheImpl; + return self::$_metadataCache; } private static function getSharedQueryCacheImpl(): Cache From f634c64b7af6f2328819ce60538f5129f4432154 Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Fri, 23 Apr 2021 08:45:23 +0200 Subject: [PATCH 2/4] Use stubs over mocks in tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Grégoire Paris --- tests/Doctrine/Tests/ORM/ConfigurationTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/ConfigurationTest.php b/tests/Doctrine/Tests/ORM/ConfigurationTest.php index 24606e7bbf5..4b09bc070b9 100644 --- a/tests/Doctrine/Tests/ORM/ConfigurationTest.php +++ b/tests/Doctrine/Tests/ORM/ConfigurationTest.php @@ -136,7 +136,7 @@ public function testSetGetMetadataCacheImpl(): void public function testSetGetMetadataCache(): void { $this->assertNull($this->configuration->getMetadataCache()); - $cache = $this->createMock(CacheItemPoolInterface::class); + $cache = $this->createStub(CacheItemPoolInterface::class); $this->configuration->setMetadataCache($cache); $this->assertSame($cache, $this->configuration->getMetadataCache()); } @@ -233,7 +233,7 @@ public function testEnsureProductionSettingsQueryArrayCache(): void $this->configuration->ensureProductionSettings(); } - public function testEnsureProductionSettingsLegacyLegacyMetadataArrayCache(): void + public function testEnsureProductionSettingsLegacyMetadataArrayCache(): void { $this->setProductionSettings(); $this->configuration->setMetadataCacheImpl(new ArrayCache()); From 91387382b7926e0b3e93b9192552dbec384d789d Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Thu, 29 Apr 2021 09:48:27 +0200 Subject: [PATCH 3/4] Allow symfony/cache 4.4 to provide PHP 7.1 support --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index b3300bee5c5..c5f951a32ff 100644 --- a/composer.json +++ b/composer.json @@ -38,7 +38,7 @@ "phpstan/phpstan": "^0.12.83", "phpunit/phpunit": "^7.5|^8.5|^9.4", "squizlabs/php_codesniffer": "3.6.0", - "symfony/cache": "^5.2", + "symfony/cache": "^4.4|^5.2", "symfony/yaml": "^3.4|^4.0|^5.0", "vimeo/psalm": "4.7.0" }, From 46918392012f144f05915d464262de659be22fe0 Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Thu, 29 Apr 2021 12:54:23 +0200 Subject: [PATCH 4/4] Extend DoctrineTestCase to fix missing methods --- tests/Doctrine/Tests/ORM/ConfigurationTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/ConfigurationTest.php b/tests/Doctrine/Tests/ORM/ConfigurationTest.php index 4b09bc070b9..25b95e401b1 100644 --- a/tests/Doctrine/Tests/ORM/ConfigurationTest.php +++ b/tests/Doctrine/Tests/ORM/ConfigurationTest.php @@ -17,8 +17,8 @@ use Doctrine\ORM\ORMException; use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\Persistence\Mapping\Driver\MappingDriver; +use Doctrine\Tests\DoctrineTestCase; use Doctrine\Tests\Models\DDC753\DDC753CustomRepository; -use PHPUnit\Framework\TestCase; use Psr\Cache\CacheItemPoolInterface; use ReflectionClass; use Symfony\Component\Cache\Adapter\ArrayAdapter; @@ -26,7 +26,7 @@ /** * Tests for the Configuration object */ -class ConfigurationTest extends TestCase +class ConfigurationTest extends DoctrineTestCase { /** @var Configuration */ private $configuration;