diff --git a/composer.json b/composer.json index 4172468111a..c5f951a32ff 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": "^4.4|^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..25b95e401b1 100644 --- a/tests/Doctrine/Tests/ORM/ConfigurationTest.php +++ b/tests/Doctrine/Tests/ORM/ConfigurationTest.php @@ -17,14 +17,16 @@ 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; /** * Tests for the Configuration object */ -class ConfigurationTest extends TestCase +class ConfigurationTest extends DoctrineTestCase { /** @var Configuration */ private $configuration; @@ -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->createStub(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 testEnsureProductionSettingsLegacyMetadataArrayCache(): 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