Skip to content
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

Fix Metadata Caching when it changes in EventListeners #42

Open
wants to merge 6 commits into
base: 1.1.x
Choose a base branch
from
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[] */
private $aliasesMap = [];

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

Expand Down Expand Up @@ -156,14 +159,7 @@ public function getMetadataFor($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);
}
$realClassName = $this->getRealClassName($className);

if (isset($this->loadedMetadata[$realClassName])) {
// We do not have the alias name in the map, include it
Expand All @@ -177,32 +173,25 @@ public function getMetadataFor($className)
$cached = $this->cacheDriver->fetch($realClassName . $this->cacheSalt);
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]
);
}
$this->loadMetadata($realClassName);
}
} else {
$this->loadMetadata($realClassName);
}
} catch (MappingException $loadingException) {
$fallbackMetadataResponse = $this->onNotFoundMetadata($realClassName);
$fallbackMetadataResponse = $this->onNotFoundMetadata($className);

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

$this->loadedMetadata[$realClassName] = $fallbackMetadataResponse;
$this->setMetadataFor($className, $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, $this->loadedMetadata[$realClassName]);
}

return $this->loadedMetadata[$className];
Expand Down Expand Up @@ -233,6 +222,15 @@ public function hasMetadataFor($className)
public function setMetadataFor($className, $class)
{
$this->loadedMetadata[$className] = $class;

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

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

/**
Expand Down Expand Up @@ -302,8 +300,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 @@ -398,16 +395,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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should most likely stay private

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is protected as per suggestion of @alcaeus at #21 (comment)

{
$pos = strrpos($class, '\\' . Proxy::MARKER . '\\');
if (isset($this->aliasesMap[$className])) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Considering that duplicate entries are allowed, is this property still needed?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was done because of #21 (comment) Can you please decipher comment chain and explain if something still needs to be done?

return $this->aliasesMap[$className];
}

if ($pos === false) {
return $class;
$realClassName = $className;

if (strpos($className, ':') !== false) {
[$namespaceAlias, $simpleClassName] = explode(':', $className, 2);
$realClassName = $this->getFqcnFromAlias($namespaceAlias, $simpleClassName);
}

$pos = strrpos($className, '\\' . Proxy::MARKER . '\\');
if ($pos !== false) {
$realClassName = substr($className, $pos + Proxy::MARKER_LENGTH + 2);
}

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