Problem/Motivation
The introduction of hasTranslationChanges() and using it when validating entities introduced some problems with field types that don't return TRUE when they should.
Before that, it was only used in the storage to optimize saving, so it was only a minor performance issue when the field values had to updated wen it actually wasn't necessary.
Now, it can prevent users from saving translations, without any workaround, so I'm setting this to major.
One example in core is the image field, from #2826021-48: FieldItemList::equals is sufficient from the storage perspective but not for code checking for changes:
Unfortunately it's not quite that simple. The widget used for images can end up submitting with no change in the referenced image, but clearing the height & width. So currently that's then considered a change, even though the preSave would only go and set the height & width properties what they already are in storage. ie. something like the following are compared:
array( 'file reference' => 'fid123', 'height' => NULL, 'width' => NULL, 'alt' => 'no change', )
vs
array( 'file reference' => 'fid123', 'height' => '456', 'width' => '789', 'alt' => 'no change', )
Those are not considered equal, even though the height & width in the first array will get set to '456' and '789' by preSave. I hope that makes sense?
Additional examples can be found in contrib, from #2826021-41: FieldItemList::equals is sufficient from the storage perspective but not for code checking for changes:
We've been testing this in our distribution as part of enabling and using content moderation. We've been running into some problems with more complex field types, problems identified so far:
GeolocationItem: It has a bunch of extra properties that it recalculates in preSave(), but that's only called after validation. I had some success in fixing that by doing that logic in setValue(), or maybe an onChange so that the computed + stored properties are always in sync if you change lat/long. However, it also has a map property that isn't set at all and then the properties don't match. Worked around that by initializing that in setValue() to an empty array, but that's kind of an API change?
WebformEntityReferenceItem: Same deal with a map property.
What seemed to help with the data properties was to put an array_filter() into the callback in FieldItemList::equals(), but not sure if that makes sense? Does seem fine to ignore a property being NULL/''/[] with one that's not set at all, which == fails on?
Proposed resolution
There are multiple approaches on how to deal with it, for example not doing value recalucation in preSave() but doing that in setValue(), to make sure it is always consistent, or overriding the equals() method to ensure it happens there.
Fix the field types in core, test them, create a change record to help contrib do the same.
Remaining tasks
User interface changes
None.
API changes
Likely none.
Data model changes
Also none.