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 #21

Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -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,32 @@ 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.
*
* @param string $className
*
* @return string
*/
private function getRealClass(string $class) : string
private function getRealClassName($className)
Copy link
Member

Choose a reason for hiding this comment

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

Type hints are here to stay: there's no reason you should assume anything else than a string for $className.

Copy link
Member

Choose a reason for hiding this comment

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

We may also profit from making this method protected: class name resolution for proxy class names is currently more difficult in ODM 2.0 and ORM 3.0 (master) since moving away from the legacy proxies. Overriding this method would at least temporarily solve this problem. Opinions @lcobucci @Majkl578?

{
$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