diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index e0df63958..e982d6b8b 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -2233,6 +2233,8 @@ class UnitOfWork implements PropertyChangedListener function ($assoc) { return $assoc['isCascadeRemove']; } ); + $entitiesToCascade = array(); + foreach ($associationMappings as $assoc) { if ($entity instanceof Proxy && !$entity->__isInitialized__) { $entity->__load(); @@ -2245,18 +2247,22 @@ class UnitOfWork implements PropertyChangedListener case (is_array($relatedEntities)): // If its a PersistentCollection initialization is intended! No unwrap! foreach ($relatedEntities as $relatedEntity) { - $this->doRemove($relatedEntity, $visited); + $entitiesToCascade[] = $relatedEntity; } break; case ($relatedEntities !== null): - $this->doRemove($relatedEntities, $visited); + $entitiesToCascade[] = $relatedEntities; break; default: // Do nothing } } + + foreach ($entitiesToCascade as $relatedEntity) { + $this->doRemove($relatedEntity, $visited); + } } /** diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2775Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2775Test.php new file mode 100644 index 000000000..e98ff91a3 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2775Test.php @@ -0,0 +1,146 @@ + + */ +class DDC2775Test extends OrmFunctionalTestCase +{ + protected function setUp() + { + parent::setUp(); + + $this->setUpEntitySchema(array( + 'Doctrine\Tests\ORM\Functional\Ticket\User', + 'Doctrine\Tests\ORM\Functional\Ticket\Role', + 'Doctrine\Tests\ORM\Functional\Ticket\AdminRole', + 'Doctrine\Tests\ORM\Functional\Ticket\Authorization', + )); + } + + /** + * @group DDC-2775 + */ + public function testIssueCascadeRemove() + { + $user = new User(); + + $role = new AdminRole(); + $user->addRole($role); + + $authorization = new Authorization(); + $user->addAuthorization($authorization); + $role->addAuthorization($authorization); + + $this->_em->persist($user); + $this->_em->flush(); + + // Need to clear so that associations are lazy-loaded + $this->_em->clear(); + + $user = $this->_em->find('Doctrine\Tests\ORM\Functional\Ticket\User', $user->id); + + $this->_em->remove($user); + $this->_em->flush(); + + // With the bug, the second flush throws an error because the cascade remove didn't work correctly + $this->_em->flush(); + } +} + +/** + * @Entity @Table(name="ddc2775_role") + * @InheritanceType("JOINED") + * @DiscriminatorColumn(name="role_type", type="string") + * @DiscriminatorMap({"admin"="AdminRole"}) + */ +abstract class Role +{ + /** + * @Id @Column(type="integer") + * @GeneratedValue + */ + public $id; + + /** + * @ManyToOne(targetEntity="User", inversedBy="roles") + */ + public $user; + + /** + * @OneToMany(targetEntity="Authorization", mappedBy="role", cascade={"all"}, orphanRemoval=true) + */ + public $authorizations; + + public function addAuthorization(Authorization $authorization) + { + $this->authorizations[] = $authorization; + $authorization->role = $this; + } +} + +/** @Entity @Table(name="ddc2775_admin_role") */ +class AdminRole extends Role +{ +} + +/** + * @Entity @Table(name="ddc2775_authorizations") + */ +class Authorization +{ + /** + * @Id @Column(type="integer") + * @GeneratedValue + */ + public $id; + + /** + * @ManyToOne(targetEntity="User", inversedBy="authorizations") + */ + public $user; + + /** + * @ManyToOne(targetEntity="Role", inversedBy="authorizations") + */ + public $role; +} + +/** + * @Entity @Table(name="ddc2775_users") + */ +class User +{ + /** + * @Id @Column(type="integer") + * @GeneratedValue(strategy="AUTO") + */ + public $id; + + /** + * @OneToMany(targetEntity="Role", mappedBy="user", cascade={"all"}, orphanRemoval=true) + */ + public $roles; + + /** + * @OneToMany(targetEntity="Authorization", mappedBy="user", cascade={"all"}, orphanRemoval=true) + */ + public $authorizations; + + public function addRole(Role $role) + { + $this->roles[] = $role; + $role->user = $this; + } + + public function addAuthorization(Authorization $authorization) + { + $this->authorizations[] = $authorization; + $authorization->user = $this; + } +}