Cache namespace from Setup::createCacheConfiguration gets lost #6782

Open
opened 2026-01-22 15:38:34 +01:00 by admin · 8 comments
Owner

Originally created by @flack on GitHub (Jul 6, 2021).

BC Break Report

Q A
BC Break yes
Version 2.9.0

Summary

I'm running multiple independent websites on the same server. They all use Doctrine and share the same Memcache instance for ClassMetadata cache. In some cases, the sites use the same entity classnames, but with different fields.

Previous behavior

Until now, everything was working fine, because each site uses a separate proxy dir, and the proxy dir is used as a namespace in the cache setup:

3c805b22b4/lib/Doctrine/ORM/Tools/Setup.php (L172)

If I called dump($em->getConfiguration()->getMetadataCacheImpl()); on doctrine/orm 2.8, I would get the following output:

Screenshot_20210706_144656

Current behavior

The same call to dump($em->getConfiguration()->getMetadataCacheImpl()); on doctrine/orm 2.9 prints the following output:

Screenshot_20210706_145844

The visible problem is that now only one of my sites is working, the other one gets wrong ClassMetadata information and then prints various errors. When I clear the cache, then this site starts working and the other one is throwing errors, because they overwrite each other's cache entries

I'm not exactly sure where it happens, but the first place I noticed the namespace getting lost is in the CacheAdapter::wrap here:

3c805b22b4/lib/Doctrine/ORM/Tools/Setup.php (L144)

if I dump($cache->getPool(), $cache) in line 39 of doctrine/cache/lib/Doctrine/Common/Cache/Psr6/CacheAdapter.php it looks like this:

Screenshot_20210706_145224

so the namespace information is available on the $cache object, but not on the inner pool variable.

How to reproduce

In principle it should be enough to create a connection and an EntityManager:

$config = \Doctrine\ORM\Tools\Setup::createConfiguration($dev_mode, $some_dir . '/cache');
$em = \Doctrine\ORM\EntityManager::create($db_config, $config);
dump($em->getConfiguration()->getMetadataCacheImpl());
Originally created by @flack on GitHub (Jul 6, 2021). ### BC Break Report <!-- Fill in the relevant information below to help triage your issue. --> | Q | A |------------ | ------ | BC Break | yes | Version | 2.9.0 #### Summary I'm running multiple independent websites on the same server. They all use Doctrine and share the same Memcache instance for ClassMetadata cache. In some cases, the sites use the same entity classnames, but with different fields. #### Previous behavior Until now, everything was working fine, because each site uses a separate proxy dir, and the proxy dir is used as a namespace in the cache setup: https://github.com/doctrine/orm/blob/3c805b22b473a8599ebe7fc89d4f15c95b7bee89/lib/Doctrine/ORM/Tools/Setup.php#L172 If I called `dump($em->getConfiguration()->getMetadataCacheImpl());` on doctrine/orm 2.8, I would get the following output: ![Screenshot_20210706_144656](https://user-images.githubusercontent.com/425166/124602468-2cd4ad80-de69-11eb-87f1-b58328bfa866.png) #### Current behavior The same call to `dump($em->getConfiguration()->getMetadataCacheImpl());` on doctrine/orm 2.9 prints the following output: ![Screenshot_20210706_145844](https://user-images.githubusercontent.com/425166/124603879-acaf4780-de6a-11eb-81bf-e5f0778c1953.png) The visible problem is that now only one of my sites is working, the other one gets wrong ClassMetadata information and then prints various errors. When I clear the cache, then this site starts working and the other one is throwing errors, because they overwrite each other's cache entries I'm not exactly sure where it happens, but the first place I noticed the namespace getting lost is in the `CacheAdapter::wrap` here: https://github.com/doctrine/orm/blob/3c805b22b473a8599ebe7fc89d4f15c95b7bee89/lib/Doctrine/ORM/Tools/Setup.php#L144 if I `dump($cache->getPool(), $cache)` in line 39 of doctrine/cache/lib/Doctrine/Common/Cache/Psr6/CacheAdapter.php it looks like this: ![Screenshot_20210706_145224](https://user-images.githubusercontent.com/425166/124603008-c7cd8780-de69-11eb-98d5-7a755da0c316.png) so the namespace information is available on the `$cache` object, but not on the inner pool variable. #### How to reproduce In principle it should be enough to create a connection and an EntityManager: ```php $config = \Doctrine\ORM\Tools\Setup::createConfiguration($dev_mode, $some_dir . '/cache'); $em = \Doctrine\ORM\EntityManager::create($db_config, $config); dump($em->getConfiguration()->getMetadataCacheImpl()); ```
Author
Owner

@flack commented on GitHub (Jul 7, 2021):

Here's a minimal diff that seems to fix the problem for me:

--- a/lib/Doctrine/ORM/Tools/Setup.php
+++ b/lib/Doctrine/ORM/Tools/Setup.php
@@ -157,24 +157,10 @@ class Setup
 
     private static function createCacheConfiguration(bool $isDevMode, string $proxyDir, ?Cache $cache): Cache
     {
-        $cache = self::createCacheInstance($isDevMode, $cache);
-
-        if (! $cache instanceof CacheProvider) {
-            return $cache;
-        }
-
-        $namespace = $cache->getNamespace();
-
-        if ($namespace !== '') {
-            $namespace .= ':';
-        }
-
-        $cache->setNamespace($namespace . 'dc2_' . md5($proxyDir) . '_'); // to avoid collisions
-
-        return $cache;
+        return self::createCacheInstance($isDevMode, $cache, 'dc2_' . md5($proxyDir) . '_');
     }
 
-    private static function createCacheInstance(bool $isDevMode, ?Cache $cache): Cache
+    private static function createCacheInstance(bool $isDevMode, ?Cache $cache, string $namespace): Cache
     {
         if ($cache !== null) {
             return $cache;
@@ -187,7 +173,7 @@ class Setup
         if ($isDevMode === true) {
             $cache = class_exists(ArrayCache::class) ? new ArrayCache() : new ArrayAdapter();
         } elseif (extension_loaded('apcu')) {
-            $cache = class_exists(ApcuCache::class) ? new ApcuCache() : new ApcuAdapter();
+            $cache = class_exists(ApcuCache::class) ? new ApcuCache() : new ApcuAdapter($namespace);
         } elseif (extension_loaded('memcached')) {
             $memcached = new Memcached();
             $memcached->addServer('127.0.0.1', 11211);
@@ -196,7 +182,7 @@ class Setup
                 $cache = new MemcachedCache();
                 $cache->setMemcached($memcached);
             } else {
-                $cache = new MemcachedAdapter($memcached);
+                $cache = new MemcachedAdapter($memcached, $namespace);
             }
         } elseif (extension_loaded('redis')) {
             $redis = new Redis();
@@ -206,7 +192,7 @@ class Setup
                 $cache = new RedisCache();
                 $cache->setRedis($redis);
             } else {
-                $cache = new RedisAdapter($redis);
+                $cache = new RedisAdapter($redis, $namespace);
             }
         } else {
             $cache = class_exists(ArrayCache::class) ? new ArrayCache() : new ArrayAdapter();

@flack commented on GitHub (Jul 7, 2021): Here's a minimal diff that seems to fix the problem for me: ```diff --- a/lib/Doctrine/ORM/Tools/Setup.php +++ b/lib/Doctrine/ORM/Tools/Setup.php @@ -157,24 +157,10 @@ class Setup private static function createCacheConfiguration(bool $isDevMode, string $proxyDir, ?Cache $cache): Cache { - $cache = self::createCacheInstance($isDevMode, $cache); - - if (! $cache instanceof CacheProvider) { - return $cache; - } - - $namespace = $cache->getNamespace(); - - if ($namespace !== '') { - $namespace .= ':'; - } - - $cache->setNamespace($namespace . 'dc2_' . md5($proxyDir) . '_'); // to avoid collisions - - return $cache; + return self::createCacheInstance($isDevMode, $cache, 'dc2_' . md5($proxyDir) . '_'); } - private static function createCacheInstance(bool $isDevMode, ?Cache $cache): Cache + private static function createCacheInstance(bool $isDevMode, ?Cache $cache, string $namespace): Cache { if ($cache !== null) { return $cache; @@ -187,7 +173,7 @@ class Setup if ($isDevMode === true) { $cache = class_exists(ArrayCache::class) ? new ArrayCache() : new ArrayAdapter(); } elseif (extension_loaded('apcu')) { - $cache = class_exists(ApcuCache::class) ? new ApcuCache() : new ApcuAdapter(); + $cache = class_exists(ApcuCache::class) ? new ApcuCache() : new ApcuAdapter($namespace); } elseif (extension_loaded('memcached')) { $memcached = new Memcached(); $memcached->addServer('127.0.0.1', 11211); @@ -196,7 +182,7 @@ class Setup $cache = new MemcachedCache(); $cache->setMemcached($memcached); } else { - $cache = new MemcachedAdapter($memcached); + $cache = new MemcachedAdapter($memcached, $namespace); } } elseif (extension_loaded('redis')) { $redis = new Redis(); @@ -206,7 +192,7 @@ class Setup $cache = new RedisCache(); $cache->setRedis($redis); } else { - $cache = new RedisAdapter($redis); + $cache = new RedisAdapter($redis, $namespace); } } else { $cache = class_exists(ArrayCache::class) ? new ArrayCache() : new ArrayAdapter(); ```
Author
Owner

@stof commented on GitHub (Jul 7, 2021):

Having the namespace being set in the DoctrineProvider object is the expected behavior. This will apply the namespace to the id before asking the PSR-6 cache to get it.
The fact that the symfony/cache adapter has its own concept of namespacing is totally independent (and irrelevant in that case).

@stof commented on GitHub (Jul 7, 2021): Having the namespace being set in the DoctrineProvider object is the expected behavior. This will apply the namespace to the id before asking the PSR-6 cache to get it. The fact that the symfony/cache adapter has its own concept of namespacing is totally independent (and irrelevant in that case).
Author
Owner

@flack commented on GitHub (Jul 7, 2021):

@stof well, the namespace might be set on the DoctrineProvider, but it gets lost during $config->setMetadataCache(CacheAdapter::wrap($cache)); (because wrap returns the inner pool object, which doesn't have the namespace info). So effectively, all sites on the same machine share the same (empty) cache namespace for ClassMetadata, which wasn't the case in previous versions.

@flack commented on GitHub (Jul 7, 2021): @stof well, the namespace might be set on the DoctrineProvider, but it gets lost during `$config->setMetadataCache(CacheAdapter::wrap($cache));` (because `wrap` returns the inner pool object, which doesn't have the namespace info). So effectively, all sites on the same machine share the same (empty) cache namespace for ClassMetadata, which wasn't the case in previous versions.
Author
Owner

@stof commented on GitHub (Jul 7, 2021):

@alcaeus this might be a flaw of the PSR-6 migration layer than, that does not account for namespaces.

@stof commented on GitHub (Jul 7, 2021): @alcaeus this might be a flaw of the PSR-6 migration layer than, that does not account for namespaces.
Author
Owner

@stof commented on GitHub (Jul 7, 2021):

then, Setup::createCacheConfiguration in the ORM might want to put the namespace in the symfony/cache pool instead of the doctrine/cache object when using symfony/cache as the storage (to put the namespace on the actual storage rather than a compat adapter)

@stof commented on GitHub (Jul 7, 2021): then, `Setup::createCacheConfiguration` in the ORM might want to put the namespace in the symfony/cache pool **instead of** the doctrine/cache object when using symfony/cache as the storage (to put the namespace on the actual storage rather than a compat adapter)
Author
Owner

@flack commented on GitHub (Jul 7, 2021):

in case it helps, here's a simple reproducer script (add db config as necessary):

$config1 = \Doctrine\ORM\Tools\Setup::createConfiguration($dev_mode, $some_dir);
$em1 = \Doctrine\ORM\EntityManager::create($db_config, $config1);
$cache1 = $em1->getConfiguration()->getMetadataCache();

$config2 = \Doctrine\ORM\Tools\Setup::createConfiguration($dev_mode, $some_other_dir);
$em2 = \Doctrine\ORM\EntityManager::create($db_config, $config2);
$cache2 = $em2->getConfiguration()->getMetadataCache();

$item = $cache1->getItem('bla');
$item->set('xxx');
$cache1->save($item);

dump($cache2->hasItem('bla')); // prints true
@flack commented on GitHub (Jul 7, 2021): in case it helps, here's a simple reproducer script (add db config as necessary): ```php $config1 = \Doctrine\ORM\Tools\Setup::createConfiguration($dev_mode, $some_dir); $em1 = \Doctrine\ORM\EntityManager::create($db_config, $config1); $cache1 = $em1->getConfiguration()->getMetadataCache(); $config2 = \Doctrine\ORM\Tools\Setup::createConfiguration($dev_mode, $some_other_dir); $em2 = \Doctrine\ORM\EntityManager::create($db_config, $config2); $cache2 = $em2->getConfiguration()->getMetadataCache(); $item = $cache1->getItem('bla'); $item->set('xxx'); $cache1->save($item); dump($cache2->hasItem('bla')); // prints true ```
Author
Owner

@stof commented on GitHub (Jul 19, 2021):

@greg0ire this one is not fixed by the doctrine/cache change. The bug does not appear anymore thanks to the doctrine/cache fix, but the Setup tools should still be updated to use a symfony/cache namespacing instead of a doctrine/cache one when it instantiates a symfony cache, to avoid a hard dependency on doctrine/cache forever.

@stof commented on GitHub (Jul 19, 2021): @greg0ire this one is *not* fixed by the doctrine/cache change. The bug does not appear anymore thanks to the doctrine/cache fix, but the Setup tools should still be updated to use a symfony/cache namespacing instead of a doctrine/cache one when it instantiates a symfony cache, to avoid a hard dependency on doctrine/cache forever.
Author
Owner

@alcaeus commented on GitHub (Jul 19, 2021):

IIRC, the setup tool is considered deprecated, which is why I only "made it work". If we want to keep it around for 3.0, it makes sense to create issues for improvements so people can work on them independently.

@alcaeus commented on GitHub (Jul 19, 2021): IIRC, the setup tool is considered deprecated, which is why I only "made it work". If we want to keep it around for 3.0, it makes sense to create issues for improvements so people can work on them independently.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#6782