Problem/Motivation
A you can specifiy a FieldItem normalization class such as \Drupal\serialization\Normalizer\EntityReferenceFieldItemNormalizer.
Normalization will work in these classes because \Drupal\serialization\Normalizer\ContentEntityNormalizer::normalize will call normalize() on the field level.
ContentEntityNormalizer though does not override \Drupal\serialization\Normalizer\EntityNormalizer::denormalize so denormalize() is not called on the field values
The entity is simply created via:
// Create the entity from data.
$entity = $this->entityManager->getStorage($entity_type_id)->create($data);
Proposed resolution
Implement \Drupal\serialization\Normalizer\ContentEntityNormalizer::denormalize()
Add a FieldNormalizer and FieldItemNormalizer
Remaining tasks
User interface changes
API changes
Data model changes
Comment | File | Size | Author |
---|---|---|---|
#2 | 2827218-2-field_denormalize.patch | 14.34 KB | tedbow |
| |||
#5 | 2827218-4-field_denormalize.patch | 14.37 KB | tedbow |
#7 | 2827218-7-field_denormalize.patch | 14.36 KB | tedbow |
#7 | interdiff-5-7.txt | 871 bytes | tedbow |
#9 | 2827218-9-field_denormalize.patch | 15.15 KB | tedbow |
Comments
Comment #2
tedbowThis will probably break test but new test \Drupal\Tests\serialization\Kernel\FieldItemSerializationTest should pass
Comment #4
larowlanThis could throw an exception if the field doesn't exist, we should wrap it in a
::hasField
check - sometimes normalizers add additional data to the output that doesn't correspond to an entity field, e.g. #2698785: Cannot deploy book nodes - book metatdata not serialized does thisComment #5
tedbowForgot to add @group to test. Should run now
Comment #7
tedbowFix priorities. the priorities of the new field noramlizer should be below hal's versions.
Comment #9
tedbowNeeded to update priority of
Comment #10
tedbowComment #12
tedbowThis patch does a couple things
Comment #13
Wim LeersI currently find this patch very confusing still. That may be me though. I'd love to see damiankloip review this.
"remaining"? That implies some are discarded?
Anyway, I think this entire comment can be removed.
So how exactly does it do this?
We should not mention REST module. As far as this code is concerned, we're just dealing with serialization.
The comment is talking about FieldItemList. But the
$context['target_instance']
suggests an entity reference field. And then finally we just call denormalize on$field_data
and don't even modify$entity
anywhere.I don't understand how this works.
c/p remnant: this is not at all about HAL.
Overly verbose; can just use "inheritdoc".
Oh so this is where target_instance comes to play again. So this is an "internal use only" thing, i.e. an implementation detail we should shield the world against. Right?
If so, I suggest we use a more specific name, and possibly prefixed with a double underscore?
Strict check please.
Isn't this
lang
attribute HAL-specific stuff?Same as above.
Same as above.
Same as above.
Same as above.
Comment #15
tedbow@Wim Leers thanks for the review. Sorry for my sloppiness for not cleaning this more before I uploaded.
This patch address the review and adds more comments that hopefully makes how the process works more clear.
1. removed comment
2. replaced the comment with one that I hope describes what is going on.
3. removed in update comment above
4. Updated to __target_field_instance which hopefully removes the confusion with an entity reference.
This class calls the Field normalizer which in turn calls the FieldItem normalizer. The field item normalizer will need the FieldItem to set the values once denormalized. The entity is not needed but will be updated via its fieldItems
5,6 fixed
7. Yes added the underscore.
FieldNormalizer actually recieves the FieldItemList in the context so updated to __target_field_instance
FieldItemNormalizer receives the FieldItem so I updated to __target_field_item_instance.
I also added type hint comment so this more clear.
8.fixed
9.Yes I missed that this was HAL specific removed and also removed createTranslatedInstance()
10-13 fixed
Comment #16
tedbowComment #18
tedbowLooking at the failing tests.
\Drupal\serialization\Normalizer\ContentEntityNormalizer::denormalize should not be updating the bundle field. This will be set in \Drupal\serialization\Normalizer\EntityNormalizer::denormalize
That function is also doing this
So tests that were sending the bundle in different formats would fail when ContentEntityNormalizer::denormalize was trying to set the value again.
Comment #19
Wim LeersFirst of all: wow. This definitely needs an Entity Field API expert's eyes on it. Preferably multiple.
There was a comment justifying this priority. But the hal priority is not being changed, yet this one is. So either this change must be reverted, or the justification must be updated.
Nit: s/Hal/hal/
Nit: s/Field/field/
These need justifications too.
===
Nit: underscores, not camelCase.
Is this also correct for the case of
POST
an entity but with only required fields provided in the request body? Will this still result in the default values being set for a new entity?IOW: what if
$field_data === []
? Does the denormalizer then automatically set it to the default values again? At which point I wonder if this code should only run if$field_data !== []
?What is this parent necessary for? I don't see any
->parent()
invocation?Should this not be called
FieldItemListNormalizer
?Why does this extend
ListNormalizer
?===
FQCN.
Why this priority?
Comment #20
tedbow@Wim Leers thanks for the review
re #19
1. Updated the comment. It has to now be higher than the new FieldItemNormalizer
2. fixed
3. fixed
4. Added comment. They need to be higher than hal corresponding normalizers
5. Fixed
6. fixed
7.
Yes it will work to still set default values. I have updated FieldItemSerializationTest to show this. I created a text field with default and then do another denormalization where the field value is not provided.
The above test works either way but it is 1 less function call that isn't necessary so fixed it.
8. Yes removed.
9.
I followed the naming convention that hal used for this. I think it is easier to understand when the corresponding normalizers are named the same.
Without this patch \Drupal\serialization\Normalizer\ListNormalizer::normalize was being called to normalized FieldItemLists.
This is because ListNormalizer has
protected $supportedInterfaceOrClass = 'Drupal\Core\TypedData\ListInterface';
and
interface FieldItemListInterface extends ListInterface
Therefore by extending ListNormalizer the normalization is still covered by the same function.
10. removed because no need for parent.
11. fixed
12. Lowered priority and added comment. Needs to be above serialization.normalizer.field_item.
Comment #21
Wim LeersThanks, this now looks much better. Now blocked on Entity Field API expert's review.
Comment #22
BerdirSo what's interesting is that the parent class has a whole bunch of logic that content entity specific too. 80% of it is bundle related and calls content/fieldableentity specific methods.
separate issue but that should probably move down too?
Second, can we avoid code duplication (and difference implementations) between this and \Drupal\hal\Normalizer\ContentEntityNormalizer::denormalize(). That has a bunch of hal specific things, but actually creating the fields and running normalizers on each field is basically the same, no? except that that seems to filter out the bundle field differently and also does empty-ing slightly different. And the hal implementation also has special logic for the langcode, not sure if that applies here.
Btw. All those tricks are necessary because we have no proper entity factory that can create an *empty* entity. Look how much simpler this gets with #1867228: Make EntityTypeManager provide an entity factory.
So the basic problem that we assign twice.
First, we create the entity with the raw values. And then we loop over it again and set the values, again.
This only works because entity field API allows you to put whatever you want into the values of a field item and it will also happily return it again.
But it might actually cause problems once we actually have normalizers that result in different structure where trying to set that would cause problems.
If you compare with hal.module, it works differently, it actually creates the entity only with the absolutely required data* (bundle if existing and langcode) and then assigns everything only after it has been denormalized. So again, can't we share more code with hal?
Comment #23
damiankloip CreditAttribution: damiankloip commentedI spoke to berdir about this on IRC, going to do a bit of work on it.
Comment #24
damiankloip CreditAttribution: damiankloip commentedLet's first try to address the point about assigning the field values twice. This does something more like hal module does; create an empty entity just based on the bundle then apply each field value from the data.
I spoke with Berdir about moving the code currently in \Drupal\serialization\Normalizer\EntityNormalizer::denormalize() (and probably what's in Drupal\serialization\Normalizer\ContentEntityNormalizer::normalize()) into a new FieldableEntityNormalizer as this is most accurate. The main concern around this initially is that this logic completely doesn't apply to config entities. HOWEVER, we have the small issue around BC. So based on that I think we might be stuck adding this functionality into EntityNormalizer instead so we can keep the fieldable stuff it provides but we could keep EntityNormalizer functionality as-is and reuse some of these methods in a FieldableEntityNormalizer instead possibly. I have also refactored some of the existing code in an attempt to make it more re-usable in subsequent patches (if we try and tackle being able to reuse this stuff in hal module too).
This is a first attempt, it seems to work ok so far.
Comment #26
tstoecklerThis comment seems strange to me. As far as I can tell, all fields we're normalized in
parent::denormalize()
. Is this being ignored because it's read-only? Is so, feel free to leave the code as is, and point to #2350429: Clarify the meaning of read-only fields and properties in the code, but I think the current comment could be improved.I can somehow guess why that's both needed, but this should could use some serious comment on the different input data here. As far as I can see, the point being that
$field_data
can also be an (empty) string or integer.This is somewhat lying as people can always do
$items->getEntity()
and still do crazy stuff with the entity.The '__' prefix suggests this is something very much internal, but it seems like pretty legitimate usage of the
$context
parameter. Can you explain?Also why "_instance" instead of "_item_list"?
Could use
FieldItemInterface::class
Again I think "_item" is more clear than "_item_instance" in my opinion.
Comment #27
damiankloip CreditAttribution: damiankloip commented@tstoeckler looks like this was not based on that latest patch? I'll see which ones apply on the current patch, some other good points though - thanks! We will be trying to consolidate what we can with the hal functionality, so I think that means (for 8.x atleast) that we use the same naming scheme that hal uses. I.e. 'target_instance' so we can hopefully share more code.
Comment #28
damiankloip CreditAttribution: damiankloip commentedComment #29
tstoecklerOh yeah, that was a pretty big x-post, sorry.
Comment #30
damiankloip CreditAttribution: damiankloip commentedThis addresses most of your feedback I think, and makes the name changes to fall in line with hal module as suggested above. As if we want to consolidate this functionality more, we need to use the name and $context usage that hal already uses. better naming will have to be talked about for Drupal 9 I think.
1. Doesn't exist anymore, but yes, it was because it's a read only field. We now just create an entity with the bundle, then iterate over the remaining field data.
2. This has a comment already in the more recent patch and is done slightly differently too (we don't need to check the value).
3. Updated the comment. I think that's clearer...
4 & 6: Renamed to only use 'target_instance' like hal module
5. Changed!
Let's see how this patch gets on before trying to see what we can share between serialization and hal module.
EDIT: the previous patch got removed/deleted and I can't restore it for some reason...
Comment #32
damiankloip CreditAttribution: damiankloip commentedThis should fix the EntityNormalizerTest so at least we have something passing that we can tweak.
Comment #33
tedbowLooking good!
Here are some nits
Missing "*"
Description for the @return value is missing (at line 108)
Missing parameter comment
Comment #34
Wim Leers#24:
If all this logic actually belongs in a
FieldableEntityNormalizer
, can we then create a D9-only issue, put this in a helper method, and annotate that helper method with a TODO pointing to that D9 issue?#32: This should also update the HAL module.
These priorities don't allow anything to be injected in between.
80 cols violation.
80 cols violation.
Comment #35
damiankloip CreditAttribution: damiankloip commentedCreated the FieldableEntityNormalizer D9 issue: #2834734: Add a FieldableEntityNormalizer (rather than having support for fields in EntityNormalizer, which we can't change anymore due to BC) - this is referenced in the latest patch.
This does:
FieldableEntityNormalizerTrait
where the methods added in the previous couple of patches now live@Wim can you clarify what you mean by this comment too please? :) thanks!
Comment #36
damiankloip CreditAttribution: damiankloip commentedComment #37
damiankloip CreditAttribution: damiankloip commentedtrait/property notice.
Comment #40
damiankloip CreditAttribution: damiankloip commentedComment #41
Wim LeersNever mind that, I confused this with #2751325: All serialized values are strings, should be integers/booleans when appropriate.
Comment #42
Wim LeersI think this looks great. RTBC as far as I'm concerned. I'll let @tedbow actually RTBC this, since he knows this code better than I do.
Comment #44
damiankloip CreditAttribution: damiankloip commentedThanks Wim. That un-confuses me now too :)
Need to make sure the _restSubmittedFields mess is added to the non field case too I think...
Comment #45
tedbowLooks great! @damiankloip thanks for completing this!
Comment #46
alexpottGot to love traits - nice to not maintain the duplicate code.
A few nits to fix
We need a CR for this. And the delicate dance of priorities here makes me think that we might take the opportunity to borrow a leaf from Symfony's book at space these numbers out a bit more... They use numbers to the power of 2... ie. 32,64,128,256,512,1024... not a substitute for a proper dependency system but better than what we're doing.
Comment #47
BerdirOn 3: #2575761: Discuss a better system for discovering and selecting normalizers
Comment #48
damiankloip CreditAttribution: damiankloip commentedThanks Alex. Here is a new patch to fix the nits.
The priorities are indeed a massive pain in everyones behind. It is amplified by the fact that we have to juggle normalizers for various formats in here too. I would ideally like to split the serializer out so we have one serializer instance per format. That's another story though. We should have gone with better priorities initially and it would have made our lives a little easier. The trouble with changing these now is that people could be depending on the current priorities. E.g. JSON API uses priorities based on what core is currently using. I would guess there is a whole load of contrib and custom code that's doing the same.
Comment #49
damiankloip CreditAttribution: damiankloip commentedCR: https://www.drupal.org/node/2836018
Comment #50
Wim Leers#46.3:
What we really should do, is get rid of priorities altogether. It's the same brittle unmaintainable system as we already have with "weights" in Drupal. We're moving things away from weights (e.g. assets in D7 vs D8), but sadly with Symfony we just got more of it. We need "before" and "after" relationships, then let code calculate the right order.
But alas, that would be an even bigger BC break of course.
Comment #51
Wim LeersIndeed.
But @damiankloip, your patch is modifying priorities. So is there a good reason to not do what Alex Pott is suggesting? The only reason I can think of would be:
.Comment #52
damiankloip CreditAttribution: damiankloip commentedIt does, yes. IMO this is minimal though, for example, it does not break any hal usage. If we completely change priorities we would have to change them everywhere. For the record, I am OK (and would like to do that), I just assumed we might not be allowed to?
Comment #53
Wim LeersRight. I can't answer that, that's up to Alex Pott/core committers. So, back to RTBC.
Comment #54
Wim LeersThis is actually major, because this prevents contrib from defining field normalizers.
And for the same reason, it's also blocking several core issues (see the related issues).
Comment #57
damiankloip CreditAttribution: damiankloip commentedComment #58
Wim LeersWas a random CI fail.
Comment #59
alexpottCommitted 23a9dd8 and pushed to 8.3.x. Thanks!
Comment #61
Wim LeersYay!
This unblocked:
Comment #62
Wim LeersThis blocked #2577923: MenuLinkContent entities pointing to nodes are not deployable: LinkItem should have a "target_uuid" computed property, which needs to be fixed in 8.2.x too. Besides, this is also a bug in 8.2.x, so I think this actually needs to be committed to 8.2.x too. Our bad for not setting the version right.
The committed patch also applies cleanly to 8.2.x.
Comment #65
alexpottI'm not sure that adding methods and services are 8.2.x eligible.
Comment #66
Wim LeersConclusion: we consider both this bug and #2577923: MenuLinkContent entities pointing to nodes are not deployable: LinkItem should have a "target_uuid" computed property as in 8.2.
Comment #70
bradjones1I believe the test on both the interface and bundle key is too much? One can implement fieldable entities without also supporting bundles. See #2866083: EntityNormalizer::denormalize() shouldn't require bundle key for a follow-up.
Comment #71
bbralaAdding linked issue we might remove in #3266544: Remove remaining (textual) references to the HAL module