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:
parent
6bbf2d9da3
commit
2cfc61db84
10 changed files with 147 additions and 23 deletions
|
@ -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'])) {
|
||||||
|
|
|
@ -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;
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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');
|
||||||
|
}
|
||||||
|
}
|
|
@ -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');
|
||||||
|
}
|
||||||
|
}
|
|
@ -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']);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Reference in a new issue