Normalizers should be as straightforward and simple as possible.

Directly accessing the request object shouldn't be a part of their responsibilities as it is here:

  /**
   * Expand the context information based on the current request context.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The request to get the URL params from to expand the context.
   * @param \Drupal\jsonapi\ResourceType\ResourceType $resource_type
   *   The resource type to translate to internal fields.
   *
   * @return array
   *   The expanded context.
   */
  protected function expandContext(Request $request, ResourceType $resource_type) {
    // Translate ALL the includes from the public field names to the internal.
    $includes = array_filter(explode(',', $request->query->get('include')));
    // The primary resource type for 'related' routes is different than the
    // primary resource type of individual and relationship routes and is
    // determined by the relationship field name.
    $related = $request->get('_on_relationship') ? FALSE : $request->get('related');
    $public_includes = array_map(function ($include) use ($resource_type, $related) {
      $trimmed = trim($include);
      // If the request is a related route, prefix the path with the related
      // field name so that the path can be resolved from the base resource
      // type. Then, remove it after the path is resolved.
      $path_parts = explode('.', $related ? "{$related}.{$trimmed}" : $trimmed);
      return array_map(function ($resolved) use ($related) {
        return implode('.', $related ? array_slice($resolved, 1) : $resolved);
      }, FieldResolver::resolveInternalIncludePath($resource_type, $path_parts));
    }, $includes);
    // Flatten the resolved possible include paths.
    $public_includes = array_reduce($public_includes, 'array_merge', []);
    // Build the expanded context.
    $context = [
      'account' => NULL,
      'sparse_fieldset' => NULL,
      'resource_type' => NULL,
      'include' => $public_includes,
      'expanded' => TRUE,
    ];
    if ($request->query->get('fields')) {
      $context['sparse_fieldset'] = array_map(function ($item) {
        return explode(',', $item);
      }, $request->query->get('fields'));
    }

    return $context;
  }

IOW, we should be "expanding" that context prior to (de)normalization.

CommentFileSizeAuthor
#59 2984911-59.patch41.2 KBgabesullice
#59 interdiff.txt2.25 KBgabesullice
#57 2984911-57.patch41.31 KBgabesullice
#57 interdiff.txt1.89 KBgabesullice
#55 2984911-55.patch41.32 KBgabesullice
#55 interdiff.txt2.97 KBgabesullice
#54 2984911-54.patch41.66 KBgabesullice
#54 interdiff-enum-and-assertions.txt7.75 KBgabesullice
#54 intediff-clean-up.txt1.45 KBgabesullice
#45 2984911-45.patch39.6 KBwim leers
#45 interdiff.txt2.32 KBwim leers
#45 interdiff-rebase.txt21.18 KBwim leers
#41 2984911-41.patch34.64 KBwim leers
#41 interdiff.txt7.88 KBwim leers
#39 2984911-39.patch34.84 KBwim leers
#39 interdiff.txt4.34 KBwim leers
#37 2984911-37.patch32.15 KBwim leers
#37 interdiff-32-37.txt6.96 KBwim leers
#37 interdiff.txt7.62 KBwim leers
#34 interdiff-34.txt2.91 KBgabesullice
#34 2984911-34.patch31.36 KBgabesullice
#32 2984911-32.patch31.07 KBwim leers
#32 interdiff.txt5.63 KBwim leers
#27 2984911-27.patch30.5 KBwim leers
#27 interdiff.txt1.75 KBwim leers
#23 2984911-22.patch28.59 KBwim leers
#23 interdiff.txt1.75 KBwim leers
#21 2984911-21.patch28.58 KBwim leers
#21 interdiff.txt2.03 KBwim leers
#20 2984911-19.patch27.48 KBwim leers
#20 interdiff.txt1.74 KBwim leers
#18 2984911-18.patch27.35 KBwim leers
#15 2984911-15.patch27.33 KBwim leers
#15 interdiff.txt19.1 KBwim leers
#12 2984911-12.patch13.45 KBwim leers
#12 interdiff-8-12.txt7.57 KBwim leers
#11 interdiff.txt854 byteswim leers
#9 2984911-9.patch8.54 KBwim leers
#9 interdiff.txt853 byteswim leers
#8 2984911-8.patch8.48 KBwim leers
#8 interdiff.txt4.93 KBwim leers
#6 2984911-6.patch3.61 KBwim leers
#6 interdiff.txt672 byteswim leers
#2 2984911-2.patch2.97 KBwim leers

Comments

gabesullice created an issue. See original summary.

wim leers’s picture

Status: Active » Needs review
StatusFileSize
new2.97 KB

IOW, we should be "expanding" that context prior to (de)normalization.

Agreed.

Super hacky first attempt to see what breaks. I won't be working on this anymore today, so feel free to continue this!

wim leers’s picture

+++ b/src/Normalizer/JsonApiDocumentTopLevelNormalizer.php
@@ -217,7 +214,7 @@ class JsonApiDocumentTopLevelNormalizer extends NormalizerBase implements Denorm
-  protected function expandContext(Request $request, ResourceType $resource_type) {
+  public static function expandContext(Request $request, ResourceType $resource_type) {

I'm of course not proposing to keep the logic here and keep it as a public method … that's just to make the patch as small as possible in a first attempt!

Status: Needs review » Needs work

The last submitted patch, 2: 2984911-2.patch, failed testing. View results

wim leers’s picture

Most important problem:

Undefined index: account
wim leers’s picture

Assigned: Unassigned » wim leers
Status: Needs work » Needs review
StatusFileSize
new672 bytes
new3.61 KB

Status: Needs review » Needs work

The last submitted patch, 6: 2984911-6.patch, failed testing. View results

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new4.93 KB
new8.48 KB

This is the problem with kernel tests.that are sort of integration tests but should be unit tests.

wim leers’s picture

StatusFileSize
new853 bytes
new8.54 KB

Green!

Now let's address this:

Directly accessing the request object shouldn't

Status: Needs review » Needs work

The last submitted patch, 9: 2984911-9.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

wim leers’s picture

StatusFileSize
new854 bytes

98 fails.

Because

// Add the self link.
    if ($this->context['request']) {
      /* @var \Symfony\Component\HttpFoundation\Request $request */
      $request = $this->context['request'];
      $rasterized['links'] += [
        'self' => $this->linkManager->getRequestLink($request),
      ];
      // If this is a collection we need to append the pager data.
      if ($this->isCollection) {
        // Add the pager links.
        $rasterized['links'] += $this->linkManager->getPagerLinks($request, $this->linkContext);

in \Drupal\jsonapi\Normalizer\Value\JsonApiDocumentTopLevelNormalizerValue::rasterizeValue().

Thoughts?

For now, undoing #9. (i.e. going back to #8)

wim leers’s picture

Assigned: wim leers » Unassigned
Status: Needs work » Needs review
StatusFileSize
new7.57 KB
new13.45 KB

Meanwhile, moved the context generation logic from JsonApiDocumentTopLevelNormalizer to \Drupal\jsonapi\EventSubscriber\ResourceResponseSubscriber::generateContext().

This now needs feedback.

gabesullice’s picture

Status: Needs review » Needs work
  1. +++ b/src/EventSubscriber/ResourceResponseSubscriber.php
    @@ -111,10 +113,7 @@ class ResourceResponseSubscriber implements EventSubscriberInterface {
           // First normalize the data.
    ...
    -      ] + JsonApiDocumentTopLevelNormalizer::expandContext($request, $request->get('resource_type')));
    +      $jsonapi_doc_object = $serializer->normalize($data, $format, static::generateContext($request));
    

    😍

  2. +++ b/src/EventSubscriber/ResourceResponseSubscriber.php
    @@ -127,6 +126,53 @@ class ResourceResponseSubscriber implements EventSubscriberInterface {
    +   * Generates the context to pass to normalizers.
    

    "Generates a top-level JSON API normalization context."

  3. +++ b/src/EventSubscriber/ResourceResponseSubscriber.php
    @@ -127,6 +126,53 @@ class ResourceResponseSubscriber implements EventSubscriberInterface {
    +   *   The request to get the URL params from to expand the context.
    

    "The request from which the context can be derived."

  4. +++ b/src/EventSubscriber/ResourceResponseSubscriber.php
    @@ -127,6 +126,53 @@ class ResourceResponseSubscriber implements EventSubscriberInterface {
    +      'request' => $request,
    

    I don't think we can call this done until this is removed. Ofc, that's what you're asking about in #11... see below.


[Problems] in \Drupal\jsonapi\Normalizer\Value\JsonApiDocumentTopLevelNormalizerValue::rasterizeValue().

Thoughts?

I think this is just the kind of information that should be carried by the JsonApiDocumentTopLevel object. I'd add a $links argument to its constructor which receives an array of Url objects keyed by link relation type.

In that constructor: assert(empty(array_diff(array_keys[$links], ['self', 'first', 'next', 'prev', 'last', 'related'])))

Then, do all that work with the link manager in the controller rather than in the normalization process.

This will make it easier to capture that pesky cache data too!

wim leers’s picture

Assigned: Unassigned » wim leers
  1. :)
  2. ✔️
  3. ✔️
  4. RE: your thoughts. Makes sense. On it 👍
wim leers’s picture

Assigned: wim leers » Unassigned
Status: Needs work » Needs review
StatusFileSize
new19.1 KB
new27.33 KB

This is not 100% done, but does handle the majority of the structural changes. Looking for a review of those changes.

Not yet done:

  1. Lots of code style/docs nits.
  2. Transition from string to Url was not made yet; that requires extensive changes to LinkManager.

Status: Needs review » Needs work

The last submitted patch, 15: 2984911-15.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

gabesullice’s picture

Initial review below. I made a few nits that I know you said were not yet done, but /shrug :P

Overall, I love the direction of this patch!

that requires extensive changes to LinkManager.

What kind of changes? Perhaps we should make a prerequisite issue for it? My dream would to see LinkManager simply removed, or made into an internal class with public static helper methods, or a really solid API for adding custom links to various levels of the document introduced. Or some combination of those things.


  1. +++ b/src/Controller/EntityResource.php
    @@ -801,12 +801,15 @@ class EntityResource {
    +   * @param \Drupal\Core\Url[]|string[] $links
    +   *   The URLs to which to link. A 'self' link is added automatically.
    ...
    +    $links['self'] = $this->linkManager->getRequestLink(\Drupal::request());
    

    I'd like to see the self link be explicit, unless that would explode the scope of this patch.

  2. +++ b/src/Controller/EntityResource.php
    @@ -821,7 +824,14 @@ class EntityResource {
    +        ? $entity_collection->getTotalCount()
    
    +++ b/src/EventSubscriber/ResourceResponseSubscriber.php
    @@ -110,10 +113,7 @@ class ResourceResponseSubscriber implements EventSubscriberInterface {
    -      $jsonapi_doc_object = $serializer->normalize($data, $format, [
    -        'request' => $request,
    -        'resource_type' => $request->get('resource_type'),
    -      ]);
    
    @@ -126,6 +126,52 @@ class ResourceResponseSubscriber implements EventSubscriberInterface {
    +    $context = [
    +      'account' => NULL,
    +      'sparse_fieldset' => NULL,
    +      'resource_type' => $resource_type,
    +      'include' => $public_includes,
    +    ];
    

    This shows that the request is no longer added part of the normalization process.

  3. +++ b/src/Controller/EntityResource.php
    @@ -821,7 +824,14 @@ class EntityResource {
    +    $collection_links = $this->linkManager->getPagerLinks(\Drupal::request(), $link_context);
    

    Nit: I would call these "pagination_links"

    I like that this extracts the link manager from the normalization process in at least one case. It always felt a little like the old CurrentContext class, in that it weaves its way through everything.

  4. +++ b/src/JsonApiResource/JsonApiDocumentTopLevel.php
    @@ -22,14 +25,27 @@ class JsonApiDocumentTopLevel {
    +   * @param \Drupal\Core\Url[]|string[] $links
    +   *   The URLs to which to link.
    

    "The URLs to which the top-level document should link."

  5. +++ b/src/JsonApiResource/JsonApiDocumentTopLevel.php
    @@ -22,14 +25,27 @@ class JsonApiDocumentTopLevel {
    +    assert(empty(array_diff(array_keys($links), ['self', 'first', 'next', 'prev', 'last', 'related'])));
    

    Perhaps we should document the allowed link relations in the docblock.

  6. +++ b/src/JsonApiResource/JsonApiDocumentTopLevel.php
    @@ -42,4 +58,14 @@ class JsonApiDocumentTopLevel {
    +   *   The document-level links.
    

    s/document/top

  7. +++ b/src/Normalizer/RelationshipItemNormalizer.php
    @@ -60,7 +60,7 @@ class RelationshipItemNormalizer extends FieldItemNormalizer {
           $entity_and_access = EntityResource::getEntityAndAccess($target_entity);
    -      $included_normalizer_value = $this->serializer->normalize(new JsonApiDocumentTopLevel($entity_and_access['entity']), $format, $context);
    +      $included_normalizer_value = $this->serializer->normalize(new JsonApiDocumentTopLevel($entity_and_access['entity'], []), $format, $context);
    

    This will need to be adjusted to account for #2987206: Refactor `getEntityAndAccess` to return cacheable objects with access cacheability rather than an entity/access pair.

  8. +++ b/src/Normalizer/Value/JsonApiDocumentTopLevelNormalizerValue.php
    @@ -24,18 +26,16 @@ class JsonApiDocumentTopLevelNormalizerValue implements ValueExtractorInterface,
    -   * The includes.
    -   *
    -   * @var array
    +   * @var array|\Drupal\Core\Url[]|string[]
    

    Missing a comment.

  9. +++ b/src/Normalizer/Value/JsonApiDocumentTopLevelNormalizerValue.php
    @@ -45,34 +45,17 @@ class JsonApiDocumentTopLevelNormalizerValue implements ValueExtractorInterface,
    -  public function __construct(array $values, array $context, array $link_context, $is_collection = FALSE) {
    +  public function __construct(array $values, array $links, $is_collection = FALSE) {
    

    This is a much nicer signature for a value object.

wim leers’s picture

Status: Needs review » Needs work

The last submitted patch, 18: 2984911-18.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new1.74 KB
new27.48 KB

Overall, I love the direction of this patch!

🎉

that requires extensive changes to LinkManager.

What kind of changes? Perhaps we should make a prerequisite issue for it? My dream would to see LinkManager simply removed, or made into an internal class with public static helper methods, or a really solid API for adding custom links to various levels of the document introduced. Or some combination of those things.

LinkManager is already an @internal class. It totally should be. Let's mark it @internal here?

Those extensive changes are: A) change the return signature of every single method on LinkManager, B) make LinkManager::getRequestLink() no longer just return $request->getUri() if there is no query.

  1. I'm not sure what you mean by this. Do you mean that each controller method should generate its own self link?
  2. Yep! 🎉
  3. We're dealing with a collection; these are links for the collection. Right now, there's only pager/pagination links, but there might be more in the future. (For example: #2857730: Add a link to get only published entities in collections.) Agreed with your "weaving" observation :)
  4. ✔️
  5. This is just an internal API. Plus this is fairly probable to change. So I think it's fine to keep this as just an assertion for now — that's effectively executable documentation anyway.
  6. ✔️
  7. Yep! 🎉
wim leers’s picture

StatusFileSize
new2.03 KB
new28.58 KB

I missed a spot where #2987603: Make resource type an explicit parameter to each EntityResource method forces me to make changes here.

The last submitted patch, 20: 2984911-19.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

wim leers’s picture

StatusFileSize
new1.75 KB
new28.59 KB

Nit: retain/restore a rename that #2977669: Spec Compliance: some entity types have an "id", "type" or "uuid" field, this violates the spec made after this patch already existed.

The last submitted patch, 21: 2984911-21.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

Status: Needs review » Needs work

The last submitted patch, 23: 2984911-22.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

gabesullice’s picture

Let's mark it @internal here?

That's a first step in the right direction :)

What I was trying to say is I don't think it the LinkManager should have its own state, i.e. if every method is public static, its methods exist only to avoid code duplication. IOW, this would ensure that it's no longer a dependency woven through everything. It would become a simple set of reusable functions. I don't think that should happen here.

OTOH, I've also it at the back of my mind that JSON API needs a public API for customizable HATEOAS links. My last remark and this need are related, but not compatible. I just want it to be on your mind too.

1. I'm not sure what you mean by this. Do you mean that each controller method should generate its own self link?

Exactly. In general, I'd like to see links explicitly passed to JsonApiDocumentTopLevel and to each value object that has links, rather than having value objects generating their own links.

3 & 5. WFM!

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new1.75 KB
new30.5 KB

This fixes all but one failure. That last failure is tricky: it's the meta.count that is missing. It used to be handled in \Drupal\jsonapi\Normalizer\Value\JsonApiDocumentTopLevelNormalizerValue::rasterizeValue(). I accidentally removed it (yay for tests!), but … to be able to do that, we'd either need to:

  1. again pass $context['total_count'], which is exactly what we don't want
  2. or pass the ResourceType to the JsonApiDocumentTopLevelNormalizerValue constructor, so that we can do $collection->getTotalCount(), but that'd mean we're gathering the count in two places: once in JsonApiDocumentTopLevelNormalizerValue::rasterizeValue() during rasterization, plus once in \Drupal\jsonapi\Controller\EntityResource::respondWithCollection() during link generation.

I don't like either.

Ideas?

wim leers’s picture

#26:

  • WRT "no state": that'd be nice. But I don't think that's a must-have here. That's a different scope than this issue's title.
  • WRT customizable HATOEAS links: that'd be nice too. But definitely a future feature request, which we can completely ignore in this issue.
  • WRT location of link generation: I think you're slightly contradicting yourself? I asked about controller methods, you say "yes, value objects should do this". But controller methods explicitly are NOT value objects. In fact, JsonApiDocumentTopLevelNormalizerValue used to generate its own self link, and that now lives in the controller … based on what you said here, that'd be the opposite of what you want?
    It seems impossible to combine the goal of this issue ("normalizers unaware of Request object") with the intent to have normalizer value objects generate their own links? Or what am I missing?

Status: Needs review » Needs work

The last submitted patch, 27: 2984911-27.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

gabesullice’s picture

It seems impossible to combine the goal of this issue ("normalizers unaware of Request object") with the intent to have normalizer value objects generate their own links? Or what am I missing?

I think you understood the reverse of what I wrote. I do not want any value objects to generate their own links (or call the LinkManager).

Will respond to #27 soon. I'll have to stare at it a bit.

wim leers’s picture

I think you understood the reverse of what I wrote. I do not want any value objects to generate their own links (or call the LinkManager).

You're right, oops 😇😁😭

Re-reading isn't enough for me, I apparently need re-re-reading. 👴

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new5.63 KB
new31.07 KB

Fixed CS violations.

Status: Needs review » Needs work

The last submitted patch, 32: 2984911-32.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

gabesullice’s picture

Status: Needs work » Needs review
StatusFileSize
new31.36 KB
new2.91 KB

Re: #27

What about something like this? Can't it be an explicit argument to the value object? Am I missing something?

wim leers’s picture

It totally can. But it's not very elegant, is it?

Then I'd much rather have not $total_count = FALSE in the constructor, but $meta = [], which we'd deep-merge. That'd allow any controller to pass additional meta-stuff; rather than it being specific for collection counts. Might be useful in the future. Is less tightly coupled.

gabesullice’s picture

It's not very elegant, but it's an improvement :P

$meta ... in #35 is a good suggestion.

wim leers’s picture

StatusFileSize
new7.62 KB
new6.96 KB
new32.15 KB

So what about this?

gabesullice’s picture

Status: Needs review » Needs work

Couple nits, and a q. After that, RTBC I think.

  1. +++ b/src/Controller/EntityResource.php
    @@ -792,12 +792,17 @@ class EntityResource {
    +    $links['self'] = $this->linkManager->getRequestLink(\Drupal::request());
    

    Why not inject the request?

  2. +++ b/src/Controller/EntityResource.php
    @@ -805,18 +810,28 @@ class EntityResource {
    +      'total_count' => $resource_type->includeCount()
    +        ? $entity_collection->getTotalCount()
    +        : FALSE,
    

    Nit: Can we just add this to the array conditionally, rather than overloading total_count?

  3. +++ b/src/Controller/EntityResource.php
    @@ -805,18 +810,28 @@ class EntityResource {
    +    $meta = $link_context['total_count'] === FALSE
    

    Nit: let's use $resource_type->includeCount() instead of checking for FALSE.

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new4.34 KB
new34.84 KB
  1. ✔️ No reason; just didn't get to that yet! Done.
  2. ✔️
  3. ✔️

Status: Needs review » Needs work

The last submitted patch, 39: 2984911-39.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new7.88 KB
new34.64 KB

I think you actually meant this in #38.1.

gabesullice’s picture

Status: Needs review » Reviewed & tested by the community

Awesome :)

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 41: 2984911-41.patch, failed testing. View results

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new21.18 KB
new2.32 KB
new39.6 KB

Rebased.

Exciting: the two lengthy @todos in JsonApiDocumentTopLevel are now condensed to a single much shorter one!

Tricky: #2949807: Spec Compliance: Error responses are missing the `jsonapi` top-level member started using JsonApiDocumentTopLevelNormalizerValue's $context parameter to indicate whether this was for an "errors document" or not … but the patch here was removing the $context parameter! For now, brought it back. Until #2965056: Support `include` parameter on relationship routes (turns out it accidentally works on cold caches: fix + test this) lands, this will be necessary. Actually, it's worse … because the whole point of this issue is to remove access to the Request object during normalization. But #2991389: Test coverage: relationship response cacheabiliity made that impossible :( At least until #2965056: Support `include` parameter on relationship routes (turns out it accidentally works on cold caches: fix + test this) lands. I worked around this by replacing $context['request'] with \Drupal::requestStack()->getCurrentRequest().

This was one super difficult rebase 😵 Really hoping this passes tests! 🤞

gabesullice’s picture

+++ b/src/Controller/EntityResource.php
+++ b/src/Controller/EntityResource.php
      // though the normalizer skips disabled references, we can avoid unnecessary

+++ b/src/Normalizer/Value/JsonApiDocumentTopLevelNormalizerValue.php
+++ b/src/Normalizer/Value/JsonApiDocumentTopLevelNormalizerValue.php
 -    // Flatten the includes.
...
-       if (!$context['request']->get('_on_relationship')) {
++      if (!\Drupal::requestStack()->getCurrentRequest()->get('_on_relationship')) {

This was my fault. Can't we just add a $context['on_relationship'] === TRUE in generateContext? No, because then we'd have to add the $context param back.

wim leers’s picture

Assigned: wim leers » Unassigned

#46: exactly! That's what I first tried to do, but then I realized this was better/more elegant, since it only exists temporarily anyway. See the <del> part of #45.

Also: HOLY SHIT this is green from the first time!!!!!!! 🎉🎉🎉🎉🎉

gabesullice’s picture

+++ b/src/Normalizer/Value/JsonApiDocumentTopLevelNormalizerValue.php
@@ -55,18 +57,11 @@ class JsonApiDocumentTopLevelNormalizerValue implements ValueExtractorInterface,
-   *

@@ -74,25 +69,25 @@ class JsonApiDocumentTopLevelNormalizerValue implements ValueExtractorInterface,
+      if (!\Drupal::requestStack()->getCurrentRequest()->get('_on_relationship')) {
         // Make sure that different sparse fieldsets are cached differently.
         $this->addCacheContexts(array_map(function ($query_parameter_name) {
           return sprintf('url.query_args:%s', $query_parameter_name);
@@ -100,13 +95,24 @@ class JsonApiDocumentTopLevelNormalizerValue implements ValueExtractorInterface,

@@ -100,13 +95,24 @@ class JsonApiDocumentTopLevelNormalizerValue implements ValueExtractorInterface,
       }

Perhaps we can add these cache contexts somewhere else then? E.g. in EntityResource::getCollection()

AND HECK YEAH :D

wim leers’s picture

Once this is in, we'll also be able to make \Drupal\jsonapi\Controller\EntryPoint much nicer! 😀

Looking forward to an RTBC on this one :)

e0ipso’s picture

Assigned: Unassigned » e0ipso

I will be taking a look at this shortly. Thanks for all the work. Everything I saw in a quick scan made sense to me so far.

wim leers’s picture

Thanks, @e0ipso!

I wanted to address #48, but that led me to realize that this leads me to be quite far out-of-scope. Created #2992673: Set collection-specific query parameter cache contexts on collection responses instead of all responses for this.

gabesullice’s picture

Bump.

e0ipso’s picture

Assigned: e0ipso » Unassigned

I know I've said this in the past already, but I feel that these refactors in the name of maintainability have subjective value. One can see them as improvements, and another as a step back. Some of the brightest minds of the Drupal community have been puzzled by the inner workings of the module due to the lack of a cohesive execution thread to follow. In this particular case I think this issue is a step in the right direction.


Some generic comments.

  1. +++ b/src/Controller/EntityResource.php
    @@ -776,16 +778,23 @@ class EntityResource {
    +   * @param \Drupal\Core\Url[]|string[] $links
    +   *   The URLs to which to link. A 'self' link is added automatically.
    

    Why do we need to separate links out, and why a polymorphic argument?

  2. +++ b/src/EventSubscriber/ResourceResponseSubscriber.php
    @@ -126,6 +128,52 @@ class ResourceResponseSubscriber implements EventSubscriberInterface {
    +    assert($resource_type instanceof ResourceType);
    

    I'm unsure this level of babysitting is necessary.

  3. +++ b/src/Normalizer/Value/JsonApiDocumentTopLevelNormalizerValue.php
    @@ -74,25 +69,25 @@ class JsonApiDocumentTopLevelNormalizerValue implements ValueExtractorInterface,
    +   *   -2 is reserved to indicate that this is an errors document.
    

    Hmmm, this seems like an abuse of the parameter.

  4. +++ b/src/Normalizer/Value/JsonApiDocumentTopLevelNormalizerValue.php
    @@ -100,13 +95,24 @@ class JsonApiDocumentTopLevelNormalizerValue implements ValueExtractorInterface,
    +      assert(empty(array_diff(array_keys($links), [
    +        'self',
    +        'first',
    +        'next',
    +        'prev',
    +        'last',
    +        'related',
    +      ])));
    +      assert(Inspector::assertAll(function ($v) {
    +        return is_string($v) || $v instanceof Url;
    +      }, $links));
    

    Again, I'm not sure I understand what's the value of all these assertions.

gabesullice’s picture

StatusFileSize
new1.45 KB
new7.75 KB
new41.66 KB

In this particular case I think this issue is a step in the right direction.

❤️


1. IIRC, because value objects probably shouldn't be generating their own links. Also, doing so removes the need to pass in the LinkManager which depends on the request object. I agree with your confusion about accepting string or a Url objects. I'll investigate.

2. I wouldn't call this particular instance "babysitting." This guarantees that the response subscriber is getting an upcasted value, not a string. Given the complexity of the routing system, I'm happy to have this assert there so that we can quickly pinpoint when/if that ever breaks.

3. I agree. In a better language we'd make this an explicit argument using an enum. I did my best to replicate that in the attached patch.

4. The first was my suggestion in #13 and I remember @Wim Leers was actually not entirely sold either. Since then, I've actually come to think about link object keys differently. Therefore, I've removed the first assertion because I agree that it's not helpful. WRT to the second assertion, I guess it comes down to the same arguments that exist between type safe and untyped languages. My preference is on the latter, so I appreciate these kinds of type assertions.

gabesullice’s picture

StatusFileSize
new2.97 KB
new41.32 KB

Re: #54.1 I removed the Url stuff. I imagine @Wim Leers was prepping to better handle link cacheability, but that can happen in a separate issue.

Status: Needs review » Needs work

The last submitted patch, 55: 2984911-55.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

gabesullice’s picture

Status: Needs work » Needs review
StatusFileSize
new1.89 KB
new41.31 KB

2 CS violations and forgot to update a unit test.

IMHO, this is ready.

e0ipso’s picture

I use git hooks to make sure DCS don't go to d.o https://asciinema.org/a/pqa3uRQwf9Uq6d4I7iaQf1WxZ

If you are interested on that I can share the setup for contrib development.

gabesullice’s picture

StatusFileSize
new2.25 KB
new41.2 KB

I lied. This gets rid of some outdated comments since #55.

  • e0ipso committed b307802 on 8.x-2.x authored by gabesullice
    Issue #2984911 by Wim Leers, gabesullice, e0ipso: Remove access to the...
e0ipso’s picture

Status: Needs review » Fixed

Merged.

gabesullice’s picture

🎉

wim leers’s picture

I imagine @Wim Leers was prepping to better handle link cacheability, but that can happen in a separate issue.

+1

🎉

wim leers’s picture

wim leers’s picture

Quoting myself in #49:

Once this is in, we'll also be able to make \Drupal\jsonapi\Controller\EntryPoint much nicer! 😀

Issue: #3000622: Improve EntryPoint::index() now that JsonApiDocumentTopLevel is more capable.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.