From d7042ab828c487420371a6ac12e32359e9e204fa Mon Sep 17 00:00:00 2001 From: comfortablynumb Date: Fri, 16 Dec 2011 20:01:10 -0300 Subject: [PATCH 1/3] Merge branch 'master', remote branch 'upstream/master' From f0db9a842d6ab6b0674f1e5cbf26e27ae166fe61 Mon Sep 17 00:00:00 2001 From: comfortablynumb Date: Fri, 16 Dec 2011 20:19:27 -0300 Subject: [PATCH 2/3] [DDC-1542] - Inheritance: Added default discriminator map (only annotations yet) --- .../ORM/Mapping/ClassMetadataFactory.php | 94 ++++++++++++++++++- .../AnotherChildClass.php | 10 ++ .../JoinedInheritanceType/ChildClass.php | 10 ++ .../JoinedInheritanceType/RootClass.php | 16 ++++ .../ORM/Mapping/ClassMetadataFactoryTest.php | 39 ++++++++ 5 files changed, 165 insertions(+), 4 deletions(-) create mode 100644 tests/Doctrine/Tests/Models/JoinedInheritanceType/AnotherChildClass.php create mode 100644 tests/Doctrine/Tests/Models/JoinedInheritanceType/ChildClass.php create mode 100644 tests/Doctrine/Tests/Models/JoinedInheritanceType/RootClass.php diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php index bf802ecf9..b327c5073 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php @@ -164,13 +164,11 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface } if ($this->cacheDriver) { - if (($cached = $this->cacheDriver->fetch("$realClassName\$CLASSMETADATA")) !== false) { + if (($cached = $this->fetchMetadataFromCache($realClassName)) !== false) { $this->loadedMetadata[$realClassName] = $cached; } else { foreach ($this->loadMetadata($realClassName) as $loadedClassName) { - $this->cacheDriver->save( - "$loadedClassName\$CLASSMETADATA", $this->loadedMetadata[$loadedClassName], null - ); + $this->cacheMetadata($loadedClassName, $this->loadedMetadata[$loadedClassName]); } } } else { @@ -319,6 +317,11 @@ 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)) { + $this->addDefaultDiscriminatorMap($class); + } + if ($this->evm->hasListeners(Events::loadClassMetadata)) { $eventArgs = new \Doctrine\ORM\Event\LoadClassMetadataEventArgs($class, $this->em); $this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs); @@ -388,6 +391,89 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface return new ClassMetadata($className); } + /** + * Adds a default discriminator map if no one is given + * + * @param \Doctrine\ORM\Mapping\ClassMetadata $class + */ + private function addDefaultDiscriminatorMap(ClassMetadata $class) + { + $allClasses = $this->driver->getAllClassNames(); + $subClassesMetadata = array(); + $fqcn = $class->getName(); + $map = array(str_replace('\\', '.', $fqcn) => $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); + } + + if (!$subClassMetadata->isMappedSuperclass) { + $map[str_replace('\\', '.', $c)] = $c; + $subClassesMetadata[$c] = $subClassMetadata; + } + } + } + + $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); + } + } + } + + /** + * Cache the metadata + * + * @param $className + * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata + */ + private function cacheMetadata($className, ClassMetadata $metadata) + { + $this->cacheDriver->save( + "$className\$CLASSMETADATA", $metadata, null + ); + } + + /** + * Verify if metadata is cached + * + * @param $className + * @return bool + */ + private function cacheContainsMetadata($className) + { + return $this->cacheDriver->contains("$className\$CLASSMETADATA"); + } + + /** + * Fetch metadata from cache + * + * @param $className + */ + private function fetchMetadataFromCache($className) + { + return $this->cacheDriver->fetch("$className\$CLASSMETADATA"); + } + /** * Adds inherited fields to the subclass mapping. * diff --git a/tests/Doctrine/Tests/Models/JoinedInheritanceType/AnotherChildClass.php b/tests/Doctrine/Tests/Models/JoinedInheritanceType/AnotherChildClass.php new file mode 100644 index 000000000..e49d89807 --- /dev/null +++ b/tests/Doctrine/Tests/Models/JoinedInheritanceType/AnotherChildClass.php @@ -0,0 +1,10 @@ +assertTrue($em->getMetadataFactory()->isTransient('CMS:CmsUser')); $this->assertFalse($em->getMetadataFactory()->isTransient('CMS:CmsArticle')); } + + public function testAddDefaultDiscriminatorMap() + { + $cmf = new ClassMetadataFactory(); + $driver = $this->createAnnotationDriver(array(__DIR__ . '/../../Models/JoinedInheritanceType/')); + $em = $this->_createEntityManager($driver); + $cmf->setEntityManager($em); + + $rootMetadata = $cmf->getMetadataFor('Doctrine\Tests\Models\JoinedInheritanceType\RootClass'); + $childMetadata = $cmf->getMetadataFor('Doctrine\Tests\Models\JoinedInheritanceType\ChildClass'); + $anotherChildMetadata = $cmf->getMetadataFor('Doctrine\Tests\Models\JoinedInheritanceType\AnotherChildClass'); + $rootDiscriminatorMap = $rootMetadata->discriminatorMap; + $childDiscriminatorMap = $childMetadata->discriminatorMap; + $anotherChildDiscriminatorMap = $anotherChildMetadata->discriminatorMap; + + $rootClass = 'Doctrine\Tests\Models\JoinedInheritanceType\RootClass'; + $childClass = 'Doctrine\Tests\Models\JoinedInheritanceType\ChildClass'; + $anotherChildClass = 'Doctrine\Tests\Models\JoinedInheritanceType\AnotherChildClass'; + + $rootClassKey = array_search($rootClass, $rootDiscriminatorMap); + $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($childDiscriminatorMap, $rootDiscriminatorMap); + $this->assertEquals($anotherChildDiscriminatorMap, $rootDiscriminatorMap); + + // ClassMetadataFactory::addDefaultDiscriminatorMap shouldn't be called again, because the + // discriminator map is already cached + $cmf = $this->getMock('Doctrine\ORM\Mapping\ClassMetadataFactory', array('addDefaultDiscriminatorMap')); + $cmf->setEntityManager($em); + $cmf->expects($this->never()) + ->method('addDefaultDiscriminatorMap'); + + $rootMetadata = $cmf->getMetadataFor('Doctrine\Tests\Models\JoinedInheritanceType\RootClass'); + } protected function _createEntityManager($metadataDriver) { From 99e303e21180814a0a319ca1b224d766a482299c Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Fri, 4 May 2012 23:15:12 +0200 Subject: [PATCH 3/3] [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);