Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix broken changeset computation for entities loaded through fetch=EAGER + using inheritance #10884

Merged
merged 1 commit into from
Aug 9, 2023

Conversation

mpdude
Copy link
Contributor

@mpdude mpdude commented Aug 5, 2023

#10880 reports a case where the changes from #10785 cause entity updates to be missed.

Upon closer inspection, this change seems to be causing it:

https://github.com/doctrine/orm/pull/10785/files#diff-55a900494fc8033ab498c53929716caf0aa39d6bdd7058e7d256787a24412ee4L2990-L3003

The code was changed to use registerManaged() instead, which basically does the same things, but (since #10785) also includes an additional check against duplicate entity instances.

But, one detail slipped through tests and reviews: registerManaged() also updates \Doctrine\ORM\UnitOfWork::$originalEntityData, which is used to compute entity changesets. An empty array [] was passed for $data here.

This will make the changeset computation assume that a partial object was loaded and effectively ignore all field updates here:

if (! (isset($originalData[$propName]) || array_key_exists($propName, $originalData))) {
continue;
}

I think that, effectively, it is sufficient to call registerManaged() only in the two cases where a proxy was created.

Calling registerManaged() with [] as data for a proxy object is consistent with e. g. \Doctrine\ORM\EntityManager::getReference().

In the case that a full entity has to be loaded, we need not call registerManaged() at all, since that will already happen inside EntityManager::find() (or, more specifically, UnitOfWork::createEntity() called inside it).

Note that the test case has to make some provisions so that we actually reach this case:

  • Load an entity that uses fetch="EAGER" on a to-one association
  • That association being against a class that uses inheritance (why's that?)

@mpdude mpdude force-pushed the reproducer-10880 branch 2 times, most recently from 66c4c76 to 70a969b Compare August 6, 2023 21:19
@mpdude mpdude changed the title (Try to) add a reproducer for #10880 Fix #10880 – changeset computation broken in 2.16.0 for entities loaded through fetch=EAGER + using inheritance Aug 6, 2023
@mpdude mpdude marked this pull request as ready for review August 6, 2023 21:48
@mpdude
Copy link
Contributor Author

mpdude commented Aug 6, 2023

@eXsio does this change fix it for you?

@eXsio
Copy link

eXsio commented Aug 7, 2023

@mpdude yup, that does it, the tests are green :)

@eXsio
Copy link

eXsio commented Aug 7, 2023

One thing I am wondering ( as a total ignorant, so I may be completely off here :) ) but is there a valid scenario when the registerManaged() is called twice for a single Entity instance? Shouldn't there be a validation or a warning (inside of that method) if such thing happen? Breaking the Change Detection seems to me like a worst error that can possibly be in any ORM, because it breaks the contract and has potential of effectively poisoning the client's data. There is no error, there is "only" data inconsistency. If not for our single behat test that failed, we would've been happy to release it into prod and acted only when our clients discovered data errors on their side.

Just a thought.

@derrabus derrabus added the Bug label Aug 9, 2023
@derrabus derrabus added this to the 2.16.1 milestone Aug 9, 2023
…ies loaded through fetch=EAGER + using inheritance

 doctrine#10880 reports a case where the changes from doctrine#10785 cause entity updates to be missed.

Upon closer inspection, this change seems to be causing it:

https://github.com/doctrine/orm/pull/10785/files#diff-55a900494fc8033ab498c53929716caf0aa39d6bdd7058e7d256787a24412ee4L2990-L3003

The code was changed to use `registerManaged()` instead, which basically does the same things, but (since doctrine#10785) also includes an additional check against duplicate entity instances.

But, one detail slipped through tests and reviews: `registerManaged()` also updates `\Doctrine\ORM\UnitOfWork::$originalEntityData`, which is used to compute entity changesets. An empty array `[]` was passed for $data here.

This will make the changeset computation assume that a partial object was loaded and effectively ignore all field updates here:

https://github.com/doctrine/orm/blob/a616914887ea160db4158d2c67752e99624f7c8a/lib/Doctrine/ORM/UnitOfWork.php#L762-L764

I think that, effectively, it is sufficient to call `registerManaged()` only in the two cases where a proxy was created.

Calling `registerManaged()` with `[]` as data for a proxy object is consistent with e. g. `\Doctrine\ORM\EntityManager::getReference()`.

In the case that a full entity has to be loaded, we need not call `registerManaged()` at all, since that will already happen inside `EntityManager::find()` (or, more specifically, `UnitOfWork::createEntity()` called inside it).

Note that the test case has to make some provisions so that we actually reach this case:
* Load an entity that uses `fetch="EAGER"` on a to-one association
* That association being against a class that uses inheritance (why's that?)
@derrabus derrabus changed the title Fix #10880 – changeset computation broken in 2.16.0 for entities loaded through fetch=EAGER + using inheritance Fix broken changeset computation for entities loaded through fetch=EAGER + using inheritance Aug 9, 2023
@derrabus
Copy link
Member

derrabus commented Aug 9, 2023

I've fixed the PHPCS failure for you. ✌🏻

@derrabus derrabus merged commit 440b244 into doctrine:2.16.x Aug 9, 2023
41 checks passed
@mpdude mpdude deleted the reproducer-10880 branch August 9, 2023 11:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

EntityManager::find() calls UnitOfWork::registerManaged() twice and that causes Change Detection to fail
3 participants