1
0
Fork 0
mirror of synced 2025-04-03 13:23:37 +03:00

Fixed bug with orphanRemoval not removing associated Entity on OneToMany and OneToOne relationships. As defined in ClassMatedataInfo, in these situations, when orphanRemoval=true, cascade=remove is implicit. This fixes DDC-1321.

This commit is contained in:
Guilherme Blanco 2011-09-06 01:58:16 -03:00
parent 6bbf2d9da3
commit 2cfc61db84
10 changed files with 147 additions and 23 deletions

View file

@ -908,9 +908,8 @@ class ClassMetadataInfo implements ClassMetadata
$mapping['targetToSourceKeyColumns'] = array_flip($mapping['sourceToTargetKeyColumns']); $mapping['targetToSourceKeyColumns'] = array_flip($mapping['sourceToTargetKeyColumns']);
} }
//TODO: if orphanRemoval, cascade=remove is implicit! $mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) ? (bool) $mapping['orphanRemoval'] : false;
$mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) ? $mapping['isCascadeRemove'] = $mapping['orphanRemoval'] ? true : $mapping['isCascadeRemove'];
(bool) $mapping['orphanRemoval'] : false;
if (isset($mapping['id']) && $mapping['id'] === true && !$mapping['isOwningSide']) { if (isset($mapping['id']) && $mapping['id'] === true && !$mapping['isOwningSide']) {
throw MappingException::illegalInverseIdentifierAssocation($this->name, $mapping['fieldName']); throw MappingException::illegalInverseIdentifierAssocation($this->name, $mapping['fieldName']);
@ -935,9 +934,8 @@ class ClassMetadataInfo implements ClassMetadata
throw MappingException::oneToManyRequiresMappedBy($mapping['fieldName']); throw MappingException::oneToManyRequiresMappedBy($mapping['fieldName']);
} }
//TODO: if orphanRemoval, cascade=remove is implicit! $mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) ? (bool) $mapping['orphanRemoval'] : false;
$mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) ? $mapping['isCascadeRemove'] = $mapping['orphanRemoval'] ? true : $mapping['isCascadeRemove'];
(bool) $mapping['orphanRemoval'] : false;
if (isset($mapping['orderBy'])) { if (isset($mapping['orderBy'])) {
if ( ! is_array($mapping['orderBy'])) { if ( ! is_array($mapping['orderBy'])) {

View file

@ -31,7 +31,7 @@ class CmsUser
*/ */
public $name; public $name;
/** /**
* @OneToMany(targetEntity="CmsPhonenumber", mappedBy="user", cascade={"persist", "remove", "merge"}, orphanRemoval=true) * @OneToMany(targetEntity="CmsPhonenumber", mappedBy="user", cascade={"persist", "merge"}, orphanRemoval=true)
*/ */
public $phonenumbers; public $phonenumbers;
/** /**

View file

@ -0,0 +1,58 @@
<?php
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Tests\Models\CMS\CmsUser,
Doctrine\Tests\Models\CMS\CmsAddress,
Doctrine\Tests\Models\CMS\CmsPhonenumber;
require_once __DIR__ . '/../../TestInit.php';
/**
* Tests a bidirectional one-to-many association mapping with orphan removal.
*/
class OneToManyOrphanRemovalTest extends \Doctrine\Tests\OrmFunctionalTestCase
{
protected function setUp()
{
$this->useModelSet('cms');
parent::setUp();
}
public function testOrphanRemoval()
{
$user = new CmsUser;
$user->status = 'dev';
$user->username = 'romanb';
$user->name = 'Roman B.';
$phone = new CmsPhonenumber;
$phone->phonenumber = '123456';
$user->addPhonenumber($phone);
$this->_em->persist($user);
$this->_em->flush();
$userId = $user->getId();
$this->_em->clear();
$userProxy = $this->_em->getReference('Doctrine\Tests\Models\CMS\CmsUser', $userId);
$this->_em->remove($userProxy);
$this->_em->flush();
$this->_em->clear();
$query = $this->_em->createQuery('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u');
$result = $query->getResult();
$this->assertEquals(0, count($result), 'CmsUser should be removed by EntityManager');
$query = $this->_em->createQuery('SELECT p FROM Doctrine\Tests\Models\CMS\CmsPhonenumber p');
$result = $query->getResult();
$this->assertEquals(0, count($result), 'CmsPhonenumber should be removed by orphanRemoval');
}
}

View file

@ -0,0 +1,60 @@
<?php
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Tests\Models\CMS\CmsUser,
Doctrine\Tests\Models\CMS\CmsAddress,
Doctrine\Tests\Models\CMS\CmsPhonenumber;
require_once __DIR__ . '/../../TestInit.php';
/**
* Tests a bidirectional one-to-one association mapping with orphan removal.
*/
class OneToOneOrphanRemovalTest extends \Doctrine\Tests\OrmFunctionalTestCase
{
protected function setUp()
{
$this->useModelSet('cms');
parent::setUp();
}
public function testOrphanRemoval()
{
$user = new CmsUser;
$user->status = 'dev';
$user->username = 'romanb';
$user->name = 'Roman B.';
$address = new CmsAddress;
$address->country = 'de';
$address->zip = 1234;
$address->city = 'Berlin';
$user->setAddress($address);
$this->_em->persist($user);
$this->_em->flush();
$userId = $user->getId();
$this->_em->clear();
$userProxy = $this->_em->getReference('Doctrine\Tests\Models\CMS\CmsUser', $userId);
$this->_em->remove($userProxy);
$this->_em->flush();
$this->_em->clear();
$query = $this->_em->createQuery('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u');
$result = $query->getResult();
$this->assertEquals(0, count($result), 'CmsUser should be removed by EntityManager');
$query = $this->_em->createQuery('SELECT a FROM Doctrine\Tests\Models\CMS\CmsAddress a');
$result = $query->getResult();
$this->assertEquals(0, count($result), 'CmsAddress should be removed by orphanRemoval');
}
}

View file

@ -182,7 +182,7 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase
$this->assertTrue(isset($class->associationMappings['phonenumbers'])); $this->assertTrue(isset($class->associationMappings['phonenumbers']));
$this->assertFalse($class->associationMappings['phonenumbers']['isOwningSide']); $this->assertFalse($class->associationMappings['phonenumbers']['isOwningSide']);
$this->assertTrue($class->associationMappings['phonenumbers']['isCascadePersist']); $this->assertTrue($class->associationMappings['phonenumbers']['isCascadePersist']);
$this->assertFalse($class->associationMappings['phonenumbers']['isCascadeRemove']); $this->assertTrue($class->associationMappings['phonenumbers']['isCascadeRemove']);
$this->assertFalse($class->associationMappings['phonenumbers']['isCascadeRefresh']); $this->assertFalse($class->associationMappings['phonenumbers']['isCascadeRefresh']);
$this->assertFalse($class->associationMappings['phonenumbers']['isCascadeDetach']); $this->assertFalse($class->associationMappings['phonenumbers']['isCascadeDetach']);
$this->assertFalse($class->associationMappings['phonenumbers']['isCascadeMerge']); $this->assertFalse($class->associationMappings['phonenumbers']['isCascadeMerge']);

View file

@ -219,10 +219,11 @@ abstract class AbstractClassMetadataExporterTest extends \Doctrine\Tests\OrmTest
$this->assertEquals('CASCADE', $class->associationMappings['address']['joinColumns'][0]['onDelete']); $this->assertEquals('CASCADE', $class->associationMappings['address']['joinColumns'][0]['onDelete']);
$this->assertTrue($class->associationMappings['address']['isCascadeRemove']); $this->assertTrue($class->associationMappings['address']['isCascadeRemove']);
$this->assertFalse($class->associationMappings['address']['isCascadePersist']); $this->assertTrue($class->associationMappings['address']['isCascadePersist']);
$this->assertFalse($class->associationMappings['address']['isCascadeRefresh']); $this->assertFalse($class->associationMappings['address']['isCascadeRefresh']);
$this->assertFalse($class->associationMappings['address']['isCascadeMerge']); $this->assertFalse($class->associationMappings['address']['isCascadeMerge']);
$this->assertFalse($class->associationMappings['address']['isCascadeDetach']); $this->assertFalse($class->associationMappings['address']['isCascadeDetach']);
$this->assertTrue($class->associationMappings['address']['orphanRemoval']);
return $class; return $class;
} }
@ -239,11 +240,12 @@ abstract class AbstractClassMetadataExporterTest extends \Doctrine\Tests\OrmTest
$this->assertEquals('user', $class->associationMappings['phonenumbers']['mappedBy']); $this->assertEquals('user', $class->associationMappings['phonenumbers']['mappedBy']);
$this->assertEquals(array('number' => 'ASC'), $class->associationMappings['phonenumbers']['orderBy']); $this->assertEquals(array('number' => 'ASC'), $class->associationMappings['phonenumbers']['orderBy']);
$this->assertFalse($class->associationMappings['phonenumbers']['isCascadeRemove']); $this->assertTrue($class->associationMappings['phonenumbers']['isCascadeRemove']);
$this->assertTrue($class->associationMappings['phonenumbers']['isCascadePersist']); $this->assertTrue($class->associationMappings['phonenumbers']['isCascadePersist']);
$this->assertFalse($class->associationMappings['phonenumbers']['isCascadeRefresh']); $this->assertFalse($class->associationMappings['phonenumbers']['isCascadeRefresh']);
$this->assertFalse($class->associationMappings['phonenumbers']['isCascadeMerge']); $this->assertTrue($class->associationMappings['phonenumbers']['isCascadeMerge']);
$this->assertFalse($class->associationMappings['phonenumbers']['isCascadeDetach']); $this->assertFalse($class->associationMappings['phonenumbers']['isCascadeDetach']);
$this->assertTrue($class->associationMappings['phonenumbers']['orphanRemoval']);
return $class; return $class;
} }
@ -300,9 +302,11 @@ abstract class AbstractClassMetadataExporterTest extends \Doctrine\Tests\OrmTest
public function testCascadeIsExported($class) public function testCascadeIsExported($class)
{ {
$this->assertTrue($class->associationMappings['phonenumbers']['isCascadePersist']); $this->assertTrue($class->associationMappings['phonenumbers']['isCascadePersist']);
$this->assertFalse($class->associationMappings['phonenumbers']['isCascadeMerge']); $this->assertTrue($class->associationMappings['phonenumbers']['isCascadeMerge']);
$this->assertFalse($class->associationMappings['phonenumbers']['isCascadeRemove']); $this->assertTrue($class->associationMappings['phonenumbers']['isCascadeRemove']);
$this->assertFalse($class->associationMappings['phonenumbers']['isCascadeRefresh']); $this->assertFalse($class->associationMappings['phonenumbers']['isCascadeRefresh']);
$this->assertFalse($class->associationMappings['phonenumbers']['isCascadeDetach']);
$this->assertTrue($class->associationMappings['phonenumbers']['orphanRemoval']);
return $class; return $class;
} }

View file

@ -23,14 +23,14 @@ class User
public $email; public $email;
/** /**
* @OneToOne(targetEntity="Doctrine\Tests\ORM\Tools\Export\Address", cascade={"remove"}, inversedBy="user") * @OneToOne(targetEntity="Doctrine\Tests\ORM\Tools\Export\Address", inversedBy="user", cascade={"persist"}, orphanRemoval=true)
* @JoinColumn(name="address_id", onDelete="CASCADE") * @JoinColumn(name="address_id", onDelete="CASCADE")
*/ */
public $address; public $address;
/** /**
* *
* @OneToMany(targetEntity="Doctrine\Tests\ORM\Tools\Export\Phonenumber", mappedBy="user", cascade={"persist"}) * @OneToMany(targetEntity="Doctrine\Tests\ORM\Tools\Export\Phonenumber", mappedBy="user", cascade={"persist", "merge"}, orphanRemoval=true)
* @OrderBy({"number"="ASC"}) * @OrderBy({"number"="ASC"})
*/ */
public $phonenumbers; public $phonenumbers;

View file

@ -37,7 +37,7 @@ $metadata->mapOneToOne(array(
'inversedBy' => 'user', 'inversedBy' => 'user',
'cascade' => 'cascade' =>
array( array(
0 => 'remove', 0 => 'persist',
), ),
'mappedBy' => NULL, 'mappedBy' => NULL,
'joinColumns' => 'joinColumns' =>
@ -49,7 +49,7 @@ $metadata->mapOneToOne(array(
'onDelete' => 'CASCADE', 'onDelete' => 'CASCADE',
), ),
), ),
'orphanRemoval' => false, 'orphanRemoval' => true,
)); ));
$metadata->mapOneToMany(array( $metadata->mapOneToMany(array(
'fieldName' => 'phonenumbers', 'fieldName' => 'phonenumbers',
@ -57,9 +57,10 @@ $metadata->mapOneToMany(array(
'cascade' => 'cascade' =>
array( array(
1 => 'persist', 1 => 'persist',
2 => 'merge',
), ),
'mappedBy' => 'user', 'mappedBy' => 'user',
'orphanRemoval' => false, 'orphanRemoval' => true,
'orderBy' => 'orderBy' =>
array( array(
'number' => 'ASC', 'number' => 'ASC',

View file

@ -20,14 +20,15 @@
<field name="name" column="name" type="string" length="50" nullable="true" unique="true" /> <field name="name" column="name" type="string" length="50" nullable="true" unique="true" />
<field name="email" column="user_email" type="string" column-definition="CHAR(32) NOT NULL" /> <field name="email" column="user_email" type="string" column-definition="CHAR(32) NOT NULL" />
<one-to-one field="address" target-entity="Doctrine\Tests\ORM\Tools\Export\Address" inversed-by="user"> <one-to-one field="address" target-entity="Doctrine\Tests\ORM\Tools\Export\Address" inversed-by="user" orphan-removal="true">
<cascade><cascade-remove /></cascade> <cascade><cascade-persist /></cascade>
<join-column name="address_id" referenced-column-name="id" on-delete="CASCADE" on-update="CASCADE"/> <join-column name="address_id" referenced-column-name="id" on-delete="CASCADE" on-update="CASCADE"/>
</one-to-one> </one-to-one>
<one-to-many field="phonenumbers" target-entity="Doctrine\Tests\ORM\Tools\Export\Phonenumber" mapped-by="user"> <one-to-many field="phonenumbers" target-entity="Doctrine\Tests\ORM\Tools\Export\Phonenumber" mapped-by="user" orphan-removal="true">
<cascade> <cascade>
<cascade-persist/> <cascade-persist/>
<cascade-merge/>
</cascade> </cascade>
<order-by> <order-by>
<order-by-field name="number" direction="ASC" /> <order-by-field name="number" direction="ASC" />

View file

@ -23,15 +23,17 @@ Doctrine\Tests\ORM\Tools\Export\User:
name: address_id name: address_id
referencedColumnName: id referencedColumnName: id
onDelete: CASCADE onDelete: CASCADE
cascade: [ remove ] cascade: [ persist ]
inversedBy: user inversedBy: user
orphanRemoval: true
oneToMany: oneToMany:
phonenumbers: phonenumbers:
targetEntity: Doctrine\Tests\ORM\Tools\Export\Phonenumber targetEntity: Doctrine\Tests\ORM\Tools\Export\Phonenumber
mappedBy: user mappedBy: user
orderBy: orderBy:
number: ASC number: ASC
cascade: [ persist ] cascade: [ persist, merge ]
orphanRemoval: true
manyToMany: manyToMany:
groups: groups:
targetEntity: Doctrine\Tests\ORM\Tools\Export\Group targetEntity: Doctrine\Tests\ORM\Tools\Export\Group