diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php index 876f1ce57..efcf68bab 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php @@ -171,37 +171,43 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory } } - if ($parent && $parent->isInheritanceTypeSingleTable()) { - $class->setPrimaryTable($parent->table); - } + if ($parent) { + if ($parent->isInheritanceTypeSingleTable()) { + $class->setPrimaryTable($parent->table); + } - if ($parent && $parent->cache) { - $class->cache = $parent->cache; - } + if ($parent) { + $this->addInheritedIndexes($class, $parent); + } - if ($parent && $parent->containsForeignIdentifier) { - $class->containsForeignIdentifier = true; - } + if ($parent->cache) { + $class->cache = $parent->cache; + } - if ($parent && !empty($parent->namedQueries)) { - $this->addInheritedNamedQueries($class, $parent); - } + if ($parent->containsForeignIdentifier) { + $class->containsForeignIdentifier = true; + } - if ($parent && !empty($parent->namedNativeQueries)) { - $this->addInheritedNamedNativeQueries($class, $parent); - } + if ( ! empty($parent->namedQueries)) { + $this->addInheritedNamedQueries($class, $parent); + } - if ($parent && !empty($parent->sqlResultSetMappings)) { - $this->addInheritedSqlResultSetMappings($class, $parent); - } + if ( ! empty($parent->namedNativeQueries)) { + $this->addInheritedNamedNativeQueries($class, $parent); + } - if ($parent && !empty($parent->entityListeners) && empty($class->entityListeners)) { - $class->entityListeners = $parent->entityListeners; + if ( ! empty($parent->sqlResultSetMappings)) { + $this->addInheritedSqlResultSetMappings($class, $parent); + } + + if ( ! empty($parent->entityListeners) && empty($class->entityListeners)) { + $class->entityListeners = $parent->entityListeners; + } } $class->setParentClasses($nonSuperclassParents); - if ( $class->isRootEntity() && ! $class->isInheritanceTypeNone() && ! $class->discriminatorMap) { + if ($class->isRootEntity() && ! $class->isInheritanceTypeNone() && ! $class->discriminatorMap) { $this->addDefaultDiscriminatorMap($class); } @@ -415,6 +421,33 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory } } + /** + * Copy the table indices from the parent class superclass to the child class + * + * @param ClassMetadata $subClass + * @param ClassMetadata $parentClass + * + * @return void + */ + private function addInheritedIndexes(ClassMetadata $subClass, ClassMetadata $parentClass) + { + if (! $parentClass->isMappedSuperclass) { + return; + } + + foreach (array('uniqueConstraints', 'indexes') as $indexType) { + if (isset($parentClass->table[$indexType])) { + foreach ($parentClass->table[$indexType] as $indexName => $index) { + if (isset($subClass->table[$indexType][$indexName])) { + continue; // Let the inheriting table override indices + } + + $subClass->table[$indexType][$indexName] = $index; + } + } + } + } + /** * Adds inherited named queries to the subclass mapping. * diff --git a/tests/Doctrine/Tests/ORM/Mapping/BasicInheritanceMappingTest.php b/tests/Doctrine/Tests/ORM/Mapping/BasicInheritanceMappingTest.php index 2eee28b85..5a5f64277 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/BasicInheritanceMappingTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/BasicInheritanceMappingTest.php @@ -2,53 +2,61 @@ namespace Doctrine\Tests\ORM\Mapping; +use Doctrine\Common\Persistence\Mapping\RuntimeReflectionService; use Doctrine\ORM\Mapping\ClassMetadataFactory; -use Doctrine\ORM\Tools\SchemaTool; +use Doctrine\ORM\Mapping\ClassMetadataInfo; +use Doctrine\Tests\Models\DDC869\DDC869Payment; class BasicInheritanceMappingTest extends \Doctrine\Tests\OrmTestCase { - private $_factory; - - protected function setUp() { - $this->_factory = new ClassMetadataFactory(); - $this->_factory->setEntityManager($this->_getTestEntityManager()); - } + /** + * @var ClassMetadataFactory + */ + private $cmf; /** - * @expectedException Doctrine\ORM\Mapping\MappingException + * {@inheritDoc} */ + protected function setUp() { + $this->cmf = new ClassMetadataFactory(); + + $this->cmf->setEntityManager($this->_getTestEntityManager()); + } + public function testGetMetadataForTransientClassThrowsException() { - $this->_factory->getMetadataFor('Doctrine\Tests\ORM\Mapping\TransientBaseClass'); + $this->setExpectedException('Doctrine\ORM\Mapping\MappingException'); + + $this->cmf->getMetadataFor('Doctrine\Tests\ORM\Mapping\TransientBaseClass'); } public function testGetMetadataForSubclassWithTransientBaseClass() { - $class = $this->_factory->getMetadataFor('Doctrine\Tests\ORM\Mapping\EntitySubClass'); + $class = $this->cmf->getMetadataFor('Doctrine\Tests\ORM\Mapping\EntitySubClass'); - $this->assertTrue(empty($class->subClasses)); - $this->assertTrue(empty($class->parentClasses)); - $this->assertTrue(isset($class->fieldMappings['id'])); - $this->assertTrue(isset($class->fieldMappings['name'])); + $this->assertEmpty($class->subClasses); + $this->assertEmpty($class->parentClasses); + $this->assertArrayHasKey('id', $class->fieldMappings); + $this->assertArrayHasKey('name', $class->fieldMappings); } public function testGetMetadataForSubclassWithMappedSuperclass() { - $class = $this->_factory->getMetadataFor('Doctrine\Tests\ORM\Mapping\EntitySubClass2'); + $class = $this->cmf->getMetadataFor('Doctrine\Tests\ORM\Mapping\EntitySubClass2'); - $this->assertTrue(empty($class->subClasses)); - $this->assertTrue(empty($class->parentClasses)); + $this->assertEmpty($class->subClasses); + $this->assertEmpty($class->parentClasses); - $this->assertTrue(isset($class->fieldMappings['mapped1'])); - $this->assertTrue(isset($class->fieldMappings['mapped2'])); - $this->assertTrue(isset($class->fieldMappings['id'])); - $this->assertTrue(isset($class->fieldMappings['name'])); + $this->assertArrayHasKey('mapped1', $class->fieldMappings); + $this->assertArrayHasKey('mapped2', $class->fieldMappings); + $this->assertArrayHasKey('id', $class->fieldMappings); + $this->assertArrayHasKey('name', $class->fieldMappings); - $this->assertFalse(isset($class->fieldMappings['mapped1']['inherited'])); - $this->assertFalse(isset($class->fieldMappings['mapped2']['inherited'])); - $this->assertFalse(isset($class->fieldMappings['transient'])); + $this->assertArrayNotHasKey('inherited', $class->fieldMappings['mapped1']); + $this->assertArrayNotHasKey('inherited', $class->fieldMappings['mapped2']); + $this->assertArrayNotHasKey('transient', $class->fieldMappings); - $this->assertTrue(isset($class->associationMappings['mappedRelated1'])); + $this->assertArrayHasKey('mappedRelated1', $class->associationMappings); } /** @@ -56,28 +64,28 @@ class BasicInheritanceMappingTest extends \Doctrine\Tests\OrmTestCase */ public function testGetMetadataForSubclassWithMappedSuperclassWithRepository() { - $class = $this->_factory->getMetadataFor('Doctrine\Tests\Models\DDC869\DDC869CreditCardPayment'); + $class = $this->cmf->getMetadataFor('Doctrine\Tests\Models\DDC869\DDC869CreditCardPayment'); - $this->assertTrue(isset($class->fieldMappings['id'])); - $this->assertTrue(isset($class->fieldMappings['value'])); - $this->assertTrue(isset($class->fieldMappings['creditCardNumber'])); - $this->assertEquals($class->customRepositoryClassName, "Doctrine\Tests\Models\DDC869\DDC869PaymentRepository"); + $this->assertArrayHasKey('id', $class->fieldMappings); + $this->assertArrayHasKey('value', $class->fieldMappings); + $this->assertArrayHasKey('creditCardNumber', $class->fieldMappings); + $this->assertEquals($class->customRepositoryClassName, 'Doctrine\Tests\Models\DDC869\DDC869PaymentRepository'); - $class = $this->_factory->getMetadataFor('Doctrine\Tests\Models\DDC869\DDC869ChequePayment'); + $class = $this->cmf->getMetadataFor('Doctrine\Tests\Models\DDC869\DDC869ChequePayment'); - $this->assertTrue(isset($class->fieldMappings['id'])); - $this->assertTrue(isset($class->fieldMappings['value'])); - $this->assertTrue(isset($class->fieldMappings['serialNumber'])); - $this->assertEquals($class->customRepositoryClassName, "Doctrine\Tests\Models\DDC869\DDC869PaymentRepository"); + $this->assertArrayHasKey('id', $class->fieldMappings); + $this->assertArrayHasKey('value', $class->fieldMappings); + $this->assertArrayHasKey('serialNumber', $class->fieldMappings); + $this->assertEquals($class->customRepositoryClassName, 'Doctrine\Tests\Models\DDC869\DDC869PaymentRepository'); // override repositoryClass - $class = $this->_factory->getMetadataFor('Doctrine\Tests\ORM\Mapping\SubclassWithRepository'); + $class = $this->cmf->getMetadataFor('Doctrine\Tests\ORM\Mapping\SubclassWithRepository'); - $this->assertTrue(isset($class->fieldMappings['id'])); - $this->assertTrue(isset($class->fieldMappings['value'])); - $this->assertEquals($class->customRepositoryClassName, "Doctrine\ORM\EntityRepository"); + $this->assertArrayHasKey('id', $class->fieldMappings); + $this->assertArrayHasKey('value', $class->fieldMappings); + $this->assertEquals($class->customRepositoryClassName, 'Doctrine\ORM\EntityRepository'); } /** @@ -86,14 +94,14 @@ class BasicInheritanceMappingTest extends \Doctrine\Tests\OrmTestCase public function testSerializationWithPrivateFieldsFromMappedSuperclass() { - $class = $this->_factory->getMetadataFor(__NAMESPACE__ . '\\EntitySubClass2'); + $class = $this->cmf->getMetadataFor(__NAMESPACE__ . '\\EntitySubClass2'); $class2 = unserialize(serialize($class)); - $class2->wakeupReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService); + $class2->wakeupReflection(new RuntimeReflectionService); - $this->assertTrue(isset($class2->reflFields['mapped1'])); - $this->assertTrue(isset($class2->reflFields['mapped2'])); - $this->assertTrue(isset($class2->reflFields['mappedRelated1'])); + $this->assertArrayHasKey('mapped1', $class2->reflFields); + $this->assertArrayHasKey('mapped2', $class2->reflFields); + $this->assertArrayHasKey('mappedRelated1', $class2->reflFields); } /** @@ -101,11 +109,11 @@ class BasicInheritanceMappingTest extends \Doctrine\Tests\OrmTestCase */ public function testUnmappedSuperclassInHierarchy() { - $class = $this->_factory->getMetadataFor(__NAMESPACE__ . '\\HierarchyD'); + $class = $this->cmf->getMetadataFor(__NAMESPACE__ . '\\HierarchyD'); - $this->assertTrue(isset($class->fieldMappings['id'])); - $this->assertTrue(isset($class->fieldMappings['a'])); - $this->assertTrue(isset($class->fieldMappings['d'])); + $this->assertArrayHasKey('id', $class->fieldMappings); + $this->assertArrayHasKey('a', $class->fieldMappings); + $this->assertArrayHasKey('d', $class->fieldMappings); } /** @@ -113,9 +121,14 @@ class BasicInheritanceMappingTest extends \Doctrine\Tests\OrmTestCase */ public function testUnmappedEntityInHierarchy() { - $this->setExpectedException('Doctrine\ORM\Mapping\MappingException', "Entity 'Doctrine\Tests\ORM\Mapping\HierarchyBEntity' has to be part of the discriminator map of 'Doctrine\Tests\ORM\Mapping\HierarchyBase' to be properly mapped in the inheritance hierarchy. Alternatively you can make 'Doctrine\Tests\ORM\Mapping\HierarchyBEntity' an abstract class to avoid this exception from occurring."); + $this->setExpectedException( + 'Doctrine\ORM\Mapping\MappingException', + 'Entity \'Doctrine\Tests\ORM\Mapping\HierarchyBEntity\' has to be part of the discriminator map' + . ' of \'Doctrine\Tests\ORM\Mapping\HierarchyBase\' to be properly mapped in the inheritance hierarchy.' + . ' Alternatively you can make \'Doctrine\Tests\ORM\Mapping\HierarchyBEntity\' an abstract class to' + . ' avoid this exception from occurring.'); - $class = $this->_factory->getMetadataFor(__NAMESPACE__ . '\\HierarchyE'); + $this->cmf->getMetadataFor(__NAMESPACE__ . '\\HierarchyE'); } /** @@ -124,9 +137,9 @@ class BasicInheritanceMappingTest extends \Doctrine\Tests\OrmTestCase */ public function testMappedSuperclassWithId() { - $class = $this->_factory->getMetadataFor(__NAMESPACE__ . '\\SuperclassEntity'); + $class = $this->cmf->getMetadataFor(__NAMESPACE__ . '\\SuperclassEntity'); - $this->assertTrue(isset($class->fieldMappings['id'])); + $this->assertArrayHasKey('id', $class->fieldMappings); } /** @@ -135,11 +148,14 @@ class BasicInheritanceMappingTest extends \Doctrine\Tests\OrmTestCase */ public function testGeneratedValueFromMappedSuperclass() { - $class = $this->_factory->getMetadataFor(__NAMESPACE__ . '\\SuperclassEntity'); + $class = $this->cmf->getMetadataFor(__NAMESPACE__ . '\\SuperclassEntity'); /* @var $class ClassMetadataInfo */ $this->assertInstanceOf('Doctrine\ORM\Id\SequenceGenerator', $class->idGenerator); - $this->assertEquals(array('allocationSize' => 1, 'initialValue' => 10, 'sequenceName' => 'foo'), $class->sequenceGeneratorDefinition); + $this->assertEquals( + array('allocationSize' => 1, 'initialValue' => 10, 'sequenceName' => 'foo'), + $class->sequenceGeneratorDefinition + ); } /** @@ -148,11 +164,14 @@ class BasicInheritanceMappingTest extends \Doctrine\Tests\OrmTestCase */ public function testSequenceDefinitionInHierarchyWithSandwichMappedSuperclass() { - $class = $this->_factory->getMetadataFor(__NAMESPACE__ . '\\HierarchyD'); + $class = $this->cmf->getMetadataFor(__NAMESPACE__ . '\\HierarchyD'); /* @var $class ClassMetadataInfo */ $this->assertInstanceOf('Doctrine\ORM\Id\SequenceGenerator', $class->idGenerator); - $this->assertEquals(array('allocationSize' => 1, 'initialValue' => 10, 'sequenceName' => 'foo'), $class->sequenceGeneratorDefinition); + $this->assertEquals( + array('allocationSize' => 1, 'initialValue' => 10, 'sequenceName' => 'foo'), + $class->sequenceGeneratorDefinition + ); } /** @@ -161,11 +180,30 @@ class BasicInheritanceMappingTest extends \Doctrine\Tests\OrmTestCase */ public function testMultipleMappedSuperclasses() { - $class = $this->_factory->getMetadataFor(__NAMESPACE__ . '\\MediumSuperclassEntity'); + $class = $this->cmf->getMetadataFor(__NAMESPACE__ . '\\MediumSuperclassEntity'); /* @var $class ClassMetadataInfo */ $this->assertInstanceOf('Doctrine\ORM\Id\SequenceGenerator', $class->idGenerator); - $this->assertEquals(array('allocationSize' => 1, 'initialValue' => 10, 'sequenceName' => 'foo'), $class->sequenceGeneratorDefinition); + $this->assertEquals( + array('allocationSize' => 1, 'initialValue' => 10, 'sequenceName' => 'foo'), + $class->sequenceGeneratorDefinition + ); + } + + /** + * Ensure indexes are inherited from the mapped superclass. + * + * @group DDC-3418 + */ + public function testMappedSuperclassIndex() + { + $class = $this->cmf->getMetadataFor(__NAMESPACE__ . '\\EntityIndexSubClass'); + /* @var $class ClassMetadataInfo */ + + $this->assertArrayHasKey('mapped1', $class->fieldMappings); + $this->assertArrayHasKey('IDX_NAME_INDEX', $class->table['uniqueConstraints']); + $this->assertArrayHasKey('IDX_MAPPED1_INDEX', $class->table['uniqueConstraints']); + $this->assertArrayHasKey('IDX_MAPPED2_INDEX', $class->table['indexes']); } } @@ -206,6 +244,29 @@ class EntitySubClass2 extends MappedSuperclassBase { private $name; } +/** + * @MappedSuperclass + * @Table( + * uniqueConstraints={@UniqueConstraint(name="IDX_MAPPED1_INDEX",columns={"mapped1"})}, + * indexes={@Index(name="IDX_MAPPED2_INDEX", columns={"mapped2"})} + * ) + */ +class MappedSuperclassBaseIndex { + /** @Column(type="string") */ + private $mapped1; + /** @Column(type="string") */ + private $mapped2; +} + +/** @Entity @Table(uniqueConstraints={@UniqueConstraint(name="IDX_NAME_INDEX",columns={"name"})}) */ +class EntityIndexSubClass extends MappedSuperclassBaseIndex +{ + /** @Id @Column(type="integer") */ + private $id; + /** @Column(type="string") */ + private $name; +} + /** * @Entity * @InheritanceType("SINGLE_TABLE") @@ -226,92 +287,67 @@ abstract class HierarchyBase public $id; } -/** - * @MappedSuperclass - */ +/** @MappedSuperclass */ abstract class HierarchyASuperclass extends HierarchyBase { /** @Column(type="string") */ public $a; } -/** - * @Entity - */ +/** @Entity */ class HierarchyBEntity extends HierarchyBase { /** @Column(type="string") */ public $b; } -/** - * @Entity - */ +/** @Entity */ class HierarchyC extends HierarchyBase { /** @Column(type="string") */ public $c; } -/** - * @Entity - */ +/** @Entity */ class HierarchyD extends HierarchyASuperclass { /** @Column(type="string") */ public $d; } -/** - * @Entity - */ +/** @Entity */ class HierarchyE extends HierarchyBEntity { /** @Column(type="string") */ public $e; } -/** - * @Entity - */ +/** @Entity */ class SuperclassEntity extends SuperclassBase { - } -/** - * @MappedSuperclass - */ +/** @MappedSuperclass */ abstract class SuperclassBase { /** * @Column(type="integer") @Id @GeneratedValue(strategy="SEQUENCE") * @SequenceGenerator(sequenceName="foo", initialValue=10) - * @var int */ public $id; } -/** - * @MappedSuperclass - */ +/** @MappedSuperclass */ abstract class MediumSuperclassBase extends SuperclassBase { - } -/** - * @Entity - */ +/** @Entity */ class MediumSuperclassEntity extends MediumSuperclassBase { - } -/** - * @Entity(repositoryClass = "Doctrine\ORM\EntityRepository") - */ -class SubclassWithRepository extends \Doctrine\Tests\Models\DDC869\DDC869Payment +/** @Entity(repositoryClass = "Doctrine\ORM\EntityRepository") */ +class SubclassWithRepository extends DDC869Payment { - }