-
-
Notifications
You must be signed in to change notification settings - Fork 66
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Deprecate using doctrine/cache in favour of PSR-6 #144
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
UPGRADE FROM 2.1 to 2.2 | ||
======================= | ||
|
||
* Deprecated using doctrine/cache for metadata caching. The `setCacheDriver` and | ||
`getCacheDriver` methods in `Doctrine\Persistence\Mapping\AbstractMetadata` | ||
have been deprecated. Please use `getCache` and `setCache` with a PSR-6 | ||
implementation instead. Note that even after switching to PSR-6, | ||
`getCacheDriver` will return a cache instance that wraps the PSR-6 cache. | ||
Note that if you use a custom implementation of doctrine/cache, the library | ||
may not be able to provide a forward compatibility layer. The cache | ||
implementation MUST extend the `Doctrine\Common\Cache\CacheProvider` class. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,17 +2,30 @@ | |
|
||
namespace Doctrine\Persistence\Mapping; | ||
|
||
use BadMethodCallException; | ||
use Doctrine\Common\Cache\Cache; | ||
use Doctrine\Common\Cache\CacheProvider; | ||
use Doctrine\Persistence\Mapping\Driver\MappingDriver; | ||
use Doctrine\Persistence\Proxy; | ||
use Psr\Cache\CacheItemPoolInterface; | ||
use ReflectionException; | ||
use Symfony\Component\Cache\Adapter\DoctrineAdapter; | ||
use Symfony\Component\Cache\DoctrineProvider; | ||
|
||
use function array_combine; | ||
use function array_keys; | ||
use function array_map; | ||
use function array_reverse; | ||
use function array_unshift; | ||
use function explode; | ||
use function sprintf; | ||
use function str_replace; | ||
use function strpos; | ||
use function strrpos; | ||
use function substr; | ||
use function trigger_error; | ||
|
||
use const E_USER_DEPRECATED; | ||
|
||
/** | ||
* The ClassMetadataFactory is used to create ClassMetadata objects that contain all the | ||
|
@@ -28,11 +41,14 @@ abstract class AbstractClassMetadataFactory implements ClassMetadataFactory | |
* | ||
* @var string | ||
*/ | ||
protected $cacheSalt = '$CLASSMETADATA'; | ||
protected $cacheSalt = '__CLASSMETADATA__'; | ||
|
||
/** @var Cache|null */ | ||
private $cacheDriver; | ||
|
||
/** @var CacheItemPoolInterface|null */ | ||
private $cache; | ||
|
||
/** @var ClassMetadata[] */ | ||
private $loadedMetadata = []; | ||
|
||
|
@@ -45,23 +61,54 @@ abstract class AbstractClassMetadataFactory implements ClassMetadataFactory | |
/** | ||
* Sets the cache driver used by the factory to cache ClassMetadata instances. | ||
* | ||
* @deprecated setCacheDriver was deprecated in doctrine/persistence 2.2 and will be removed in 3.0. Use setCache instead | ||
* | ||
* @return void | ||
*/ | ||
public function setCacheDriver(?Cache $cacheDriver = null) | ||
{ | ||
@trigger_error(sprintf('%s is deprecated. Use setCache() with a PSR-6 cache instead.', __METHOD__), E_USER_DEPRECATED); | ||
|
||
$this->cacheDriver = $cacheDriver; | ||
|
||
if ($cacheDriver === null) { | ||
$this->cache = null; | ||
|
||
return; | ||
} | ||
|
||
if (! $cacheDriver instanceof CacheProvider) { | ||
throw new BadMethodCallException('Cannot convert cache to PSR-6 cache'); | ||
alcaeus marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
$this->cache = new DoctrineAdapter($cacheDriver); | ||
} | ||
|
||
/** | ||
* Gets the cache driver used by the factory to cache ClassMetadata instances. | ||
* | ||
* @deprecated getCacheDriver was deprecated in doctrine/persistence 2.2 and will be removed in 3.0. Use getCache instead | ||
* | ||
* @return Cache|null | ||
*/ | ||
public function getCacheDriver() | ||
{ | ||
@trigger_error(sprintf('%s is deprecated. Use getCache() instead.', __METHOD__), E_USER_DEPRECATED); | ||
|
||
return $this->cacheDriver; | ||
} | ||
|
||
public function setCache(CacheItemPoolInterface $cache): void | ||
{ | ||
$this->cache = $cache; | ||
$this->cacheDriver = new DoctrineProvider($cache); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this should use the doctrine/cache PSR-6 provider instead, which is available in the latest version There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
} | ||
|
||
public function getCache(): ?CacheItemPoolInterface | ||
{ | ||
return $this->cache; | ||
} | ||
|
||
/** | ||
* Returns an array of all the loaded metadata currently in memory. | ||
* | ||
|
@@ -174,19 +221,29 @@ public function getMetadataFor($className) | |
$loadingException = null; | ||
|
||
try { | ||
if ($this->cacheDriver) { | ||
$cached = $this->cacheDriver->fetch($realClassName . $this->cacheSalt); | ||
if ($this->cache) { | ||
$cached = $this->cache->getItem($this->getCacheKey($realClassName))->get(); | ||
if ($cached instanceof ClassMetadata) { | ||
$this->loadedMetadata[$realClassName] = $cached; | ||
|
||
$this->wakeupReflection($cached, $this->getReflectionService()); | ||
} else { | ||
foreach ($this->loadMetadata($realClassName) as $loadedClassName) { | ||
$this->cacheDriver->save( | ||
$loadedClassName . $this->cacheSalt, | ||
$this->loadedMetadata[$loadedClassName] | ||
); | ||
$loadedMetadata = $this->loadMetadata($realClassName); | ||
$classNames = array_combine( | ||
array_map([$this, 'getCacheKey'], $loadedMetadata), | ||
$loadedMetadata | ||
); | ||
|
||
foreach ($this->cache->getItems(array_keys($classNames)) as $item) { | ||
if (! isset($classNames[$item->getKey()])) { | ||
alcaeus marked this conversation as resolved.
Show resolved
Hide resolved
|
||
continue; | ||
} | ||
|
||
$item->set($this->loadedMetadata[$classNames[$item->getKey()]]); | ||
$this->cache->saveDeferred($item); | ||
} | ||
|
||
$this->cache->commit(); | ||
} | ||
} else { | ||
$this->loadMetadata($realClassName); | ||
|
@@ -400,6 +457,11 @@ public function getReflectionService() | |
return $this->reflectionService; | ||
} | ||
|
||
protected function getCacheKey(string $realClassName): string | ||
{ | ||
return str_replace('\\', '__', $realClassName) . $this->cacheSalt; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This (along with the changed cache salt) will invalidate existing caches. We'll have to check if existing implementations manually mess with cache keys, in which case we'll have to come back and check the impact of this change. |
||
} | ||
|
||
/** | ||
* Gets the real class name of a class name that could be a proxy. | ||
*/ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,14 +3,16 @@ | |
namespace Doctrine\Tests\Persistence\Mapping; | ||
|
||
use Doctrine\Common\Cache\ArrayCache; | ||
use Doctrine\Common\Cache\Cache; | ||
use Doctrine\Persistence\Mapping\ClassMetadata; | ||
use Doctrine\Persistence\Mapping\Driver\MappingDriver; | ||
use Doctrine\Persistence\Mapping\MappingException; | ||
use Doctrine\Tests\DoctrineTestCase; | ||
use Psr\Cache\CacheItemInterface; | ||
use Psr\Cache\CacheItemPoolInterface; | ||
use stdClass; | ||
|
||
use function assert; | ||
use Symfony\Component\Cache\Adapter\ArrayAdapter; | ||
use Symfony\Component\Cache\Adapter\DoctrineAdapter; | ||
use Symfony\Component\Cache\DoctrineProvider; | ||
|
||
/** | ||
* @covers \Doctrine\Persistence\Mapping\AbstractClassMetadataFactory | ||
|
@@ -27,12 +29,31 @@ protected function setUp(): void | |
$this->cmf = new TestClassMetadataFactory($driver, $metadata); | ||
} | ||
|
||
public function testGetCacheDriver(): void | ||
public function testSetGetCacheDriver(): void | ||
{ | ||
self::assertNull($this->cmf->getCacheDriver()); | ||
self::assertNull($this->cmf->getCache()); | ||
|
||
$cache = new ArrayCache(); | ||
$this->cmf->setCacheDriver($cache); | ||
|
||
self::assertSame($cache, $this->cmf->getCacheDriver()); | ||
self::assertInstanceOf(DoctrineAdapter::class, $this->cmf->getCache()); | ||
|
||
$this->cmf->setCacheDriver(null); | ||
self::assertNull($this->cmf->getCacheDriver()); | ||
self::assertNull($this->cmf->getCache()); | ||
} | ||
|
||
public function testSetGetCache(): void | ||
{ | ||
self::assertNull($this->cmf->getCache()); | ||
self::assertNull($this->cmf->getCacheDriver()); | ||
|
||
$cache = new ArrayAdapter(); | ||
$this->cmf->setCache($cache); | ||
self::assertSame($cache, $this->cmf->getCache()); | ||
self::assertInstanceOf(DoctrineProvider::class, $this->cmf->getCacheDriver()); | ||
} | ||
|
||
public function testGetMetadataFor(): void | ||
|
@@ -61,22 +82,26 @@ public function testGetParentMetadata(): void | |
public function testGetCachedMetadata(): void | ||
{ | ||
$metadata = $this->createMock(ClassMetadata::class); | ||
$cache = new ArrayCache(); | ||
$cache->save(ChildEntity::class . '$CLASSMETADATA', $metadata); | ||
$cache = new ArrayAdapter(); | ||
$item = $cache->getItem($this->cmf->getCacheKey(ChildEntity::class)); | ||
$item->set($metadata); | ||
$cache->save($item); | ||
|
||
$this->cmf->setCacheDriver($cache); | ||
$this->cmf->setCache($cache); | ||
|
||
self::assertSame($metadata, $this->cmf->getMetadataFor(ChildEntity::class)); | ||
self::assertEquals($metadata, $this->cmf->getMetadataFor(ChildEntity::class)); | ||
} | ||
|
||
public function testCacheGetMetadataFor(): void | ||
{ | ||
$cache = new ArrayCache(); | ||
$this->cmf->setCacheDriver($cache); | ||
$cache = new ArrayAdapter(); | ||
$this->cmf->setCache($cache); | ||
|
||
$loadedMetadata = $this->cmf->getMetadataFor(ChildEntity::class); | ||
|
||
self::assertSame($loadedMetadata, $cache->fetch(ChildEntity::class . '$CLASSMETADATA')); | ||
$item = $cache->getItem($this->cmf->getCacheKey(ChildEntity::class)); | ||
self::assertTrue($item->isHit()); | ||
self::assertEquals($loadedMetadata, $item->get()); | ||
} | ||
|
||
public function testGetAliasedMetadata(): void | ||
|
@@ -139,18 +164,81 @@ public function testWillFailOnFallbackFailureWithNotLoadedMetadata(): void | |
*/ | ||
public function testWillIgnoreCacheEntriesThatAreNotMetadataInstances(): void | ||
{ | ||
$cacheDriver = $this->createMock(Cache::class); | ||
|
||
$this->cmf->setCacheDriver($cacheDriver); | ||
|
||
$cacheDriver->expects(self::once())->method('fetch')->with('Foo$CLASSMETADATA')->willReturn(new stdClass()); | ||
$key = $this->cmf->getCacheKey(RootEntity::class); | ||
|
||
$metadata = $this->cmf->metadata; | ||
|
||
$item = $this->createMock(CacheItemInterface::class); | ||
|
||
$item | ||
->method('getKey') | ||
->willReturn($key); | ||
$item | ||
->method('get') | ||
->willReturn(new stdClass()); | ||
$item | ||
->expects(self::once()) | ||
->method('set') | ||
->with($metadata); | ||
|
||
$cacheDriver = $this->createMock(CacheItemPoolInterface::class); | ||
$cacheDriver | ||
->method('getItem') | ||
->with($key) | ||
->willReturn($item); | ||
$cacheDriver | ||
->expects(self::once()) | ||
->method('getItems') | ||
->with([$key]) | ||
->willReturn([$item]); | ||
$cacheDriver | ||
->expects(self::once()) | ||
->method('saveDeferred') | ||
->with($item); | ||
$cacheDriver | ||
->expects(self::once()) | ||
->method('commit'); | ||
|
||
$this->cmf->setCache($cacheDriver); | ||
|
||
self::assertSame($metadata, $this->cmf->getMetadataFor(RootEntity::class)); | ||
} | ||
|
||
$metadata = $this->createMock(ClassMetadata::class); | ||
assert($metadata instanceof ClassMetadata); | ||
public function testWillNotCacheFallbackMetadata(): void | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I duplicated that test, as I realised that using the |
||
{ | ||
$key = $this->cmf->getCacheKey('Foo'); | ||
|
||
$metadata = $this->cmf->metadata; | ||
|
||
$item = $this->createMock(CacheItemInterface::class); | ||
|
||
$item | ||
->method('get') | ||
->willReturn(null); | ||
$item | ||
->expects(self::never()) | ||
->method('set'); | ||
|
||
$cacheDriver = $this->createMock(CacheItemPoolInterface::class); | ||
$cacheDriver | ||
->expects(self::once()) | ||
->method('getItem') | ||
->with($key) | ||
->willReturn($item); | ||
$cacheDriver | ||
->expects(self::never()) | ||
->method('saveDeferred'); | ||
$cacheDriver | ||
->expects(self::never()) | ||
->method('commit'); | ||
|
||
$this->cmf->setCache($cacheDriver); | ||
|
||
$fallbackCallback = $this->getMockBuilder(stdClass::class)->setMethods(['__invoke'])->getMock(); | ||
|
||
$fallbackCallback->expects(self::any())->method('__invoke')->willReturn($metadata); | ||
$fallbackCallback | ||
->method('__invoke') | ||
->willReturn($metadata); | ||
|
||
$this->cmf->fallbackCallback = $fallbackCallback; | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is used to provide the forward/compatibility layer. Once we drop that in 3.0, this becomes a dev dependency.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
symfony/cache
has a dev-dependency ondoctrine/cache
, so this means a circular dependency (not for us, but for them when they are in dev mode). I think this might be OK though since it's not meant to last, and since symfony seems to be resorting toCOMPOSER_ROOT_VERSION
in its CI already: https://github.com/symfony/symfony/search?q=COMPOSER_ROOT_VERSIONStill, we might want to check if we don't break their pipeline after releasing this (or merging it? I think they have builds that use untagged versions).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How is there a circular dependency? This is doctrine/persistence, not doctrine/cache.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
symfony/symfony
depends ondoctrine/persistence
and replacessymfony/cache
. Anyway, we should be good here. 😃There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, nevermind my comment, not sure what I meant but I must have been confused.