Skip to content

Instantly share code, notes, and snippets.

@doctrinebot
Created December 13, 2015 18:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save doctrinebot/0462974f72618e031f98 to your computer and use it in GitHub Desktop.
Save doctrinebot/0462974f72618e031f98 to your computer and use it in GitHub Desktop.
Attachments to Doctrine Jira Issue DDC-166 - https://github.com/doctrine/doctrine2/issues/2303
diff --git a/lib/Doctrine/ORM/PersistentCollection.php b/lib/Doctrine/ORM/PersistentCollection.php
index bf7c6da..f578a22 100644
--- a/lib/Doctrine/ORM/PersistentCollection.php
+++ b/lib/Doctrine/ORM/PersistentCollection.php
@@ -487,6 +487,11 @@ final class PersistentCollection implements Collection
$this->initialize();
$this->coll->set($key, $value);
$this->changed();
+
+ if ($this->association !== null && $this->association['type'] == ClassMetadata::ONE_TO_MANY &&
+ $this->association['orphanRemoval']) {
+ $this->em->getUnitOfWork()->unscheduleOrphanRemoval($value);
+ }
}
/**
@@ -496,6 +501,12 @@ final class PersistentCollection implements Collection
{
$this->coll->add($value);
$this->changed();
+
+ if ($this->association !== null && $this->association['type'] == ClassMetadata::ONE_TO_MANY &&
+ $this->association['orphanRemoval']) {
+ $this->em->getUnitOfWork()->unscheduleOrphanRemoval($value);
+ }
+
return true;
}
diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php
index 76ce521..7e8ac49 100644
--- a/lib/Doctrine/ORM/UnitOfWork.php
+++ b/lib/Doctrine/ORM/UnitOfWork.php
@@ -1797,6 +1797,20 @@ class UnitOfWork implements PropertyChangedListener
{
$this->orphanRemovals[spl_object_hash($entity)] = $entity;
}
+
+ /**
+ * INTERNAL:
+ * Unschedules an orphaned entity from removal during flush().
+ *
+ * This can happen if an orphan entity is moved from one to another collection.
+ *
+ * @ignore
+ * @param object $entity
+ */
+ public function unscheduleOrphanRemoval($entity)
+ {
+ unset($this->orphanRemovals[spl_object_hash($entity)]);
+ }
/**
* INTERNAL:
diff --git a/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php b/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php
index 7a7da0c..35810ee 100644
--- a/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php
+++ b/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php
@@ -172,6 +172,37 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals(0, $this->_em->getConnection()->fetchColumn("select count(*) from cms_phonenumbers"));
}
+ /**
+ * @group DDC-166
+ */
+ public function testReuseOrphanUnschedulesRemoval()
+ {
+ $user1 = new CmsUser;
+ $user1->name = 'Roman';
+ $user1->username = 'romanb';
+ $user1->status = 'developer';
+
+ $phone = new CmsPhonenumber;
+ $phone->phonenumber = 100;
+ $user1->addPhonenumber($phone);
+
+ $user2 = new CmsUser;
+ $user2->name = 'Guilherme';
+ $user2->username = 'gblanco';
+ $user2->status = 'developer';
+ $this->_em->persist($user1);
+ $this->_em->persist($user2);
+ $this->_em->persist($phone);
+ $this->_em->flush();
+
+ $user1->getPhonenumbers()->remove(0); // here its scheduled for orphan removal
+ $user2->addPhonenumber($phone); // here its reused in another collection
+
+ $this->_em->flush();
+
+ $this->assertTrue($this->_em->contains($phone), "The Phonenumber should be contained in the UoW and associated with user2.");
+ }
+
public function testBasicQuery()
{
$user = new CmsUser;
diff --git a/lib/Doctrine/ORM/PersistentCollection.php b/lib/Doctrine/ORM/PersistentCollection.php
index bf7c6da..f578a22 100644
--- a/lib/Doctrine/ORM/PersistentCollection.php
+++ b/lib/Doctrine/ORM/PersistentCollection.php
@@ -487,6 +487,11 @@ final class PersistentCollection implements Collection
$this->initialize();
$this->coll->set($key, $value);
$this->changed();
+
+ if ($this->association !== null && $this->association['type'] == ClassMetadata::ONE_TO_MANY &&
+ $this->association['orphanRemoval']) {
+ $this->em->getUnitOfWork()->unscheduleOrphanRemoval($value);
+ }
}
/**
@@ -496,6 +501,12 @@ final class PersistentCollection implements Collection
{
$this->coll->add($value);
$this->changed();
+
+ if ($this->association !== null && $this->association['type'] == ClassMetadata::ONE_TO_MANY &&
+ $this->association['orphanRemoval']) {
+ $this->em->getUnitOfWork()->unscheduleOrphanRemoval($value);
+ }
+
return true;
}
diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php
index 76ce521..a2c4ec1 100644
--- a/lib/Doctrine/ORM/UnitOfWork.php
+++ b/lib/Doctrine/ORM/UnitOfWork.php
@@ -407,6 +407,13 @@ class UnitOfWork implements PropertyChangedListener
}
$assoc = $class->associationMappings[$name];
+
+ // unschedule possible orphan removals if entities are reused.
+ if ($assoc['type'] == ClassMetadata::ONE_TO_MANY && $assoc['orphanRemoval']) {
+ foreach ($value AS $element) {
+ unset($this->orphanRemovals[spl_object_hash($element)]);
+ }
+ }
// Inject PersistentCollection
$coll = new PersistentCollection(
@@ -1797,6 +1804,20 @@ class UnitOfWork implements PropertyChangedListener
{
$this->orphanRemovals[spl_object_hash($entity)] = $entity;
}
+
+ /**
+ * INTERNAL:
+ * Unschedules an orphaned entity from removal during flush().
+ *
+ * This can happen if an orphan entity is moved from one to another collection.
+ *
+ * @ignore
+ * @param object $entity
+ */
+ public function unscheduleOrphanRemoval($entity)
+ {
+ unset($this->orphanRemovals[spl_object_hash($entity)]);
+ }
/**
* INTERNAL:
diff --git a/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php b/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php
index 7a7da0c..5b66425 100644
--- a/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php
+++ b/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php
@@ -172,6 +172,50 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals(0, $this->_em->getConnection()->fetchColumn("select count(*) from cms_phonenumbers"));
}
+ /**
+ * @group DDC-166
+ */
+ public function testReuseOrphanUnschedulesRemoval()
+ {
+ $user1 = new CmsUser;
+ $user1->name = 'Roman';
+ $user1->username = 'romanb';
+ $user1->status = 'developer';
+
+ $phone = new CmsPhonenumber;
+ $phone->phonenumber = 100;
+ $user1->addPhonenumber($phone);
+
+ $user2 = new CmsUser;
+ $user2->name = 'Guilherme';
+ $user2->username = 'gblanco';
+ $user2->status = 'developer';
+ $this->_em->persist($user1);
+ $this->_em->persist($user2);
+ $this->_em->persist($phone);
+ $this->_em->flush();
+
+ $user1->getPhonenumbers()->remove(0); // here its scheduled for orphan removal
+ $user2->addPhonenumber($phone); // here its reused in another collection
+
+ $this->_em->flush();
+
+ $this->assertTrue($this->_em->contains($phone), "The Phonenumber should be contained in the UoW and associated with user2.");
+
+ $user3 = new CmsUser();
+ $user3->name = "Jonathan";
+ $user3->username = "jwage";
+ $user3->status = 'active';
+
+ $user2->getPhonenumbers()->remove(0); // here its scheduled for orphan removal
+ $user3->addPhonenumber($phone); // here it is reused, but NOT descheduled (ArrayCollection)
+
+ $this->_em->persist($user3);
+ $this->_em->flush();
+
+ $this->assertTrue($this->_em->contains($phone), "The Phonenumber should be contained in the UoW and associated with user3.");
+ }
+
public function testBasicQuery()
{
$user = new CmsUser;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment