Bidirectional OneToOne with orphanRemoval leads to the deletion of a still referenced entity #6610

Open
opened 2026-01-22 15:35:42 +01:00 by admin · 0 comments
Owner

Originally created by @NoiseByNorthwest on GitHub (Jan 22, 2021).

I have 2 entity types, say A & B.
A->b is a bidirectional OneToOne relationship with B and inversed by B->a.
As the owning side, A->b is configured with "cascade persist" & "orphanRemoval".

What happens when flushing the entity manager after having changed A#1->b from B#1 (STATE_MANAGED) to B#2 (STATE_NEW):

Am I doing something wrong / illegal here or is it a bug ?

Workaround: Since the root cause seems to be derived from the load of B#1 during the commit, my current workaround is simply to preload A#1->b at query time, ahead of the business logic & flush.

Originally created by @NoiseByNorthwest on GitHub (Jan 22, 2021). I have 2 entity types, say `A` & `B`. `A->b` is a bidirectional OneToOne relationship with `B` and inversed by `B->a`. As the owning side, `A->b` is configured with "cascade persist" & "orphanRemoval". What happens when flushing the entity manager after having changed `A#1->b` from `B#1` (`STATE_MANAGED`) to `B#2` (`STATE_NEW`): - The changeset is computed a first time: https://github.com/doctrine/orm/blob/2.8.1/lib/Doctrine/ORM/UnitOfWork.php#L344 - `A#1->b` is considered to have changed from `B#1` to `B#2` - `B#2` is now considered to be the original value of `A#1->b`: https://github.com/doctrine/orm/blob/2.8.1/lib/Doctrine/ORM/UnitOfWork.php#L781 - Since `B#1` is now an orphan, it is removed from the UoW: https://github.com/doctrine/orm/blob/2.8.1/lib/Doctrine/ORM/UnitOfWork.php#L371 - `B#1->c` being itself a relationship with "cascade remove", `B#1` must be traversed: https://github.com/doctrine/orm/blob/2.8.1/lib/Doctrine/ORM/UnitOfWork.php#L2386 - `B#1` being a proxy, it must be first loaded: https://github.com/doctrine/orm/blob/2.8.1/lib/Doctrine/ORM/UnitOfWork.php#L2393 - `B#1->a` (`A#1->b`'s inverse side) is also loaded, since [Inverse side of x-to-one can never be lazy](https://github.com/doctrine/orm/blob/2.8.1/lib/Doctrine/ORM/UnitOfWork.php#L2734): https://github.com/doctrine/orm/blob/2.8.1/lib/Doctrine/ORM/UnitOfWork.php#L2735 - `A#1->b` is overwritten with `B#1`: https://github.com/doctrine/orm/blob/2.8.1/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php#L805 This is IMO unexpected, I do not understand this behavior which is furthermore the root cause of this issue. **Why the owning entity must be touched in this case ?** - The changeset is recomputed following the preUpdate events triggering: https://github.com/doctrine/orm/blob/2.8.1/lib/Doctrine/ORM/UnitOfWork.php#L1177 - Since `A#1->b` has `B#1` as value and `B#2` being considered as its original value, it is then wrongly considered to have changed from `B#2` to `B#1` - `A#1` is updated with `B#1` as `A#1->b`'s new value, which is actually already its current value: https://github.com/doctrine/orm/blob/2.8.1/lib/Doctrine/ORM/UnitOfWork.php#L1181 - `B#1` is deleted while still being referenced by `A#1->b`, causing a `ForeignKeyConstraintViolationException`: https://github.com/doctrine/orm/blob/2.8.1/lib/Doctrine/ORM/UnitOfWork.php#L1210 Am I doing something wrong / illegal here or is it a bug ? Workaround: Since the root cause seems to be derived from the load of `B#1` during the commit, my current workaround is simply to preload `A#1->b` at query time, ahead of the business logic & flush.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#6610