Problem/Motivation
- Create an entity with two translation A and B
- Initialize all the fields for each translation through e.g. $entity->getFields()
- Clone the entity in translation B - $clone = clone $entity->getTranslation("B");
- Make changes on the cloned entity object as e.g. "$clone->enforceIsNew();"
- Now the fields of translation A of the cloned entity still point to the original entity and they get a false result by checking $this->getEntity()->isNew(), which causes e.g. causes php error and breaking the execution in ChangedItem::preSave when calling $entity->original->hasTranslationChanges() assuming $entity->original is set, but it is not as the clone is the entity that is being saved and not the original reference.
Proposed resolution
In ContentEntityBase::__clone clear the translation cache before cloning the fields in order to ensure the cloned fields will get an entity translation reference of the cloned entity instead of the original entity.
The decoupling of the object properties has to be done as well before cloning the fields as this will cause cloning of the clone when initializing the translations of the clone and we want to prevent that ::initializeTranslation will create references to the properties of the original entity.
Remaining tasks
Review & Commit.
User interface changes
None.
API changes
None.
Data model changes
None.
Comment | File | Size | Author |
---|---|---|---|
#32 | 2841311-27.patch | 6.51 KB | tstoeckler |
#2 | 2841311-2-test-only.patch | 2.57 KB | hchonov |
Comments
Comment #2
hchonovComment #3
hchonovThe decoupling of the object properties has to be done as well before cloning the fields as this will cause cloning of the clone when initializing the translations of the clone and we want to prevent that ::initializeTranslation will create references to the properties of the original entity.
Comment #4
hchonovComment #8
hchonovThe fails here look really strange however the issue here appears to be a blocker for #2839195: Add a method to access the original property .
Comment #9
hchonovIs the HEAD failing or what is happening? I've reverted all the changes locally and still getting those fails...
Comment #10
Berdir> Class 'Drupal\migrate\Event\MigrateEvents' not found
I've seen that before, some fun stuff about class autoloading... #2776235: Cached autoloader misses cause failures when missed class becomes available and possibly other issues. but that was fixed?
Comment #11
BerdirAnd cached autoloader might also explain why you still get fails even after reverting the changes, as something is still cached in apcu, a restart might help?
Comment #12
hchonovUnfortunately APCu is always disabled on my system as somehow it has issues with OSX. Even though I've restarted, but nothing changed.
When running the ConfigImportAllTest I get only the fails from ConfigImportAllTest:: testInstallUninstall, but none fails such as "Class 'Drupal\migrate\Event\MigrateEvents' not found".
I've run "composer install"
I even deleted the vendor folder and reinstalled all the dependencies again, but this did not help as well, so I have no idea why my system is still broken.
However the tests are now green here probably because of the revert of #2840596: Update Symfony components to ~2.8.16.
Comment #13
hchonovComment #14
tstoecklerWow, another beautiful find!
Code-wise it looks perfect. I think we can improve the comments a bit, though.
getTranslation()
call is the culprit and that the cache needs to be cleared so that new objects get created throughinitializeTranslation()
I had a go at trying to improve this:
Also one minor note:
Let's use just "reference" instead of "entity reference" here, please, to avoid confusions with the field.
Comment #15
tstoecklerComment #16
hchonovThanks for the review, @tstoeckler. I've addressed all your remarks.
Comment #17
hchonovOups I've forgot to update the second comment chunk .. Sorry about this.
Comment #18
tstoecklerComment #19
Grayside CreditAttribution: Grayside at Phase2 for Norwegian Cruise Line commented@hchonov pointed me here as closely related to a migration stubbing issue I'm having with taxonomy entities (and specifically not with any other entity type). In a non-multilingual site.
original
property on the entity. Also, the entity ID does not match the one generated for the stub in step (1), instead it has a tid I do not see in the database.Fatal error: Call to a member function hasTranslation() on null in core/lib/Drupal/Core/Field/Plugin/Field/FieldType/ChangedItem.php on line 52
Re-posting as a consolidated version of what I shared on #2543726: Make $term->parent behave like any other entity reference field, to fix REST and Migrate support and de-customize its Views integration for discovery/solution review purposes.
Comment #20
hchonov@Grayside does your problem still exists with the latest patch from this issue applied?
Comment #22
hchonovIt looks like it has been just a random failure, therefore putting back to RTBC.
Comment #24
BerdirNow it needs a reroll :)
Comment #25
tstoecklerRerolled.
Comment #26
tstoecklerConflict was #2675010: Cloned entity will point to the same field objects if the clone was created after an entity translation has been initialized for anyone following along at home, BTW.
Comment #27
hchonovJust one nitpick about the re-roll.
"and" is left at the false place :)
Now it needs RTBC back again :P.
Comment #28
tstoecklerOops, yeah I think I'm allowed to RTBC that interdiff then. ;-)
Comment #30
Grayside CreditAttribution: Grayside at Phase2 for Norwegian Cruise Line commentedHere is a broad backport of the patch to Drupal 8.2/8.1. There were enough changes in ContentEntityBase::__clone() that I ended up copying that method entirely over what I found in Drupal 8.2, but preliminary testing shows it's working fine.
This does appear to solve the issue I outlined in #19.
Comment #32
tstoecklerRe-uploading latest patch.
Comment #33
alexpottCommitted and pushed 151050c to 8.4.x and 6245eeb to 8.3.x. Thanks!
This is really just a move of code around so that the translation cache is cleared first. Test coverage looks good.