diff --git a/lib/Doctrine/ORM/Mapping/ManyToManyMapping.php b/lib/Doctrine/ORM/Mapping/ManyToManyMapping.php index 21bdafe20..0a1aaa34e 100644 --- a/lib/Doctrine/ORM/Mapping/ManyToManyMapping.php +++ b/lib/Doctrine/ORM/Mapping/ManyToManyMapping.php @@ -68,6 +68,20 @@ class ManyToManyMapping extends AssociationMapping */ public $orderBy; + /** + * READ-ONLY: Are entries on the owning side of this join-table deleted through a database onDelete="CASCADE" operation? + * + * @var bool + */ + public $owningIsOnDeleteCascade = false; + + /** + * READ-ONLY: Are entries on the inverse side of this join-table deleted through a database onDelete="CASCADE" operation? + * + * @var bool + */ + public $inverseIsOnDeleteCascade = false; + /** * {@inheritdoc} */ @@ -115,11 +129,19 @@ class ManyToManyMapping extends AssociationMapping } foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) { + if (isset($joinColumn['onDelete']) && strtolower($joinColumn['onDelete']) == 'cascade') { + $this->owningIsOnDeleteCascade = true; + } + $this->relationToSourceKeyColumns[$joinColumn['name']] = $joinColumn['referencedColumnName']; $this->joinTableColumns[] = $joinColumn['name']; } foreach ($mapping['joinTable']['inverseJoinColumns'] as $inverseJoinColumn) { + if (isset($inverseJoinColumn['onDelete']) && strtolower($inverseJoinColumn['onDelete']) == 'cascade') { + $this->inverseIsOnDeleteCascade = true; + } + $this->relationToTargetKeyColumns[$inverseJoinColumn['name']] = $inverseJoinColumn['referencedColumnName']; $this->joinTableColumns[] = $inverseJoinColumn['name']; } @@ -169,6 +191,12 @@ class ManyToManyMapping extends AssociationMapping $serialized[] = 'joinTableColumns'; $serialized[] = 'relationToSourceKeyColumns'; $serialized[] = 'relationToTargetKeyColumns'; + if ($this->owningIsOnDeleteCascade) { + $serialized[] = 'owningIsOnDeleteCascade'; + } + if ($this->inverseIsOnDeleteCascade) { + $serialized[] = 'inverseIsOnDeleteCascade'; + } if ($this->orderBy) { $serialized[] = 'orderBy'; } diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index e62e8bbbf..f86128fff 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -335,8 +335,6 @@ class BasicEntityPersister } } - private $requiredJoinTableDeletions = null; - /** * @todo Add check for platform if it supports foreign keys/cascading. * @param array $identifier @@ -344,44 +342,27 @@ class BasicEntityPersister */ protected function deleteJoinTableRecords($identifier) { - if ($this->requiredJoinTableDeletions === null) { - $this->requiredJoinTableDeletions = array(); - foreach ($this->_class->associationMappings AS $mapping) { - /* @var $mapping \Doctrine\ORM\Mapping\AssociationMapping */ - if ($mapping->isManyToMany()) { - // TODO: Write test for composite keys - if (!$mapping->isOwningSide) { - $relatedClass = $this->_em->getClassMetadata($mapping->targetEntityName); - $mapping = $relatedClass->associationMappings[$mapping->mappedBy]; - $keys = array_keys($mapping->relationToTargetKeyColumns); + foreach ($this->_class->associationMappings AS $mapping) { + /* @var $mapping \Doctrine\ORM\Mapping\AssociationMapping */ + if ($mapping->isManyToMany()) { + if($mapping->isOwningSide && (!$mapping->owningIsOnDeleteCascade || !$this->_platform->supportsForeignKeyConstraints())) { + $this->_conn->delete( + $mapping->joinTable['name'], + array_combine(array_keys($mapping->relationToSourceKeyColumns), $identifier) + ); + } else if (!$mapping->isOwningSide) { + $relatedClass = $this->_em->getClassMetadata($mapping->targetEntityName); + $relatedMapping = $relatedClass->associationMappings[$mapping->mappedBy]; - // this is not semantically correct, onDelete should be an option of the joinColumns - // @todo optimize this (potentially in the validate association/metadata already) - foreach ($mapping->joinTable['inverseJoinColumns'] AS $joinColumn) { - if (strtoupper($joinColumn['onDelete']) == 'CASCADE') { - continue; - } - } - } else { - // this is not semantically correct, onDelete should be an option of the joinColumns - // @todo optimize this (potentially in the validate association/metadata already) - foreach ($mapping->joinTable['joinColumns'] AS $joinColumn) { - if (strtoupper($joinColumn['onDelete']) == 'CASCADE') { - continue; - } - } - - $keys = array_keys($mapping->relationToSourceKeyColumns); + if (!$relatedMapping->inverseIsOnDeleteCascade || !$this->_platform->supportsForeignKeyConstraints()) { + $this->_conn->delete( + $relatedMapping->joinTable['name'], + array_combine(array_keys($relatedMapping->relationToTargetKeyColumns), $identifier) + ); } - $this->requiredJoinTableDeletions[$mapping->joinTable['name']] = $keys; } } } - - foreach ($this->requiredJoinTableDeletions AS $table => $keys) { - $id = array_combine($keys, $identifier); - $this->_conn->delete($table, $id); - } } /** diff --git a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php index 96fc026f7..565826e9a 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php @@ -107,6 +107,25 @@ class ClassMetadataTest extends \Doctrine\Tests\OrmTestCase 'joinColumns' => array(array('name' => 'CmsUser_id', 'referencedColumnName' => 'id', 'onDelete' => 'CASCADE')), 'inverseJoinColumns' => array(array('name' => 'CmsGroup_id', 'referencedColumnName' => 'id', 'onDelete' => 'CASCADE')) ), $assoc->joinTable); + $this->assertTrue($assoc->owningIsOnDeleteCascade); + $this->assertTrue($assoc->inverseIsOnDeleteCascade); + } + + public function testSerializeManyToManyJoinTableCascade() + { + $cm = new ClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'); + $cm->mapManyToMany( + array( + 'fieldName' => 'groups', + 'targetEntity' => 'CmsGroup' + )); + + /* @var $assoc \Doctrine\ORM\Mapping\ManyToManyMapping */ + $assoc = $cm->associationMappings['groups']; + $assoc = unserialize(serialize($assoc)); + + $this->assertTrue($assoc->owningIsOnDeleteCascade); + $this->assertTrue($assoc->inverseIsOnDeleteCascade); } /**