From 99e303e21180814a0a319ca1b224d766a482299c Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Fri, 4 May 2012 23:15:12 +0200 Subject: [PATCH] [DDC-1542] Refactored automatic discriminator map detection. --- .../ORM/Mapping/ClassMetadataFactory.php | 65 ++++++++++--------- .../ORM/Mapping/ClassMetadataInfo.php | 10 +++ lib/Doctrine/ORM/Mapping/MappingException.php | 11 ++++ .../ORM/Mapping/ClassMetadataFactoryTest.php | 8 +-- 4 files changed, 60 insertions(+), 34 deletions(-) diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php index b327c5073..d0c790b62 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php @@ -317,8 +317,7 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface $class->setParentClasses($visited); - // Calculate Discriminator Map if needed and if no discriminator map is set - if ($class->isInheritanceTypeJoined() && empty($class->discriminatorMap)) { + if ( $class->isRootEntity() && ! $class->isInheritanceTypeNone() && ! $class->discriminatorMap) { $this->addDefaultDiscriminatorMap($class); } @@ -394,6 +393,13 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface /** * Adds a default discriminator map if no one is given * + * If an entity is of any inheritance type and does not contain a + * discrimiator map, then the map is generated automatically. This process + * is expensive computation wise. + * + * The automatically generated discriminator map contains the lowercase shortname of + * each class as key. + * * @param \Doctrine\ORM\Mapping\ClassMetadata $class */ private function addDefaultDiscriminatorMap(ClassMetadata $class) @@ -401,43 +407,42 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface $allClasses = $this->driver->getAllClassNames(); $subClassesMetadata = array(); $fqcn = $class->getName(); - $map = array(str_replace('\\', '.', $fqcn) => $fqcn); + $map = array($this->getShortName($class->name) => $fqcn); - foreach ($allClasses as $c) { - if (is_subclass_of($c, $fqcn)) { - if (isset($this->loadedMetadata[$c])) { - $subClassMetadata = $this->loadedMetadata[$c]; - } else { - $subClassMetadata = $this->newClassMetadataInstance($c); - $this->driver->loadMetadataForClass($c, $subClassMetadata); + $duplicates = array(); + foreach ($allClasses as $subClassCandidate) { + if (is_subclass_of($subClassCandidate, $fqcn)) { + $shortName = $this->getShortName($subClassCandidate); + + if (isset($map[$shortName])) { + $duplicates[] = $shortName; } - if (!$subClassMetadata->isMappedSuperclass) { - $map[str_replace('\\', '.', $c)] = $c; - $subClassesMetadata[$c] = $subClassMetadata; - } + $map[$shortName] = $subClassCandidate; } } + if ($duplicates) { + throw MappingException::duplicateDiscriminatorEntry($class->name, $duplicates, $map); + } + $class->setDiscriminatorMap($map); + } - // Now we set the discriminator map for the subclasses already loaded - foreach ($subClassesMetadata as $subClassFqcn => $subClassMetadata) { - $subClassMetadata->setDiscriminatorMap($map); - - // We need to overwrite the cached version of the metadata, because - // it was cached without the discriminator map - if ($this->cacheDriver && $this->cacheContainsMetadata($subClassFqcn)) { - // If subclass metadata is not already loaded, it's incomplete so - // we reload it again from cache - if (!isset($this->loadedMetadata[$subClassFqcn])) { - $subClassMetadata = $this->fetchMetadataFromCache($subClassFqcn); - $subClassMetadata->setDiscriminatorMap($map); - } - - $this->cacheMetadata($subClassFqcn, $subClassMetadata); - } + /** + * Get the lower-case shortname of a class. + * + * @param string $className + * @return string + */ + private function getShortName($className) + { + if (strpos($className, "\\") === false) { + return strtolower($className); } + + $parts = explode("\\", $className); + return strtolower(end($parts)); } /** diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 6fbf8b3c6..934464252 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -1340,6 +1340,16 @@ class ClassMetadataInfo return isset($this->fieldMappings[$fieldName]['inherited']); } + /** + * Check if this entity is the root in any entity-inheritance-hierachy. + * + * @return bool + */ + public function isRootEntity() + { + return $this->name == $this->rootEntityName; + } + /** * Checks whether a mapped association field is inherited from a superclass. * diff --git a/lib/Doctrine/ORM/Mapping/MappingException.php b/lib/Doctrine/ORM/Mapping/MappingException.php index c71c2e91c..9b64bced6 100644 --- a/lib/Doctrine/ORM/Mapping/MappingException.php +++ b/lib/Doctrine/ORM/Mapping/MappingException.php @@ -211,6 +211,17 @@ class MappingException extends \Doctrine\ORM\ORMException ); } + public static function duplicateDiscriminatorEntry($className, array $entries, array $map) + { + return new self( + "The entries " . implode(', ', $entries) . " in discriminator map of class '" . $className . "' is duplicated. " . + "If the discriminator map is automatically generated you have to convert it to an explicit discriminator map now. " . + "The entries of the current map are: @DiscriminatorMap({" . implode(', ', array_map( + function($a, $b) { return "'$a': '$b'"; }, array_keys($map), array_values($map) + )) . "})" + ); + } + public static function missingDiscriminatorMap($className) { return new self("Entity class '$className' is using inheritance but no discriminator map was defined."); diff --git a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataFactoryTest.php b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataFactoryTest.php index 6b5c80b70..bc0cdabca 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataFactoryTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataFactoryTest.php @@ -127,7 +127,7 @@ class ClassMetadataFactoryTest extends \Doctrine\Tests\OrmTestCase $this->assertTrue($em->getMetadataFactory()->isTransient('CMS:CmsUser')); $this->assertFalse($em->getMetadataFactory()->isTransient('CMS:CmsArticle')); } - + public function testAddDefaultDiscriminatorMap() { $cmf = new ClassMetadataFactory(); @@ -150,9 +150,9 @@ class ClassMetadataFactoryTest extends \Doctrine\Tests\OrmTestCase $childClassKey = array_search($childClass, $rootDiscriminatorMap); $anotherChildClassKey = array_search($anotherChildClass, $rootDiscriminatorMap); - $this->assertEquals(str_replace('\\', '.', $rootClass), $rootClassKey); - $this->assertFalse($childClassKey); - $this->assertEquals(str_replace('\\', '.', $anotherChildClassKey), $anotherChildClassKey); + $this->assertEquals('rootclass', $rootClassKey); + $this->assertEquals('childclass', $childClassKey); + $this->assertEquals('anotherchildclass', $anotherChildClassKey); $this->assertEquals($childDiscriminatorMap, $rootDiscriminatorMap); $this->assertEquals($anotherChildDiscriminatorMap, $rootDiscriminatorMap);