From f39614136ffe9272ff3bfad5ac6a04e943d55870 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Mon, 21 Aug 2017 19:58:16 +0200 Subject: [PATCH] #1521 DDC-2922 reproducing test scenarios within the `UnitOfWorkTest` --- tests/Doctrine/Tests/ORM/UnitOfWorkTest.php | 131 ++++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php b/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php index bfad30be8..47ea39b47 100644 --- a/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php +++ b/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php @@ -625,6 +625,95 @@ class UnitOfWorkTest extends OrmTestCase self::assertSame($merged, $persistedEntity); self::assertSame($persistedEntity->generatedField, $mergedEntity->generatedField); } + + /** + * Unlike next test, this one demonstrates that the problem does + * not necessarily reproduce if all the pieces are being flushed together. + * + * @group DDC-2922 + * @group #1521 + */ + public function testNewAssociatedEntityPersistenceOfNewEntitiesThroughCascadedAssociationsFirst() + { + $persister1 = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata(CascadePersistedEntity::class)); + $persister2 = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata(EntityWithCascadingAssociation::class)); + $persister3 = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata(EntityWithNonCascadingAssociation::class)); + $this->_unitOfWork->setEntityPersister(CascadePersistedEntity::class, $persister1); + $this->_unitOfWork->setEntityPersister(EntityWithCascadingAssociation::class, $persister2); + $this->_unitOfWork->setEntityPersister(EntityWithNonCascadingAssociation::class, $persister3); + + $cascadePersisted = new CascadePersistedEntity(); + $cascading = new EntityWithCascadingAssociation(); + $nonCascading = new EntityWithNonCascadingAssociation(); + + // First we persist and flush a EntityWithCascadingAssociation with + // the cascading association not set. Having the "cascading path" involve + // a non-new object is important to show that the ORM should be considering + // cascades across entity changesets in subsequent flushes. + $cascading->cascaded = $cascadePersisted; + $nonCascading->cascaded = $cascadePersisted; + + $this->_unitOfWork->persist($cascading); + $this->_unitOfWork->persist($nonCascading); + + $this->_unitOfWork->commit(); + + $this->assertCount(1, $persister1->getInserts()); + $this->assertCount(1, $persister2->getInserts()); + $this->assertCount(1, $persister3->getInserts()); + } + + + /** + * This test exhibits the bug describe in the ticket, where an object that + * ought to be reachable causes errors. + * + * @group DDC-2922 + * @group #1521 + */ + public function testNewAssociatedEntityPersistenceOfNewEntitiesThroughNonCascadedAssociationsFirst() + { + $persister1 = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata(CascadePersistedEntity::class)); + $persister2 = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata(EntityWithCascadingAssociation::class)); + $persister3 = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata(EntityWithNonCascadingAssociation::class)); + $this->_unitOfWork->setEntityPersister(CascadePersistedEntity::class, $persister1); + $this->_unitOfWork->setEntityPersister(EntityWithCascadingAssociation::class, $persister2); + $this->_unitOfWork->setEntityPersister(EntityWithNonCascadingAssociation::class, $persister3); + + $cascadePersisted = new CascadePersistedEntity(); + $cascading = new EntityWithCascadingAssociation(); + $nonCascading = new EntityWithNonCascadingAssociation(); + + // First we persist and flush a EntityWithCascadingAssociation with + // the cascading association not set. Having the "cascading path" involve + // a non-new object is important to show that the ORM should be considering + // cascades across entity changesets in subsequent flushes. + $cascading->cascaded = null; + + $this->_unitOfWork->persist($cascading); + $this->_unitOfWork->commit(); + + self::assertCount(0, $persister1->getInserts()); + self::assertCount(1, $persister2->getInserts()); + self::assertCount(0, $persister3->getInserts()); + + // Note that we have NOT directly persisted the CascadePersistedEntity, + // and EntityWithNonCascadingAssociation does NOT have a configured + // cascade-persist. + $nonCascading->nonCascaded = $cascadePersisted; + + // However, EntityWithCascadingAssociation *does* have a cascade-persist + // association, which ought to allow us to save the CascadePersistedEntity + // anyway through that connection. + $cascading->cascaded = $cascadePersisted; + + $this->_unitOfWork->persist($nonCascading); + $this->_unitOfWork->commit(); + + self::assertCount(1, $persister1->getInserts()); + self::assertCount(1, $persister2->getInserts()); + self::assertCount(1, $persister3->getInserts()); + } } /** @@ -789,3 +878,45 @@ class EntityWithRandomlyGeneratedField $this->generatedField = mt_rand(0, 100000); } } + +/** @Entity */ +class CascadePersistedEntity +{ + /** @Id @Column(type="string") @GeneratedValue(strategy="NONE") */ + private $id; + + public function __construct() + { + $this->id = uniqid(self::class, true); + } +} + +/** @Entity */ +class EntityWithCascadingAssociation +{ + /** @Id @Column(type="string") @GeneratedValue(strategy="NONE") */ + private $id; + + /** @ManyToOne(targetEntity=CascadePersistedEntity::class, cascade={"persist"}) */ + public $cascaded; + + public function __construct() + { + $this->id = uniqid(self::class, true); + } +} + +/** @Entity */ +class EntityWithNonCascadingAssociation +{ + /** @Id @Column(type="string") @GeneratedValue(strategy="NONE") */ + private $id; + + /** @ManyToOne(targetEntity=CascadePersistedEntity::class) */ + public $nonCascaded; + + public function __construct() + { + $this->id = uniqid(self::class, true); + } +}