Problem/Motivation
From the JSON:API spec: https://jsonapi.org/format/#document-meta
Where specified, a meta member can be used to include non-standard meta-information. The value of each meta member MUST be an object (a “meta object”).
Any members MAY be specified within meta objects.
Handling of metadata in the JSON:API normalizers already exists.
RelationshipNormalizer
return CacheableNormalization::aggregate([
'data' => $this->serializer->normalize($object->getData(), $format, $context),
'links' => $this->serializer->normalize($object->getLinks(), $format, $context)->omitIfEmpty(),
'meta' => CacheableNormalization::permanent($object->getMeta())->omitIfEmpty(),
]);
ResourceIdentifierNormalizer
if ($object->getMeta()) {
$normalization['meta'] = $this->serializer->normalize($object->getMeta(), $format, $context);
}
The problem is there's no way to add meta information to these objects. See ResourceObjectNormalizer::serializeField
if ($field instanceof EntityReferenceFieldItemListInterface) {
// Build the relationship object based on the entity reference and
// normalize that object instead.
assert(!empty($context['resource_object']) && $context['resource_object'] instanceof ResourceObject);
$resource_object = $context['resource_object'];
$relationship = Relationship::createFromEntityReferenceField($resource_object, $field);
$normalized_field = $this->serializer->normalize($relationship, $format, $context);
}
createFromEntityReferenceField takes a meta and links parameter. But there's no way to add information.
This is useful for building a headless application on Drupal to provide context about the information.
Steps to reproduce
Proposed resolution
Dispatch an event to allow modules to add additional meta to the normalized data
Remaining tasks
- Reviews
Profiling per #61- Done per #82Updates to jsonapi.api.php per #61-@Eventtags added for API docs generation.
User interface changes
API changes
Data model changes
Release notes snippet
| Comment | File | Size | Author |
|---|---|---|---|
| #113 | 3100732-nr-bot.txt | 4.55 KB | needs-review-queue-bot |
| #77 | drupal-issue-3100732-mr-2794.patch | 29.29 KB | jrockowitz |
Issue fork drupal-3100732
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 #2
wim leersComment #3
mglamanI accomplished this with an imposter normalizer and a dispatched event. Whenever a resource object is normalized, it dispatches to collect metadata.
It'd be useful on relationships as well. I can provide a PoC patch based on my custom work.
Comment #4
sam711 commentedComment #5
mglamanHere's a quick and dirty port of my code as a patch. Review coming in as a comment edit once uploaded.
We actually had problems with cache metadata leaks. I believe I discussed with gabesullice and a thought was to make the event a cacheable dependency so subscribers can attach cache metadata to bubble it up.
Specifically how Commerce uses Entity API query access.
This trusts subscribers will get the meta, manipulate it, and then set the proper data.
:/ the ResourceTypeBuildEvent is in ResourceType and not in the events namespace. So there wasn't the normal namespace location.
JSON:API Hypermedia works by running an event dispatcher during normalization. This feels kind of weird, but I don't know if there is a more appropriate place (or canonical place)
Comment #6
xjmComment #7
mglamanUpdated to make the event's
setMetawork like\Drupal\Core\Form\FormState::set.In Commerce API, we also had to add an event for adding meta to relationship objects.
The harder part is trying to provide meta for relationships. It's never directly constructed, only by the
createFromEntityReferenceFieldmethod. WheneverRelationship::createFromEntityReferenceFieldis called, it should be passed the meta from this event. This method is currently called inThe "hard" part is that there isn't a shared interface we can rely on.
\Drupal\jsonapi\JsonApiResource\Relationshipimplements\Drupal\jsonapi\JsonApiResource\TopLevelDataInterfaceand has awareness about meta. But\Drupal\jsonapi\JsonApiResource\ResourceObjectDatawhich also is a top-level object does not.This is at least a next iteration. I'm going to work on the tests, next.
Comment #9
mglaman🤦♂️ fixing the indentation.
Comment #11
mglamanMissed arguments in the services definition.
Comment #13
sanjayk commentedWorking on this ticket will update soon.
Comment #14
sanjayk commented#11 patch not apply with d9 updated code that's why not uploading interdiff.
Comment #17
bbralaChanges look good, i would probably not use
Drupal\jsonapi\Events\JsonapiEventsbutDrupal\jsonapi\Normalizer\MetaDataEvents. But it is fine either way.We do need tests though.
Comment #19
claudiu.cristeaSymfony is moving to remove the event constants in favor of full namespaced event class name. Event constants are creating also other side effects. See #2825358: Event class constants for event names can cause fatal errors when a module is installed (especially, check @catch comment #25). Let's remove them and use the class names instead
Comment #20
plachRerolled and fixed a fatal. This does not take into account #19 as it seems the matter is not settled yet over there.
Comment #21
ranjith_kumar_k_u commentedFixed CS error.
Comment #22
bbralaWell done! We do still need tests though :)
Comment #23
bbralaOk, so I've written some simple tests for this issue and fixed an error in
CollectResourceObjectMetaEventwhere the context wasn't actually set in the object.Now we need a new review though :)
Comment #24
joachim commentedThese need documentation.
Could/should the test also cover adding meta data to only a specific resource?
More generally, this looks like an API. Given the rest of jsonapi module shouts VERY LOUDLY that NONE of it is an API, this new API should document that it IS an API -- on both the events constants class and the event classes, I'd say.
Comment #25
bbralaThanks for having a look! I'll have a look at how we communicate that in the event to see how things are handled there. And we do need documentation yeah. Will get back to this soon.
Comment #26
bbralaRetargeted to 9.5.x (or is that too soon? :x)
I've added the proper docblocks. Also i changed the JsonApiEvents.php file to MetaDataEvents.php since its only metadata events in there. I'll make a related issue in documentation so that this even will be document (as seen here).
The fact its a public api is the fact is isn't @internal and will be documented in the documentation. As with the ResourceTypeBuildEvent. Also I'll write a CR right after posting the patch files.
Comment #27
bbralaI will work on #24.2 also (test a single resource), which is a good test.
Comment #28
bbralaCR was added, expanding the tests i haven't finished yet.
Comment #29
bbralaBack 9.4.x, close to finishing tests. Just need some cachability tests.
Comment #30
bbralaAfter writing some proper test I did end up finding the cache tags do not bubble for the metadata in the relationship. So that still needs some work.
And I'm reminded how much I dislike working with patch files :)
Comment #31
bbralaPatch file was borked. Reuploading. Also forgot my helper testgroup. Also added @joachim credit
Comment #32
bbralaI've been poking around why caching doesn't bubble up, but cant find the reason right now. I've also added a test for the single resource endpoint on the relationship metadata event. I'm gonna walk away for now.
Comment #33
bbralaI found the fault and fixed it yay! The normalization return with the cacheable dependency wasn't saved to the same variable.
The CR I posted earlier I apparently didn't save, added a new one.
Comment #34
joachim commentedI'm not sure how this is passing CI, as there are a number of docs issues which I thought our CI picked up:
This line is too long.
Same here -- and several more.
Unfinished description?
This in the README won't be true any more, so needs changing to say that MOST of the module is internal. Are all classes definitely marked @internal -- if there are some that aren't, removing this blanket warning will make them look non-internal!
> * The JSON:API module provides *no PHP API to modify its behavior.* It is
> * designed to have zero configuration.
Comment #37
bbralaI've checked for @internal flag with grep. The only classes (outside test classes) not @internal:
That looks good i think.
Regarding codestyle, I always run the codestyle check that is provided in core, which apperantly does not check for line length. I've fixed those.
The unfinished description is technically what it returns. But i've updated it.
Regarding the internal api comment, you are entirely right. But I'd like to do that in a follow up. There are already some public API surfaces, so we are not introducing a new issue here. I will make the follow up issue.
Comment #38
bbralaComment #39
bbralaAww testbot... Will update when i see green tests
Comment #40
bbralaI think a adressed all feedback. Setting back to nr.
Comment #41
bbralaDocumentation issue with written documentation: #3281597: Update "Customizing Resources". Thought i'd wait for this to land to put it in the docs. :)
Comment #42
nod_Very related to #3186628: (use case #2) Decide on an API response format for menu data
Adding a ref to #3227824: Move the linkset functionality from the decoupled menus contributed module to core's system module just to keep track, not a prerequisite.
Comment #43
bbralaYes and no. In a way this might be helpfull, but I would you would want something in the form of a hyperlink in the data, not extra data in the metadata.
Also; looking for a review ^^
Comment #44
bbralaWhile reviewing #3257608: Allow opt-out of automatic meta.drupal_internal__target_id on entity relationships i went though this issue also and found a flaw. I actually found that this MR appearantly doesn't handle the /jsonapi/node/article/id/relationships/field_tags kind of endpoint, so it only does so for relationships on the resource itself. Setting back to needs work.
Note to self;
EntityResource::getRelationShidoes fire the event, but doesn't ad the meta toRelationship::createFromEntityReferenceField. We cannot just add it there, since the actual endpoint wraps the data there resulting in a response like this:That won't do, we need to find a way to do this a little different there.
Comment #45
bbralaI've looked into this a little more. The result is expected, this is exactly how the relationship is renderen in the resource also. This makes sense. Double checked the spec, and all good. Added a test for the relationship endpoint that is renders correctly.
I was worries a bit more :)
Comment #46
bbralaRebased onto 10.1.x, lets see if things become green again.
Comment #47
bbralaBecause reasons. My rebase was screwed over and decided to cherrypick to a fresh 10.1.x branch.
MR !2794is the correct one. Let's see if tests are still green.Comment #48
bbralaHopefully it will show up at some point.
Link to mr
Comment #51
wim leersPosted a first review round!
I think the change record should be expanded with a concrete example, to make it clear what kinds of problems you can solve with this 🤓
Comment #53
ravi.shankar commentedRebased the current MR as it was not mergeable, still needs work for feedback posted on MR
Comment #54
bbralaI've tried to go through the feedback. I agree on a good example as mentioned in #51. Still setting to NR though ^^
Comment #55
bbrala@ravi.shankar your rebase was a bit of a mess. Please don't do random merges as rebase. That is not a rebase. Please have a look here.
I rebased, and fixed the comments made.
Regarding #51. The example in the tests adds some specific entity data to the request. I kinda feel that is a pretty good way to understand how this works. This information could be anything related to the entity that might not be a field.
A really concrete example will probably be something related to commerce. Lets see if i can get @mglaman to give us one.
Comment #56
mglamanExample: In Commerce API we needed to attach meta information that wasn't part of the entity data model. The example I am thinking of is that Klarna generates a signed HTML embed for an iframe to do checkouts. Once an order was ready to be checked-out (selected Klarna, had enough info) we were able to attach the HTML for the iframe via a meta on the relationship to the selected payment relationship on the order (or the order itself, I can't remember, it's been a few years.)
I am also pretty certain once usage was to attach stock levels on a Product's relationship JSON:API objects to its variations. So we had id, data, meta.in_stock, meta.sock_level.
Comment #57
mglamanA key takeway on the above: It involves data which isn't part of the direct data model but is important to it. It could be ephemeral (payment instrument data for client) or related (stock on relation without needing full entity.)
Comment #58
nod_MR needs to be updated to be mergeable
Comment #59
bbralaRebased as asked for by @_nod, even though it kinda felt unneeded. I'll have a look at the CR.
Comment #60
bbralaAdded a proper example to the CR based on the post of mglaman. :)
Comment #61
wim leersI think this is looking great, but to ensure this does not cause unexpected side effects in performance or existing caching infrastructure, I think some more work is needed 🤓
jsonapi.api.php, tagging for that.metathat varies across request context (different user/different role/different language/whatever).Comment #62
bbralaComment #63
bbralaOk, i'm mentally getting there after a few different tries. Might just do a cache context of a user for something like the author of the entity. This could work. Caching is always a fun area to explore. Think I could loose a week in that subsystem :x
Comment #64
bbralaAlso, I've profiled with blackfire and on a site with a silly amount of relations (resulting in 16k events being despatched) it added 1k extra. Which was milliseconds.
When I get a little farther on this in regards to the caching and such I will do a new profile run. But it seems the event dispatching is pretty free which is to be expected since it is pretty much a few checks if a key is set in an array (which php does pretty fast nowadays ;)).
Comment #65
Gunjan Rao Naik commentedAdded patch against #33 in 10.1.x
Comment #66
bradjones1@Gunjan, thanks for the effort at helping, however this seems like an on-sequitur and not a constructive contribution. The work on this issue is being done in an MR, and re-rolling something from 30+ comments ago isn't particularly relevant. Can you help us understand what you're trying to achieve with the patch file you uploaded? Thanks.
Comment #68
bbralaI've done some updates and improved the testing. Hopefully it is enough like this.
Mostly a question open if we should return CacheableNormalisation in the events 'getMeta' method.
Another possible open question is if the current context testing is enough or we need a more complex case.
Comment #69
bbralaComment #70
bbralaAlso from #61:
Change record looks great — just added formatting tweaks.I'm missing test coverage for meta that varies across request context (different user/different role/different language/whatever).This MR introduces event dispatching in various performance-critical pieces of JSON:API. I'm fine with that costing something for sites that choose to implement this (although I would like to see numbers). But did we measure that there's no significant overhead for the >90% of sites that don't use this?) few ms, see #64Comment #71
bbralaAdded Wim for reviewing
Comment #72
larowlanRemoved patches as there's an MR
Adding issue credits, and removing credits for simple re-rolls/rebases
Updating issue summary and tags
Makes the job of reviewers easier ❤️
Comment #73
larowlanLeft a review
Comment #74
larowlanAdded #3375453: [policy, no patch] Decide on policy regarding use of Events class with string constants for event names or class names instead for discussion
Comment #75
wim leersTrying to get this moving forward again 😄
Merged in
11.x, addressedphpcsviolations, left a review, and … there's still @larowlan's review to address.(Trying to only touch trivial things to make things easier but keep myself distant enough to be able to RTBC this!)
Comment #76
wim leersThe tests now confirm the failure reported by @ptmkenney on the MR 👍 The failure also happens in kernel tests, which means it should be an easy fix 😄
Comment #77
jrockowitz commentedSorry to add a little noise to this ticket. I wanted to create a patch to test this via composer patches.
Comment #78
bbralaOk, i've tried to adjust to the comments on the MR. Only not sure about my context test, think it tests the context switch, but perhaps its not nested enough?
Rest seems to be adressed now, or at least explained ;)
Comment #79
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".
This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #80
ptmkenny commentedThe change record draft needs to be updated because MetaDataEvents was removed. I would update it myself but I don't understand all the new changes.
Comment #81
bbralaUpdated the changerecord to reflect the removal of the MetaDataEvents class. Fixed codestyle issues.
I'll wait for ci ;)
Comment #82
bbralaFixed spellcheck by rebasing (wonder if we should automate that...), tests are running.
Possible todos (from #61):
As per #64, it adds about 1k extra events (on 16k currently) and adds a few ms uncached, which i would say is fine. Forgot to remove the tag there, sorry.
Comment #83
bbralaTests failed, needs work.
Comment #84
bbralaFixed event dispatching, also fixed the fact the code tried to add cachability data to non-cachable reponses. Added an extra check in the testing module so we get not warning on a non existant array key.
Ready for review.
Comment #85
ptmkenny commentedI don't understand enough about the internals of JSON:API to do a code review, but I can do an end user review because I have a mobile app that uses JSON:API as a backend. I checked out the latest version of this MR and ran my app's automated tests; everything passed. I then poked around in the app for a few minutes and the metadata is added correctly as I expected. I use the meta info to return lots of extra details about user entities, so this is critical functionality for my app.
Just one comment on the change record. I would suggest adding comments to the functions as per the coding standards so that it's more clear that addPaymentInfoToResourceObjectMeta() is an arbitrary name for a custom method, whereas getSubscribedEvents() is the event subscriber method.
Something like:
Comment #86
bbralaThank you, i've updated the changerecord. And thank you for the real world testing :)
Comment #87
smustgrave commentedSorry to do it, but can we have two MRs now
10.3.x with the deprecation
11.x with the items removed or required
Thanks.
Comment #90
bradjones1Bjorn's MR will need its target changed but added one against 11.x that can stay that way, with deprecations removed.
Comment #91
bradjones1Updating IS covering last "remaining tasks." Profiling was cleared up in #82 and I think it's enough to add @Event to the new events for API docs generation.
By way of cross-promotion, API docs are really hard to generate and maintain. Meta for better docs at #3441158: [Meta, Plan] Move official Drupal docs into repository, host from static site CI artifact.
Comment #92
bbralaDid the dance, unfortunately a true rebase onto the other branch did not work, so force pushed the diff. Setting to NR, pipeline is pending.
Comment #95
smustgrave commentedNot sure if something in gitlab got stuck? But seeing a 10.3.x test failure in jsonApi
Comment #96
bradjones1I dunno about stuck but it looked like it could be a random, but just retried and same outcome. https://git.drupalcode.org/issue/drupal-3100732/-/pipelines/159676/test_...
I'll try to take a look today to help keep this moving.
Comment #97
bbralaThere was an issue with user login which broken 10.3 head. Which after also has a few other things fail. It might be something there. Too tired to check today though.
Comment #98
bbralaI've fixed the pipeline by using
drupalResetSessionin the test, which logs out the user through code.It is kinda weird though, that loggin out didn't work through the interface, but that is isolated from this issue.
Comment #99
bbralaDid a quick fix for the logOut issue bases on the changes here: #144538: User logout is vulnerable to CSRF. There where changes in how we logout, we need those changes in this test also.
I tried removing the mink session reset, but that was needed, so put it back in.
Comment #100
smustgrave commentedThanks for fixing! Left some small comments looking good!
Comment #101
bbrala11.x is updated, ill push the same changes to the 10.3 mr (excluding the BC removal)
Comment #102
bbralaUpdate 10.3 mr also, all done <3
Comment #103
smustgrave commentedFeedback appears to be addressed
Reran the kernel test and it was random failure.
Comment #104
larowlanHi Folks, unfortunately we've missed the window for 11.0.x and 10.3.x now.
I think we need to re-do this 10.4.x as the 'from' in the deprecation notice and 12.0.x for removal.
The only consolation is at least then there's only one MR.
Thanks for all the effort here.
Comment #106
bbralaSimple rebase, nothing changed but the deprecation message. Updated the CR and target for MR. Think those are small anough changes for a direct RTBC
Comment #107
quietone commentedI read the issue summary and the MR. I skimmed the comments and didn't see any unanswered questions, although like I said I only skimmed (It is noisy here and hard to concentrate).
I left comments in the MR about the comments. I also think that this should not use $rootUser. I did not do a code review.
Setting to NW.
Comment #108
quietone commentedComment #109
bbralaResolved all feedback.
All good I think :)
Comment #110
smustgrave commentedCan the MR 7828 be updated for 11.x please
Comment #111
bbralaRebased onto 11.x and fixed a small issue with a missing return type.
Comment #112
smustgrave commentedMR for 11.x is showing as all green
All threads appear to have been addressed
CR reads well to me and even better has examples (love those)
Running the test-only feature gets https://git.drupalcode.org/issue/drupal-3100732/-/jobs/2141544 showing the coverage
Code review wise nothing seems to be off that hasn't been fixed already
Deprecation message appears correct.
Everything appears to be typehinted.
Believe this one is good to go.
Comment #113
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".
This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #114
ptmkenny commentedIn #113, needs-review-queue-bot only reported a missing strict types declaration. I added this and am setting back to RTBC since it is an extremely minor change and all tests are passing.
Comment #115
bradjones1Rebasing.
Comment #116
jonathan_hunt commentedTypo at L43
core/modules/jsonapi/tests/modules/jsonapi_test_meta_events/src/EventSubscriber/MetaEventSubscriber.php, also L87, "// Only continue if the recourse type is enabled.", presumablyrecourseshould beresource.https://git.drupalcode.org/project/drupal/-/merge_requests/7828/diffs#3e...
Comment #117
larowlanComment #118
ptmkenny commentedFixed the spelling mistake identified by @jonathan_hunt in #116. Since this was just a single word, I don't think we need to change the RTBC status.
Comment #120
larowlanCommitted to 11.x - great work folks
Published the CR
Comment #123
xjmAmending attribution.