-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Description
Feature Request
| Q | A |
|---|---|
| New Feature | yes |
| RFC | ? |
| BC Break | no |
Summary
TLDR: Doctrine could find a valid delete commit order in some circular references cases if it were ignoring dependencies with onDelete="SET NULL".
I have 3 entities with the following relationships:
- A contains a
OneToManyto B withcascade={"remove"}. - B contains a
OneToManyto C withcascade={"remove"}. - A also contains a
OneToOneto C with no inverse side,nullable=trueandonDelete="SET NULL".
When I remove A via the entity manager, I get a foreign key violation error because the commit order Doctrine computes is B, C, A. Indeed B can't be deleted first since it is referenced by C.
I expected the order to be C, B, A instead. That wouldn't violate FK constraints. I eventually realized I have a circular reference (A references C and C references A via B) and that Doctrine doesn't know how to deal with it.
The thing is, the circular reference is not really one when deleting, because of onDelete="SET NULL". If Doctrine were to ignore such dependencies on delete, the commit order would end up correct.
Is there any chance this could be implemented?
Disclaimer: I'm not very familiar with Doctrine and DB engines in general, there could be downsides I'm not seeing.
More precisely what happens
UnitOfWork::getCommitOrder correctly lists the dependencies for each nodes:
- Node C with dep A,
- Node B with dep C,
- Node A with deps B and C (in that order, because B is the first entity encountered which is the owning side of A).
The order in which nodes are listed (C, B, A), comes from UnitOfWork::doRemove's recursion putting the most deeply nested child first in the deletion list, and working back towards the parents.
Note in particular the dep A in C. It comes from the OneToOne relationship between A and C (the one with onDelete="SET NULL").
A bit later in CommitOrderCalculator::visit (which computes the actual commit order from the dependency list):
- C is processed first.
- Then A (being a dependency of C).
- Then B (being the first dependency of A).
- C, the dependency of B, is in progress already so there's no new recursion on it.
- B is added to the sorted list.
- Being done with B we bubble back to A but its next dependency C is in progress already so we bubble back to C.
- C is added to the sorted list (it has no other dependency).
- Finally A, of which all dependencies have been visited already, is added to the sorted list.
Thus the order of CommitOrderCalculator::sortedNodeList ends up being: B, C, A. That order is reversed then again is used in reversed (i.e. original order).
When dependencies are first listed, Doctrine could ignore the dep A in C because that dep has onDelete="SET NULL". The whole process would then compute a valid commit order C, B, A.