Problem/Motivation
There are a few existing issues (added as related) dealing with core/json:api module's assumption that entity references are, basically, always "core" entity references which can only target a single entity type. The denormalization performed in the top-level JSON:API normalizer does what amounts to early denormalization of this data and deprives field normalizers (or Extras field enhancers) sufficient information about the reference to accomodate for other entity type targets. E.g., Dynamic Entity Reference needs to set entity_type
in addition to target_id
.
Interestingly, we are dangerously close to actually supporting this, in so far as the target entity type is identifiable by its public json:api name, which is how these entities actually pass initial validation and are loaded up to get their target IDs.
You can post DER data by specifying the target_type
in meta
, however this is unnecessary as it effectively duplicates the type member of the relationship identifier object and requires the client to know about this drupalism (and potentially expose your internals.) See #2888553: Handle denormalizing meta payload in relationships where this pattern was introduced.
Note that there is #3027430: Dynamic Entity Reference normalization support and others which address the normalization/querying side, but AFAICT simple reporting of DER field data "works", and there may be some quirks with querying (particularly as it releates to included data and filtering by relationships) but there are no open issues for the create/update denormalization side of the coin.
Steps to reproduce
Try to post just a relationship with type
and id
to a DER field.
Proposed resolution
Load up what I'm referring to as the "early denormalized" value array for entity references with the entity type; this should be a no-op for the core ER field but will provide DER with what it needs (and any other field doing something similar.)
Remaining tasks
User interface changes
API changes
Data model changes
Release notes snippet
Issue fork drupal-3277553
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #3
bradjones1Comment #4
bradjones1Another reason to change the value set from target_id to entity is that this would allow the entity reference field to validate the entity type (and, if applicable, bundle) of the entity matches the field's settings. At the moment, there's nothing stopping a client from setting an entity of a different type to a field expecting an entirely different entity type. This is because the entity is loaded in the top-level document normalizer, but so long as it's valid, its ID is set unquestionably to target_id. If the same target_id exists on the correct type, you would end up with a totally different entity being referenced. And because entity IDs are serial (as opposed to UUIDs) this is a non-trivial risk of inadvertant data corruption.
Comment #5
ericchew CreditAttribution: ericchew commentedI am building a decoupled app and ran into this issue with DER when trying to POST to an entity with a DER field. The error I received was:
InvalidArgumentException: No entity type was provided, value is not a valid entity. in Drupal\dynamic_entity_reference\Plugin\Field\FieldType\DynamicEntityReferenceItem->setValue()
. I applied the patch and was able to create the entity with no errors.Below is the shape of the POST request I am sending. I have the DER field setup as multi-value but at this time only require referencing one entity, so that is all I have been able to test. I also have not tested PATCH requests.
Comment #6
bradjones1Looks like the test in #2998662: Saving a tagged article in staged workspace causes fatal exception
::testNewEntitiesAllowedInDefaultWorkspace()
was incorrect but passing due to the incorrect type checking when setting entity reference values by passing only theentity
property. Fixing, but it would be nice to get some other eyes on this to ensure the spirit of the test is preserved/correct.Comment #7
bradjones1Comment #8
bradjones1Comment #9
bradjones1@bbrala should also receive credit for sprinting on this during DrupalCon.
Comment #11
larowlanComment #12
bbralaI've reviewed the submitted code. Great improvement imo that led to a fix in entityrelations :)
Comment #13
bbralaericchew should be credited for finding this issue and talking through it with @bradjones1
Comment #14
bradjones1There are some transient errors in Drupal 9.4 HEAD but this is green, now, after poking DrupalCI.
Thank you for the RTBC.
Comment #15
bradjones1It would be great to finish up #3057545: ResourceTypeRepository wrongly assumes that all entity reference fields have the setting "target_type" along with this; the one does not require the other, but particularly when generating (or trying to...) OpenAPI specs for DER fields, we need to continue to improve the core ER field handling to make fewer assumptions about its operation.
Comment #16
larowlanLeft some comments, needs work for a change notice
Comment #17
bradjones1Back to NR. Added two CRs, one as requested for the ER field and another documenting the change in json:api's exception translation. This should provide additional context to your other MR review question.
Also forked off #3279103: Test cleanup: Remove dead code from JsonApiFunctionalTest for the dead test code cleanup.
Comment #18
bradjones1Comment #19
bradjones1I added an additional CR to notify site owners/client implementators that they can drop the unnecessary meta information if the field type supports this. I thought this might be a CR on DER but the change is in core and at least theoretically, this affects other modules with a custom ER field type. I'm only really aware of DER though.
https://www.drupal.org/node/3279367
Comment #20
bradjones1At the risk of delaying or over-complicating this, I think we have an opportunity to make this even more awesome-er.
While testing the entity that brought me to this issue in the first place, I noticed that the response resource relationship object included a
meta
object withentity_type
set. This makes sense, as it's essentially the echo-back of the data that you could then post to Drupal to indicate the required property for extending fields (in this case, DER.)Except, we don't need to do that anymore! Of course, the goal here is to both get out of the way of contrib field types as well as reduce unnecessary Drupalisms in the API surface.
I took a look at the related code, and it turns out json:api was doing a little more work than I think it needs to in order to determine which properties to send in the meta object. Properties can be marked internal, and the normalizer was already taking that into account in filtering properties. So let's mark entity and target_id in the core field as internal, and then modules like DER can do the same with their properties that don't need to be sent to the client, either! A fluent API for the win.
We'll see if this screws up the tests, but I don't think it should?
I think this can be added to the existing CR I made about property values in
meta
.I'll open a corresponding issue in DER to mark entity_type as internal. This would be a BC break, since there may be some clients that require it. I don't think this should be a huge issue though, since to POST new content, you'd already need to know theThat would work fine if json:api were the only API shape out there, but e.g. REST would take theentity_type
out of band, somehow.target_id
andentity_type
properties directly, b/c both are necessary to create the field value.Adding a related issue regarding removing the current exposure of the internal
entity_id
for ER fields. I believe this should be easily opt-out.Thoughts?
Comment #21
bradjones1Well, I realized setting the target ID property as internal was a little over-aggressive; REST, etc. need to actually normalize that. But, I think this is an opportunity for us to better respect the properties' internal flag, and consult that to determine whether we set the
drupal_internal__target_id
value. That allows paranoid site owners like me to override the field item class and tweak the property definitions to set it as internal, and makes that part of the code more fluent. I also think cleaning up the inline comment to reflect this as more field-agnostic is good?If all of this is too much of a sidetrack, we can revert to #17/
f3af2d79
.Comment #22
bradjones1We should also respect the
isInternal()
flag when resolving the field query; e.g., if the data reference property in question; I'm going to hack on this a bit more but I think the code inFieldResolver::resolveInternalEntityQueryPath()
needs some refinement in line with the changes we're making here. This is all rather similar to the work in #3057545: ResourceTypeRepository wrongly assumes that all entity reference fields have the setting "target_type" but I think it should be appropriate here in so far as we are addressing what properties of the ER field json:api is aware of, and how we handle those properties during normalization/denormalization.Comment #23
bradjones1Rebased and added additional validation of known field properties on denormalization. To be extra cautious, if the field name isn't found, I'm just copying as-is for the moment, since we otherwise aren't doing field name validation at that point in the process.
I considered if being able to set read-only or internal properties through meta is a security issue, but I couldn't come up with any real situation that didn't involve myself tied in a knot where this would be feasible given the known universe of ER fields.
Back to NR.
Comment #24
bradjones1Added another draft CR covering the value array property filtering.
Comment #25
bradjones1One final note for the day if reviewers are curious why this additional validation should matter - There are two core field types which extend
EntityReferenceItem
:FileItem
andImageItem
. Both contain additional properties which can be set inmeta
. We have test coverage for this.Honestly, I'd be interested to hear thoughts about tightening this up even further and throwing an exception (unprocessable entity?) if there are additional, unknown properties encountered during filtering. (We could first exclude the
drupal_internal__*
values out, like currently.) I don't necessarily think that well-behaved clients should be sending any unexpected data here. I guess in theory, you could be really wacky and use non-property data in the value for a custom field type, but I don't think it's our responsibility to support that antipattern. This would continue to help the client responses be more meaningful to public clients that do not have understandings of Drupal internals.Comment #26
bbralaFirst off, i've added some comments to the MR, mostly eather clarification I'd like, and a nit.
#25: To be honest, although it sounds interesting to tighten up even further, i have the feeling this might give us more problems down the road. Especially if we are to be a little more liberal in responding with mixed collections those kind of extra validation might result in complications.
We could discuss this more, but i think i would prefer to move that to a separate issue, this is complicated enough already and rather not muddy up this issue more.
#22: My first thought on this was; 'doesnt this mean we are closing off some filtering possibilities we are currently supplieing?', but some manual testing does seem it doesn't. Technically is kinda great to use that flag instead of hardcoding stuff.
Comment #27
bradjones1Thanks for the review; all easy stuff to address. I'll put NW for those nits.
Regarding your two specific points - agreed and I do not believe there's anything actionable on this issue. We can open a separate issue for the field parameter validation if we want. Agreed it's not in scope here.
Comment #28
bradjones1Comment #29
bbralaGreat work Brad!
I'm interested to see what an committer would say in the internal comment. I do see your point, but perhaps I'm overlooking something.
Anyways, let's do this. RTBC.
Comment #31
bradjones1Two failing tests, need to at least re-base and re-test.
Comment #34
Wim Leers#3057545: ResourceTypeRepository wrongly assumes that all entity reference fields have the setting "target_type" landed!