Merge pull request #1300 from Ocramius/hotfix/#1169-DDC-3343-one-to-many-persister-deletes-only-on-extra-lazy-plus-orphan-removal-2.4
[2.4] #1169 DDC-3343 one-to-omany persister deletes only on EXTRA_LAZY plus orphanRemoval
This commit is contained in:
commit
18c873216b
@ -19,7 +19,6 @@
|
|||||||
|
|
||||||
namespace Doctrine\ORM\Persisters;
|
namespace Doctrine\ORM\Persisters;
|
||||||
|
|
||||||
use Doctrine\Common\Proxy\Proxy;
|
|
||||||
use Doctrine\ORM\PersistentCollection;
|
use Doctrine\ORM\PersistentCollection;
|
||||||
use Doctrine\ORM\UnitOfWork;
|
use Doctrine\ORM\UnitOfWork;
|
||||||
|
|
||||||
@ -223,6 +222,13 @@ class OneToManyPersister extends AbstractCollectionPersister
|
|||||||
*/
|
*/
|
||||||
public function removeElement(PersistentCollection $coll, $element)
|
public function removeElement(PersistentCollection $coll, $element)
|
||||||
{
|
{
|
||||||
|
$mapping = $coll->getMapping();
|
||||||
|
|
||||||
|
if ( ! $mapping['orphanRemoval']) {
|
||||||
|
// no-op: this is not the owning side, therefore no operations should be applied
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
$uow = $this->em->getUnitOfWork();
|
$uow = $this->em->getUnitOfWork();
|
||||||
|
|
||||||
// shortcut for new entities
|
// shortcut for new entities
|
||||||
@ -238,19 +244,10 @@ class OneToManyPersister extends AbstractCollectionPersister
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$mapping = $coll->getMapping();
|
$this
|
||||||
$persister = $this->uow->getEntityPersister($mapping['targetEntity']);
|
->uow
|
||||||
$targetMetadata = $this->em->getClassMetadata($mapping['targetEntity']);
|
->getEntityPersister($mapping['targetEntity'])
|
||||||
|
->delete($element);
|
||||||
if ($element instanceof Proxy && ! $element->__isInitialized()) {
|
|
||||||
$element->__load();
|
|
||||||
}
|
|
||||||
|
|
||||||
// clearing owning side value
|
|
||||||
$targetMetadata->reflFields[$mapping['mappedBy']]->setValue($element, null);
|
|
||||||
|
|
||||||
$this->uow->computeChangeSet($targetMetadata, $element);
|
|
||||||
$persister->update($element);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -18,17 +18,20 @@ class Tweet
|
|||||||
public $id;
|
public $id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Column(type="string")
|
* @Column(type="string", length=140)
|
||||||
*/
|
*/
|
||||||
public $content;
|
public $content = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ManyToOne(targetEntity="User", inversedBy="tweets")
|
* @ManyToOne(targetEntity="User", inversedBy="tweets", cascade={"persist"}, fetch="EXTRA_LAZY")
|
||||||
*/
|
*/
|
||||||
public $author;
|
public $author;
|
||||||
|
|
||||||
public function setAuthor(User $user)
|
/**
|
||||||
|
* @param User $author
|
||||||
|
*/
|
||||||
|
public function setAuthor(User $author)
|
||||||
{
|
{
|
||||||
$this->author = $user;
|
$this->author = $author;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,9 +29,15 @@ class User
|
|||||||
*/
|
*/
|
||||||
public $tweets;
|
public $tweets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @OneToMany(targetEntity="UserList", mappedBy="owner", fetch="EXTRA_LAZY", orphanRemoval=true)
|
||||||
|
*/
|
||||||
|
public $userLists;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->tweets = new ArrayCollection();
|
$this->tweets = new ArrayCollection();
|
||||||
|
$this->userLists = new ArrayCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addTweet(Tweet $tweet)
|
public function addTweet(Tweet $tweet)
|
||||||
@ -39,4 +45,10 @@ class User
|
|||||||
$tweet->setAuthor($this);
|
$tweet->setAuthor($this);
|
||||||
$this->tweets->add($tweet);
|
$this->tweets->add($tweet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function addUserList(UserList $userList)
|
||||||
|
{
|
||||||
|
$userList->owner = $this;
|
||||||
|
$this->userLists->add($userList);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
29
tests/Doctrine/Tests/Models/Tweet/UserList.php
Normal file
29
tests/Doctrine/Tests/Models/Tweet/UserList.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Doctrine\Tests\Models\Tweet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Entity
|
||||||
|
* @Table(name="tweet_user_list")
|
||||||
|
*/
|
||||||
|
class UserList
|
||||||
|
{
|
||||||
|
const CLASSNAME = __CLASS__;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Id
|
||||||
|
* @GeneratedValue
|
||||||
|
* @Column(type="integer")
|
||||||
|
*/
|
||||||
|
public $id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Column(type="string")
|
||||||
|
*/
|
||||||
|
public $listName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ManyToOne(targetEntity="User", inversedBy="userLists")
|
||||||
|
*/
|
||||||
|
public $owner;
|
||||||
|
}
|
@ -5,6 +5,7 @@ namespace Doctrine\Tests\ORM\Functional;
|
|||||||
use Doctrine\ORM\Mapping\ClassMetadataInfo;
|
use Doctrine\ORM\Mapping\ClassMetadataInfo;
|
||||||
use Doctrine\Tests\Models\Tweet\Tweet;
|
use Doctrine\Tests\Models\Tweet\Tweet;
|
||||||
use Doctrine\Tests\Models\Tweet\User;
|
use Doctrine\Tests\Models\Tweet\User;
|
||||||
|
use Doctrine\Tests\Models\Tweet\UserList;
|
||||||
|
|
||||||
require_once __DIR__ . '/../../TestInit.php';
|
require_once __DIR__ . '/../../TestInit.php';
|
||||||
|
|
||||||
@ -26,6 +27,8 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
|||||||
{
|
{
|
||||||
$this->useModelSet('tweet');
|
$this->useModelSet('tweet');
|
||||||
$this->useModelSet('cms');
|
$this->useModelSet('cms');
|
||||||
|
$this->useModelSet('tweet');
|
||||||
|
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
$class = $this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsUser');
|
$class = $this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsUser');
|
||||||
@ -366,8 +369,7 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
|||||||
$user->articles->removeElement($article);
|
$user->articles->removeElement($article);
|
||||||
|
|
||||||
$this->assertFalse($user->articles->isInitialized(), "Post-Condition: Collection is not initialized.");
|
$this->assertFalse($user->articles->isInitialized(), "Post-Condition: Collection is not initialized.");
|
||||||
// NOTE: +2 queries because CmsArticle is a versioned entity, and that needs to be handled accordingly
|
$this->assertEquals($queryCount, $this->getCurrentQueryCount());
|
||||||
$this->assertEquals($queryCount + 2, $this->getCurrentQueryCount());
|
|
||||||
|
|
||||||
// Test One to Many removal with Entity state as new
|
// Test One to Many removal with Entity state as new
|
||||||
$article = new \Doctrine\Tests\Models\CMS\CmsArticle();
|
$article = new \Doctrine\Tests\Models\CMS\CmsArticle();
|
||||||
@ -388,7 +390,7 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
|||||||
|
|
||||||
$user->articles->removeElement($article);
|
$user->articles->removeElement($article);
|
||||||
|
|
||||||
$this->assertEquals($queryCount, $this->getCurrentQueryCount(), "Removing a persisted entity will not cause queries when the owning side doesn't actually change.");
|
$this->assertEquals($queryCount, $this->getCurrentQueryCount(), "Removing a persisted entity should cause one query to be executed.");
|
||||||
$this->assertFalse($user->articles->isInitialized(), "Post-Condition: Collection is not initialized.");
|
$this->assertFalse($user->articles->isInitialized(), "Post-Condition: Collection is not initialized.");
|
||||||
|
|
||||||
// Test One to Many removal with Entity state as managed
|
// Test One to Many removal with Entity state as managed
|
||||||
@ -658,7 +660,7 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
|||||||
/**
|
/**
|
||||||
* @group DDC-3343
|
* @group DDC-3343
|
||||||
*/
|
*/
|
||||||
public function testRemovesManagedElementFromOneToManyExtraLazyCollection()
|
public function testRemoveManagedElementFromOneToManyExtraLazyCollectionIsNoOp()
|
||||||
{
|
{
|
||||||
list($userId, $tweetId) = $this->loadTweetFixture();
|
list($userId, $tweetId) = $this->loadTweetFixture();
|
||||||
|
|
||||||
@ -672,20 +674,21 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
|||||||
/* @var $user User */
|
/* @var $user User */
|
||||||
$user = $this->_em->find(User::CLASSNAME, $userId);
|
$user = $this->_em->find(User::CLASSNAME, $userId);
|
||||||
|
|
||||||
$this->assertCount(0, $user->tweets);
|
$this->assertCount(1, $user->tweets, 'Element was not removed - need to update the owning side first');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @group DDC-3343
|
* @group DDC-3343
|
||||||
*/
|
*/
|
||||||
public function testRemovesManagedElementFromOneToManyExtraLazyCollectionWithoutDeletingTheTargetEntityEntry()
|
public function testRemoveManagedElementFromOneToManyExtraLazyCollectionWithoutDeletingTheTargetEntityEntryIsNoOp()
|
||||||
{
|
{
|
||||||
list($userId, $tweetId) = $this->loadTweetFixture();
|
list($userId, $tweetId) = $this->loadTweetFixture();
|
||||||
|
|
||||||
/* @var $user User */
|
/* @var $user User */
|
||||||
$user = $this->_em->find(User::CLASSNAME, $userId);
|
$user = $this->_em->find(User::CLASSNAME, $userId);
|
||||||
|
$tweet = $this->_em->find(Tweet::CLASSNAME, $tweetId);
|
||||||
|
|
||||||
$user->tweets->removeElement($this->_em->find(Tweet::CLASSNAME, $tweetId));
|
$user->tweets->removeElement($tweet);
|
||||||
|
|
||||||
$this->_em->clear();
|
$this->_em->clear();
|
||||||
|
|
||||||
@ -697,7 +700,11 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
|||||||
'Even though the collection is extra lazy, the tweet should not have been deleted'
|
'Even though the collection is extra lazy, the tweet should not have been deleted'
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->assertNull($tweet->author, 'Tweet author link has been removed');
|
$this->assertInstanceOf(
|
||||||
|
User::CLASSNAME,
|
||||||
|
$tweet->author,
|
||||||
|
'Tweet author link has not been removed - need to update the owning side first'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -723,12 +730,89 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
|||||||
'Even though the collection is extra lazy, the tweet should not have been deleted'
|
'Even though the collection is extra lazy, the tweet should not have been deleted'
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->assertNull($tweet->author);
|
$this->assertInstanceOf(User::CLASSNAME, $tweet->author);
|
||||||
|
|
||||||
/* @var $user User */
|
/* @var $user User */
|
||||||
$user = $this->_em->find(User::CLASSNAME, $userId);
|
$user = $this->_em->find(User::CLASSNAME, $userId);
|
||||||
|
|
||||||
$this->assertCount(0, $user->tweets);
|
$this->assertCount(1, $user->tweets, 'Element was not removed - need to update the owning side first');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group DDC-3343
|
||||||
|
*/
|
||||||
|
public function testRemoveOrphanedManagedElementFromOneToManyExtraLazyCollection()
|
||||||
|
{
|
||||||
|
list($userId, $userListId) = $this->loadUserListFixture();
|
||||||
|
|
||||||
|
/* @var $user User */
|
||||||
|
$user = $this->_em->find(User::CLASSNAME, $userId);
|
||||||
|
|
||||||
|
$user->userLists->removeElement($this->_em->find(UserList::CLASSNAME, $userListId));
|
||||||
|
|
||||||
|
$this->_em->clear();
|
||||||
|
|
||||||
|
/* @var $user User */
|
||||||
|
$user = $this->_em->find(User::CLASSNAME, $userId);
|
||||||
|
|
||||||
|
$this->assertCount(0, $user->userLists, 'Element was removed from association due to orphan removal');
|
||||||
|
$this->assertNull(
|
||||||
|
$this->_em->find(UserList::CLASSNAME, $userListId),
|
||||||
|
'Element was deleted due to orphan removal'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group DDC-3343
|
||||||
|
*/
|
||||||
|
public function testRemoveOrphanedUnManagedElementFromOneToManyExtraLazyCollection()
|
||||||
|
{
|
||||||
|
list($userId, $userListId) = $this->loadUserListFixture();
|
||||||
|
|
||||||
|
/* @var $user User */
|
||||||
|
$user = $this->_em->find(User::CLASSNAME, $userId);
|
||||||
|
|
||||||
|
$user->userLists->removeElement(new UserList());
|
||||||
|
|
||||||
|
$this->_em->clear();
|
||||||
|
|
||||||
|
/* @var $userList UserList */
|
||||||
|
$userList = $this->_em->find(UserList::CLASSNAME, $userListId);
|
||||||
|
$this->assertInstanceOf(
|
||||||
|
UserList::CLASSNAME,
|
||||||
|
$userList,
|
||||||
|
'Even though the collection is extra lazy + orphan removal, the user list should not have been deleted'
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(
|
||||||
|
User::CLASSNAME,
|
||||||
|
$userList->owner,
|
||||||
|
'User list to owner link has not been removed'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group DDC-3343
|
||||||
|
*/
|
||||||
|
public function testRemoveOrphanedManagedLazyProxyFromExtraLazyOneToMany()
|
||||||
|
{
|
||||||
|
list($userId, $userListId) = $this->loadUserListFixture();
|
||||||
|
|
||||||
|
/* @var $user User */
|
||||||
|
$user = $this->_em->find(User::CLASSNAME, $userId);
|
||||||
|
|
||||||
|
$user->userLists->removeElement($this->_em->getReference(UserList::CLASSNAME, $userListId));
|
||||||
|
|
||||||
|
$this->_em->clear();
|
||||||
|
|
||||||
|
/* @var $user User */
|
||||||
|
$user = $this->_em->find(User::CLASSNAME, $userId);
|
||||||
|
|
||||||
|
$this->assertCount(0, $user->userLists, 'Element was removed from association due to orphan removal');
|
||||||
|
$this->assertNull(
|
||||||
|
$this->_em->find(UserList::CLASSNAME, $userListId),
|
||||||
|
'Element was deleted due to orphan removal'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -751,4 +835,25 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
|||||||
|
|
||||||
return array($user->id, $tweet->id);
|
return array($user->id, $tweet->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int[] ordered tuple: user id and user list id
|
||||||
|
*/
|
||||||
|
private function loadUserListFixture()
|
||||||
|
{
|
||||||
|
$user = new User();
|
||||||
|
$userList = new UserList();
|
||||||
|
|
||||||
|
$user->name = 'ocramius';
|
||||||
|
$userList->listName = 'PHP Developers to follow closely';
|
||||||
|
|
||||||
|
$user->addUserList($userList);
|
||||||
|
|
||||||
|
$this->_em->persist($user);
|
||||||
|
$this->_em->persist($userList);
|
||||||
|
$this->_em->flush();
|
||||||
|
$this->_em->clear();
|
||||||
|
|
||||||
|
return array($user->id, $userList->id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -166,6 +166,11 @@ abstract class OrmFunctionalTestCase extends OrmTestCase
|
|||||||
'Doctrine\Tests\Models\Taxi\Car',
|
'Doctrine\Tests\Models\Taxi\Car',
|
||||||
'Doctrine\Tests\Models\Taxi\Driver',
|
'Doctrine\Tests\Models\Taxi\Driver',
|
||||||
),
|
),
|
||||||
|
'tweet' => array(
|
||||||
|
'Doctrine\Tests\Models\Tweet\User',
|
||||||
|
'Doctrine\Tests\Models\Tweet\Tweet',
|
||||||
|
'Doctrine\Tests\Models\Tweet\UserList',
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -275,6 +280,7 @@ abstract class OrmFunctionalTestCase extends OrmTestCase
|
|||||||
}
|
}
|
||||||
if (isset($this->_usedModelSets['tweet'])) {
|
if (isset($this->_usedModelSets['tweet'])) {
|
||||||
$conn->executeUpdate('DELETE FROM tweet_tweet');
|
$conn->executeUpdate('DELETE FROM tweet_tweet');
|
||||||
|
$conn->executeUpdate('DELETE FROM tweet_user_list');
|
||||||
$conn->executeUpdate('DELETE FROM tweet_user');
|
$conn->executeUpdate('DELETE FROM tweet_user');
|
||||||
}
|
}
|
||||||
if (isset($this->_usedModelSets['legacy'])) {
|
if (isset($this->_usedModelSets['legacy'])) {
|
||||||
@ -305,6 +311,12 @@ abstract class OrmFunctionalTestCase extends OrmTestCase
|
|||||||
$conn->executeUpdate('DELETE FROM taxi_driver');
|
$conn->executeUpdate('DELETE FROM taxi_driver');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($this->_usedModelSets['tweet'])) {
|
||||||
|
$conn->executeUpdate('DELETE FROM tweet_tweet');
|
||||||
|
$conn->executeUpdate('DELETE FROM tweet_user_list');
|
||||||
|
$conn->executeUpdate('DELETE FROM tweet_user');
|
||||||
|
}
|
||||||
|
|
||||||
$this->_em->clear();
|
$this->_em->clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user