diff --git a/lib/Doctrine/ORM/Tools/SchemaTool.php b/lib/Doctrine/ORM/Tools/SchemaTool.php index 23a361e9774..f79cc94085f 100644 --- a/lib/Doctrine/ORM/Tools/SchemaTool.php +++ b/lib/Doctrine/ORM/Tools/SchemaTool.php @@ -40,6 +40,7 @@ * @author Jonathan Wage * @author Roman Borschel * @author Benjamin Eberlei + * @author Stefano Rodriguez */ class SchemaTool { @@ -140,6 +141,9 @@ public function getSchemaFromMetadata(array $classes) $metadataSchemaConfig->setExplicitForeignKeyIndexes(false); $schema = new Schema(array(), array(), $metadataSchemaConfig); + $addedFks = array(); + $blacklistedFks = array(); + foreach ($classes as $class) { if ($this->processingNotRequired($class, $processedClasses)) { continue; @@ -150,7 +154,7 @@ public function getSchemaFromMetadata(array $classes) if ($class->isInheritanceTypeSingleTable()) { $columns = $this->_gatherColumns($class, $table); - $this->_gatherRelationsSql($class, $table, $schema); + $this->_gatherRelationsSql($class, $table, $schema, $addedFks, $blacklistedFks); // Add the discriminator column $this->addDiscriminatorColumnDefinition($class, $table); @@ -164,7 +168,7 @@ public function getSchemaFromMetadata(array $classes) foreach ($class->subClasses as $subClassName) { $subClass = $this->em->getClassMetadata($subClassName); $this->_gatherColumns($subClass, $table); - $this->_gatherRelationsSql($subClass, $table, $schema); + $this->_gatherRelationsSql($subClass, $table, $schema, $addedFks, $blacklistedFks); $processedClasses[$subClassName] = true; } } else if ($class->isInheritanceTypeJoined()) { @@ -181,7 +185,7 @@ public function getSchemaFromMetadata(array $classes) } } - $this->_gatherRelationsSql($class, $table, $schema); + $this->_gatherRelationsSql($class, $table, $schema, $addedFks, $blacklistedFks); // Add the discriminator column only to the root table if ($class->name == $class->rootEntityName) { @@ -210,7 +214,7 @@ public function getSchemaFromMetadata(array $classes) throw ORMException::notSupported(); } else { $this->_gatherColumns($class, $table); - $this->_gatherRelationsSql($class, $table, $schema); + $this->_gatherRelationsSql($class, $table, $schema, $addedFks, $blacklistedFks); } $pkColumns = array(); @@ -415,9 +419,11 @@ private function _gatherColumn($class, array $mapping, $table) * @param ClassMetadata $class * @param \Doctrine\DBAL\Schema\Table $table * @param \Doctrine\DBAL\Schema\Schema $schema + * @param array $addedFks + * @param array $blacklistedFks * @return void */ - private function _gatherRelationsSql($class, $table, $schema) + private function _gatherRelationsSql($class, $table, $schema, &$addedFks, &$blacklistedFks) { foreach ($class->associationMappings as $fieldName => $mapping) { if (isset($mapping['inherited'])) { @@ -429,7 +435,7 @@ private function _gatherRelationsSql($class, $table, $schema) if ($mapping['type'] & ClassMetadata::TO_ONE && $mapping['isOwningSide']) { $primaryKeyColumns = $uniqueConstraints = array(); // PK is unnecessary for this relation-type - $this->_gatherRelationJoinColumns($mapping['joinColumns'], $table, $foreignClass, $mapping, $primaryKeyColumns, $uniqueConstraints); + $this->_gatherRelationJoinColumns($mapping['joinColumns'], $table, $foreignClass, $mapping, $primaryKeyColumns, $uniqueConstraints, $addedFks, $blacklistedFks); foreach($uniqueConstraints as $indexName => $unique) { $table->addUniqueIndex($unique['columns'], is_numeric($indexName) ? null : $indexName); @@ -446,10 +452,10 @@ private function _gatherRelationsSql($class, $table, $schema) $primaryKeyColumns = $uniqueConstraints = array(); // Build first FK constraint (relation table => source table) - $this->_gatherRelationJoinColumns($joinTable['joinColumns'], $theJoinTable, $class, $mapping, $primaryKeyColumns, $uniqueConstraints); + $this->_gatherRelationJoinColumns($joinTable['joinColumns'], $theJoinTable, $class, $mapping, $primaryKeyColumns, $uniqueConstraints, $addedFks, $blacklistedFks); // Build second FK constraint (relation table => target table) - $this->_gatherRelationJoinColumns($joinTable['inverseJoinColumns'], $theJoinTable, $foreignClass, $mapping, $primaryKeyColumns, $uniqueConstraints); + $this->_gatherRelationJoinColumns($joinTable['inverseJoinColumns'], $theJoinTable, $foreignClass, $mapping, $primaryKeyColumns, $uniqueConstraints, $addedFks, $blacklistedFks); $theJoinTable->setPrimaryKey($primaryKeyColumns); @@ -503,8 +509,10 @@ private function getDefiningClass($class, $referencedColumnName) * @param array $mapping * @param array $primaryKeyColumns * @param array $uniqueConstraints + * @param array $addedFks + * @param array $blacklistedFks */ - private function _gatherRelationJoinColumns($joinColumns, $theJoinTable, $class, $mapping, &$primaryKeyColumns, &$uniqueConstraints) + private function _gatherRelationJoinColumns($joinColumns, $theJoinTable, $class, $mapping, &$primaryKeyColumns, &$uniqueConstraints, &$addedFks, &$blacklistedFks) { $localColumns = array(); $foreignColumns = array(); @@ -565,9 +573,27 @@ private function _gatherRelationJoinColumns($joinColumns, $theJoinTable, $class, } } - $theJoinTable->addUnnamedForeignKeyConstraint( - $foreignTableName, $localColumns, $foreignColumns, $fkOptions - ); + $compositeName = $theJoinTable->getName().'.'.implode('', $localColumns); + if (isset($addedFks[$compositeName]) + && ($foreignTableName != $addedFks[$compositeName]['foreignTableName'] + || 0 < count(array_diff($foreignColumns, $addedFks[$compositeName]['foreignColumns']))) + ) { + foreach ($theJoinTable->getForeignKeys() as $fkName => $key) { + if (0 === count(array_diff($key->getLocalColumns(), $localColumns)) + && (($key->getForeignTableName() != $foreignTableName) + || 0 < count(array_diff($key->getForeignColumns(), $foreignColumns))) + ) { + $theJoinTable->removeForeignKey($fkName); + break; + } + } + $blacklistedFks[$compositeName] = true; + } elseif (!isset($blacklistedFks[$compositeName])) { + $addedFks[$compositeName] = array('foreignTableName' => $foreignTableName, 'foreignColumns' => $foreignColumns); + $theJoinTable->addUnnamedForeignKeyConstraint( + $foreignTableName, $localColumns, $foreignColumns, $fkOptions + ); + } } /** diff --git a/tests/Doctrine/Tests/Models/SingleTableInheritanceType/Structure.php b/tests/Doctrine/Tests/Models/SingleTableInheritanceType/Structure.php new file mode 100755 index 00000000000..26c577c5173 --- /dev/null +++ b/tests/Doctrine/Tests/Models/SingleTableInheritanceType/Structure.php @@ -0,0 +1,22 @@ +followedUsers = new ArrayCollection(); + $this->followedStructures = new ArrayCollection(); + } + + /* + * Remove followers + * + * @param UserFollowedUser $followers + */ + private function removeFollower(UserFollowedUser $followers) + { + $this->followers->removeElement($followers); + } + + /** + * Add followedUsers + * + * @param UserFollowedUser $followedUsers + * @return User + */ + public function addFollowedUser(UserFollowedUser $followedUsers) + { + $this->followedUsers[] = $followedUsers; + + return $this; + } + + /** + * Remove followedUsers + * + * @param UserFollowedUser $followedUsers + * @return User + */ + public function removeFollowedUser(UserFollowedUser $followedUsers) + { + $this->followedUsers->removeElement($followedUsers); + + return $this; + } + + /** + * Get followedUsers + * + * @return Doctrine\Common\Collections\Collection + */ + public function getFollowedUsers() + { + return $this->followedUsers; + } + + /** + * Add followedStructures + * + * @param UserFollowedStructure $followedStructures + * @return User + */ + public function addFollowedStructure(UserFollowedStructure $followedStructures) + { + $this->followedStructures[] = $followedStructures; + + return $this; + } + + /** + * Remove followedStructures + * + * @param UserFollowedStructure $followedStructures + * @return User + */ + public function removeFollowedStructure(UserFollowedStructure $followedStructures) + { + $this->followedStructures->removeElement($followedStructures); + + return $this; + } + + /** + * Get followedStructures + * + * @return Doctrine\Common\Collections\Collection + */ + public function getFollowedStructures() + { + return $this->followedStructures; + } +} diff --git a/tests/Doctrine/Tests/Models/SingleTableInheritanceType/UserFollowedObject.php b/tests/Doctrine/Tests/Models/SingleTableInheritanceType/UserFollowedObject.php new file mode 100755 index 00000000000..9996660c032 --- /dev/null +++ b/tests/Doctrine/Tests/Models/SingleTableInheritanceType/UserFollowedObject.php @@ -0,0 +1,32 @@ +id; + } +} diff --git a/tests/Doctrine/Tests/Models/SingleTableInheritanceType/UserFollowedStructure.php b/tests/Doctrine/Tests/Models/SingleTableInheritanceType/UserFollowedStructure.php new file mode 100755 index 00000000000..8648407da38 --- /dev/null +++ b/tests/Doctrine/Tests/Models/SingleTableInheritanceType/UserFollowedStructure.php @@ -0,0 +1,54 @@ +user = $user; + $this->followedStructure = $followedStructure; + } + + /** + * + * @return User + */ + public function getUser() + { + return $this->user; + } + + /** + * Gets followed structure + * + * @return Structure + */ + public function getFollowedStructure() + { + return $this->followedStructure; + } +} diff --git a/tests/Doctrine/Tests/Models/SingleTableInheritanceType/UserFollowedUser.php b/tests/Doctrine/Tests/Models/SingleTableInheritanceType/UserFollowedUser.php new file mode 100755 index 00000000000..0f90a2416b9 --- /dev/null +++ b/tests/Doctrine/Tests/Models/SingleTableInheritanceType/UserFollowedUser.php @@ -0,0 +1,55 @@ +user = $user; + $this->followedUser = $followedUser; + } + + /** + * {@inheritdoc} + */ + public function getUser() + { + return $this->user; + } + + /** + * Gets followed user + * + * @return User + */ + public function getFollowedUser() + { + return $this->followedUser; + } + +} diff --git a/tests/Doctrine/Tests/ORM/Tools/SchemaToolTest.php b/tests/Doctrine/Tests/ORM/Tools/SchemaToolTest.php index 66093a38d10..c215cf6f0d0 100644 --- a/tests/Doctrine/Tests/ORM/Tools/SchemaToolTest.php +++ b/tests/Doctrine/Tests/ORM/Tools/SchemaToolTest.php @@ -32,6 +32,38 @@ public function testAddUniqueIndexForUniqueFieldAnnocation() $this->assertTrue($schema->getTable('cms_users')->columnsAreIndexed(array('username')), "username column should be indexed."); } + public function testForeignKeyOnSTIWithMultipleMapping() + { + $em = $this->_getTestEntityManager(); + $schemaTool = new SchemaTool($em); + + $classes = array( + $em->getClassMetadata('Doctrine\Tests\Models\SingleTableInheritanceType\User'), + $em->getClassMetadata('Doctrine\Tests\Models\SingleTableInheritanceType\Structure'), + $em->getClassMetadata('Doctrine\Tests\Models\SingleTableInheritanceType\UserFollowedObject'), + $em->getClassMetadata('Doctrine\Tests\Models\SingleTableInheritanceType\UserFollowedStructure'), + $em->getClassMetadata('Doctrine\Tests\Models\SingleTableInheritanceType\UserFollowedUser') + ); + + $schema = $schemaTool->getSchemaFromMetadata($classes); + $this->assertTrue($schema->hasTable('users_followed_objects'), "Table users_followed_objects should exist."); + + /* @var $table \Doctrine\DBAL\Schema\Table */ + $table = ($schema->getTable('users_followed_objects')); + $this->assertTrue($table->columnsAreIndexed(array('object_id'))); + $this->assertTrue($table->columnsAreIndexed(array('user_id'))); + $foreignKeys = $table->getForeignKeys(); + $this->assertCount(1, $foreignKeys, 'user_id column has to have FK, but not object_id'); + + /* @var $fk \Doctrine\DBAL\Schema\ForeignKeyConstraint */ + $fk = reset($foreignKeys); + $this->assertEquals('users', $fk->getForeignTableName()); + + $localColumns = $fk->getLocalColumns(); + $this->assertContains('user_id', $localColumns); + $this->assertCount(1, $localColumns); + } + public function testAnnotationOptionsAttribute() { $em = $this->_getTestEntityManager();