Skip to content

Commit

Permalink
Fix Metadata Caching when it changes in EventListeners
Browse files Browse the repository at this point in the history
  • Loading branch information
dron-alterpost committed Feb 18, 2019
1 parent 7e645d0 commit af490ed
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ abstract class AbstractClassMetadataFactory implements ClassMetadataFactory
/** @var ClassMetadata[] */
private $loadedMetadata = [];

/** @var string[] */
protected $aliasesMap = [];

/** @var bool */
protected $initialized = false;

Expand Down Expand Up @@ -152,58 +155,35 @@ abstract protected function isEntity(ClassMetadata $class);
*/
public function getMetadataFor($className)
{
$className = $this->getRealClassName($className);

if (isset($this->loadedMetadata[$className])) {
return $this->loadedMetadata[$className];
}

// Check for namespace alias
if (strpos($className, ':') !== false) {
[$namespaceAlias, $simpleClassName] = explode(':', $className, 2);

$realClassName = $this->getFqcnFromAlias($namespaceAlias, $simpleClassName);
} else {
$realClassName = $this->getRealClass($className);
}

if (isset($this->loadedMetadata[$realClassName])) {
// We do not have the alias name in the map, include it
return $this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName];
}

$loadingException = null;

try {
if ($this->cacheDriver) {
$cached = $this->cacheDriver->fetch($realClassName . $this->cacheSalt);
$cached = $this->cacheDriver->fetch($className . $this->cacheSalt);
if ($cached instanceof ClassMetadata) {
$this->loadedMetadata[$realClassName] = $cached;
$this->loadedMetadata[$className] = $cached;

$this->wakeupReflection($cached, $this->getReflectionService());
} else {
foreach ($this->loadMetadata($realClassName) as $loadedClassName) {
$this->cacheDriver->save(
$loadedClassName . $this->cacheSalt,
$this->loadedMetadata[$loadedClassName],
null
);
}
$this->loadMetadata($className);
}
} else {
$this->loadMetadata($realClassName);
$this->loadMetadata($className);
}
} catch (MappingException $loadingException) {
$fallbackMetadataResponse = $this->onNotFoundMetadata($realClassName);
$fallbackMetadataResponse = $this->onNotFoundMetadata($className);

if (! $fallbackMetadataResponse) {
throw $loadingException;
}

$this->loadedMetadata[$realClassName] = $fallbackMetadataResponse;
}

if ($className !== $realClassName) {
// We do not have the alias name in the map, include it
$this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName];
$this->setMetadataFor($className, $fallbackMetadataResponse);
}

return $this->loadedMetadata[$className];
Expand All @@ -218,6 +198,8 @@ public function getMetadataFor($className)
*/
public function hasMetadataFor($className)
{
$className = $this->getRealClassName($className);

return isset($this->loadedMetadata[$className]);
}

Expand All @@ -233,7 +215,18 @@ public function hasMetadataFor($className)
*/
public function setMetadataFor($className, $class)
{
$className = $this->getRealClassName($className);
$this->loadedMetadata[$className] = $class;

if ($this->cacheDriver === null) {
return;
}

$this->cacheDriver->save(
$className . $this->cacheSalt,
$this->loadedMetadata[$className],
null
);
}

/**
Expand Down Expand Up @@ -303,8 +296,7 @@ protected function loadMetadata($name)
$this->initializeReflection($class, $reflService);

$this->doLoadMetadata($class, $parent, $rootEntityFound, $visited);

$this->loadedMetadata[$className] = $class;
$this->setMetadataFor($className, $class);

$parent = $class;

Expand Down Expand Up @@ -399,16 +391,28 @@ public function getReflectionService()
}

/**
* Gets the real class name of a class name that could be a proxy.
* Gets the real class name of a class name that could be a proxy or alias.
*/
private function getRealClass(string $class) : string
protected function getRealClassName(string $className) : string
{
$pos = strrpos($class, '\\' . Proxy::MARKER . '\\');
if (isset($this->aliasesMap[$className])) {
return $this->aliasesMap[$className];
}

if ($pos === false) {
return $class;
switch (true) {
case strpos($className, ':') !== false: // Check for namespace alias
[$namespaceAlias, $simpleClassName] = explode(':', $className, 2);
$realClassName = $this->getFqcnFromAlias($namespaceAlias, $simpleClassName);
break;
case $pos = strrpos($className, '\\' . Proxy::MARKER . '\\') !== false: // Check for namespace proxy
$realClassName = substr($className, $pos + Proxy::MARKER_LENGTH + 2);
break;
default:
$realClassName = $className;
}

return substr($class, $pos + Proxy::MARKER_LENGTH + 2);
$this->aliasesMap[$className] = $realClassName;

return $realClassName;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
use Doctrine\Common\Persistence\Mapping\MappingException;
use Doctrine\Common\Persistence\Mapping\ReflectionService;
use Doctrine\Tests\DoctrineTestCase;
use PHPUnit_Framework_MockObject_MockObject;
use PHPUnit\Framework\MockObject\MockObject;
use stdClass;

/**
Expand Down Expand Up @@ -140,7 +140,7 @@ public function testWillFailOnFallbackFailureWithNotLoadedMetadata()
*/
public function testWillIgnoreCacheEntriesThatAreNotMetadataInstances()
{
/** @var Cache|PHPUnit_Framework_MockObject_MockObject $cacheDriver */
/** @var Cache|MockObject $cacheDriver */
$cacheDriver = $this->createMock(Cache::class);

$this->cmf->setCacheDriver($cacheDriver);
Expand All @@ -150,7 +150,7 @@ public function testWillIgnoreCacheEntriesThatAreNotMetadataInstances()
/** @var ClassMetadata $metadata */
$metadata = $this->createMock(ClassMetadata::class);

/** @var PHPUnit_Framework_MockObject_MockObject|stdClass|callable $fallbackCallback */
/** @var MockObject|stdClass|callable $fallbackCallback */
$fallbackCallback = $this->getMockBuilder(stdClass::class)->setMethods(['__invoke'])->getMock();

$fallbackCallback->expects(self::any())->method('__invoke')->willReturn($metadata);
Expand All @@ -159,6 +159,35 @@ public function testWillIgnoreCacheEntriesThatAreNotMetadataInstances()

self::assertSame($metadata, $this->cmf->getMetadataFor('Foo'));
}

public function testFallbackMetadataShouldBeCached() : void
{
/** @var Cache|MockObject $cacheDriver */
$cacheDriver = $this->createMock(Cache::class);
$cacheDriver->expects(self::once())->method('save');

$this->cmf->setCacheDriver($cacheDriver);

$classMetadata = $this->createMock(ClassMetadata::class);

$this->cmf->fallbackCallback = static function () use ($classMetadata) {
return $classMetadata;
};

$this->cmf->getMetadataFor('Foo');
}

public function testSetMetadataForShouldUpdateCache() : void
{
/** @var Cache|MockObject $cacheDriver */
$cacheDriver = $this->createMock(Cache::class);
$cacheDriver->expects(self::once())->method('save');

$this->cmf->setCacheDriver($cacheDriver);

$classMetadata = $this->createMock(ClassMetadata::class);
$this->cmf->setMetadataFor('Foo', $classMetadata);
}
}

class TestClassMetadataFactory extends AbstractClassMetadataFactory
Expand Down

0 comments on commit af490ed

Please sign in to comment.