Skip to content

Commit

Permalink
Failing test: UoW unable to break cycles when removing entities witho…
Browse files Browse the repository at this point in the history
…ut DB-level cascade

This adds a failing test case for #5665.

In this example, we have a cyclic association between three entities. All associations are NULLable, so the ORM is able to perform the INSERT operation: The cycle can be broken by scheduling an "extra UPDATE" in the UoW.

However, the UoW is unable to perform the remove operation. Cyclic references by the foreign keys in the database prevent removal of two of the entities.

If the ORM were able to detect this case and perform an UPDATE _before_ the DELETE, the test would pass.

As a workaround, `@JoinColumn(onDelete="CASCADE")` can be used. That way, the DBMS will make this UPDATE just in time and without ORM support – but that seems not to be what the OP of #5665 asked for.
  • Loading branch information
mpdude committed Oct 8, 2024
1 parent d18126a commit 7a87d62
Showing 1 changed file with 116 additions and 0 deletions.
116 changes: 116 additions & 0 deletions tests/Tests/ORM/Functional/Ticket/GH5665CommitOrderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\ORM\Functional\Ticket;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Tests\OrmFunctionalTestCase;

/**
* Here, we have a cyclic dependency between entities, but all associations are nullable.
* But, we don't have "SET NULL" cascade operations defined at the database level.
*
* The ORM is able to perform the INSERT operation, since it can NULL out associations
* and schedule deferred updates to break the cycles.
*
* However, it is unable to DELETE the entities. For pending deletions, associations are _not_
* first UPDATEd to be NULL in the database before the delete happens.
*
* By adding ON CASCADE SET NULL (pushing the problem to the DB level explicitly) it will work.
*/
final class GH5665CommitOrderTest extends OrmFunctionalTestCase
{
protected function setUp(): void
{
parent::setUp();

$this->setUpEntitySchema([GH5665CommitOrderItem::class, GH5665CommitOrderSubitem::class]);
}

public function testIssue(): void
{
$item = new GH5665CommitOrderItem();
$sub1 = new GH5665CommitOrderSubitem();
$sub2 = new GH5665CommitOrderSubitem();
$item->addItem($sub1);
$item->addItem($sub2);
$item->featuredItem = $sub2;
$this->_em->persist($item);
$this->_em->flush();

$this->expectNotToPerformAssertions();

$this->_em->remove($item);
$this->_em->flush();
}
}

/**
* @ORM\Entity
*/
class GH5665CommitOrderItem
{
/**
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*
* @var int
*/
public $id;

/**
* @ORM\OneToMany(targetEntity="GH5665CommitOrderSubitem", mappedBy="item", cascade={"all"}, orphanRemoval=true)
*
* @var Collection
*/
public $items;

/**
* @ORM\ManyToOne(targetEntity="GH5665CommitOrderSubitem")
*
* Adding the following would make the test pass, since it shifts responsibility for
* NULLing references to the DB layer. The #5665 issue is about the request that this
* happen on the ORM level.
* > @-ORM\JoinColumn(onDelete="SET NULL")
*
* @var GH5665CommitOrderSubitem
*/
public $featuredItem;

public function __construct()
{
$this->items = new ArrayCollection();
}

public function addItem(GH5665CommitOrderSubitem $item)

Check failure on line 89 in tests/Tests/ORM/Functional/Ticket/GH5665CommitOrderTest.php

View workflow job for this annotation

GitHub Actions / coding-standards / Coding Standards (8.3)

Method \Doctrine\Tests\ORM\Functional\Ticket\GH5665CommitOrderItem::addItem() does not have void return type hint.
{
$this->items[] = $item;
$item->item = $this;

Check failure on line 92 in tests/Tests/ORM/Functional/Ticket/GH5665CommitOrderTest.php

View workflow job for this annotation

GitHub Actions / coding-standards / Coding Standards (8.3)

Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space
}
}

/**
* @ORM\Entity
*/
class GH5665CommitOrderSubitem
{
/**
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*
* @var integer

Check failure on line 106 in tests/Tests/ORM/Functional/Ticket/GH5665CommitOrderTest.php

View workflow job for this annotation

GitHub Actions / coding-standards / Coding Standards (8.3)

Expected "int" but found "integer" in @var annotation.
*/
public $id;

/**
* @var GH5665CommitOrderItem

Check failure on line 111 in tests/Tests/ORM/Functional/Ticket/GH5665CommitOrderTest.php

View workflow job for this annotation

GitHub Actions / coding-standards / Coding Standards (8.3)

Incorrect order of annotations groups.
*
* @ORM\ManyToOne(targetEntity="GH5665CommitOrderItem", inversedBy="items")
*/
public $item;
}

0 comments on commit 7a87d62

Please sign in to comment.