Comments

gabesullice created an issue. See original summary.

gabesullice’s picture

Status: Active » Needs review
StatusFileSize
new22.96 KB

Status: Needs review » Needs work

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

gabesullice’s picture

The whole patch is: 6 files changed, 207 insertions(+), 167 deletions(-)

However: git df 8.x-2.x --stat src/Normalizer/JsonApiDocumentTopLevelNormalizer.php is a net reduction.

The bulk of the "new" logic is in the JsonApiResource namespace, where this patch adds an OmittedData class, makes NullData extend IncludedData and adds getAccessible and getOmissions to ResourceObjectData (and its descendants). The end result is seen in JsonApiDocumentTopLevel:

-    $this->data = $data;
-    $this->includes = $includes;
+    $this->data = $data instanceof ResourceObjectData ? $data->getAccessible() : $data;
+    $this->includes = $includes->getAccessible();
     $this->links = $links->withContext($this);
     $this->meta = $meta;
+    $this->omissions = $data instanceof ResourceObjectData
+      ? OmittedData::merge($data->getOmissions(), $includes->getOmissions())
+      : $includes->getOmissions();

Which makes omissions significantly simpler to reason about in the top level normalizer.

gabesullice’s picture

Assigned: Unassigned » gabesullice

Sweet, got a test run, I'll get this green tomorrow.

gabesullice’s picture

Title: Clean-up: JsonApiDocumentTopLevelNormalizer » [PP-1] Clean-up: JsonApiDocumentTopLevelNormalizer
Related issues: +#3035149: Use ResourceObjectData with cardinality 1 for individual responses

Since #3035149: Use ResourceObjectData with cardinality 1 for individual responses is RTBC, I'm marking this as postponed on it so that I can reroll this patch on top of it. Leaving as NW for that.

gabesullice’s picture

Title: [PP-1] Clean-up: JsonApiDocumentTopLevelNormalizer » Clean-up: JsonApiDocumentTopLevelNormalizer
Issue summary: View changes
Status: Needs work » Needs review
StatusFileSize
new39.82 KB

#3035149: Use ResourceObjectData with cardinality 1 for individual responses landed.

This is both rerolled and fairly different from #2. Therefore, I don't think an interdiff would be helpful.

gabesullice’s picture

I had moved getRelationshipLinks() from EntityReferenceFieldNormalizer to EntityResource. I'm not totally happy with that. The alternative was to make it public on the normalizer, which felt worse. BUT, #3036285: Add a \JsonApiResource\Relationship object to carry relationship data, metadata and a link collection. will end up deleting this method anyway.

Thinking about it more, I think the new method on ResourceType should just be in-lined rather than creating another public method.

Leaving as NR to get any high-level remarks.

gabesullice’s picture

Assigned: gabesullice » Unassigned
StatusFileSize
new7.94 KB
new37.93 KB

Implementing #9's thought about the new method.

Also:

The alternative was to make it public on the normalizer, which felt worse. BUT, #303628 will end up deleting this method anyway.

... which means I might as well leave it on the normalizer to minimize the change set and make it easier to review. Done.

gabesullice’s picture

StatusFileSize
new2.05 KB
new36.13 KB

... which means I might as well leave it on the normalizer to minimize the change set and make it easier to review. Done.

Actually, why stop there? Let's just put it all back the way it was.

wim leers’s picture

StatusFileSize
new1.81 KB
new34.44 KB
  1. +++ b/jsonapi.services.yml
    @@ -38,6 +38,10 @@ services:
    +  serializer.normalizer.data.jsonapi:
    +    class: Drupal\jsonapi\Normalizer\DataNormalizer
    

    🤔 First thought: adding a new normalizer seems unrelated to cleaning up JsonApiDocumentTopLevelNormalizer ?

  2. +++ b/src/Controller/TemporaryJsonapiFileFieldUploader.php
    @@ -336,8 +335,8 @@ class TemporaryJsonapiFileFieldUploader {
    -      // Close the input file stream since we can't proceed with the upload.
    -      // Don't try to close $temp_file since it's FALSE at this point.
    +      // Close the file streams.
    +      fclose($temp_file);
           fclose($file_data);
           $this->logger->error('Temporary file "%path" could not be opened for file upload.', ['%path' => $temp_file_path]);
           throw new HttpException(500, 'Temporary file could not be opened');
    @@ -464,11 +463,7 @@ class TemporaryJsonapiFileFieldUploader {
    
    @@ -464,11 +463,7 @@ class TemporaryJsonapiFileFieldUploader {
         $settings = $field_definition->getSettings();
     
         // Cap the upload size according to the PHP limit.
    -    // @todo Remove when we stop supporting Drupal 8.5 and 8.6.
    -    $max_bytes = floatval(\Drupal::VERSION) < 8.7
    -      ? file_upload_max_size()
    -      : Environment::getUploadMaxSize();
    -    $max_filesize = Bytes::toInt($max_bytes);
    +    $max_filesize = Bytes::toInt(file_upload_max_size());
    

    ✅ These surely were added to this patch accidentally, removing.

  3. +++ b/jsonapi.services.yml
    @@ -38,6 +38,10 @@ services:
    +  serializer.normalizer.data.jsonapi:
    +    class: Drupal\jsonapi\Normalizer\DataNormalizer
    +    tags:
    +      - { name: jsonapi_normalizer }
    
    +++ b/src/Normalizer/DataNormalizer.php
    @@ -0,0 +1,33 @@
    +  protected $supportedInterfaceOrClass = Data::class;
    ...
    +  public function normalize($object, $format = NULL, array $context = []) {
    

    🤔 It's not crystal-clear after reading the patch why this new normalizer is necessary.

  4. +++ b/src/JsonApiResource/JsonApiDocumentTopLevel.php
    @@ -43,17 +43,24 @@ class JsonApiDocumentTopLevel {
       /**
        * The includes to normalize.
        *
    -   * @var \Drupal\jsonapi\JsonApiResource\Data
    +   * @var \Drupal\jsonapi\JsonApiResource\IncludedData
        */
       protected $includes;
    

    🥳

  5. +++ b/src/JsonApiResource/NullData.php
    @@ -11,7 +11,7 @@ namespace Drupal\jsonapi\JsonApiResource;
    -class NullData extends Data {
    +class NullData extends IncludedData {
    

    🤔 Shouldn't this then be renamed NullIncludesData?

  6. +++ b/src/JsonApiResource/ResourceObjectData.php
    @@ -31,4 +31,34 @@ class ResourceObjectData extends Data {
    +  public function getAccessible() {
    ...
    +      if (!$resource_object instanceof EntityAccessDeniedHttpException) {
    ...
    +  public function getOmissions() {
    ...
    +      if ($resource_object instanceof EntityAccessDeniedHttpException) {
    

    Genius additions. 👏 I wish I had pointed this out :) This is indeed a perfect two-way split, thanks to this in the constructor: assert(Inspector::assertAllObjects($data, ResourceObject::class, EntityAccessDeniedHttpException::class));.

  7. +++ b/src/Normalizer/FieldNormalizer.php
    @@ -38,7 +37,7 @@ class FieldNormalizer extends NormalizerBase implements DenormalizerInterface {
    -      ? array_shift($normalized_items) ?: new CacheableNormalization(new CacheableMetadata(), NULL)
    +      ? array_shift($normalized_items) ?: CacheableNormalization::permanent(NULL)
    

    🤔 Can this not instead use CacheableOmission?

  8. +++ b/src/Normalizer/JsonApiDocumentTopLevelNormalizer.php
    @@ -175,18 +173,43 @@ class JsonApiDocumentTopLevelNormalizer extends NormalizerBase implements Denorm
    +      $document['meta'] = CacheableNormalization::aggregate(['omitted' => $normalized_omissions])->map(function ($normalization) use ($object) {
    +        return array_merge($normalization, $object->getMeta());
    
    @@ -222,186 +252,76 @@ class JsonApiDocumentTopLevelNormalizer extends NormalizerBase implements Denorm
    +    return $normalized_relationship->map(function ($normalization) {
    +      return $normalization['data'];
    +    });
    
    +++ b/src/Normalizer/Value/CacheableNormalization.php
    @@ -52,6 +66,39 @@ class CacheableNormalization implements CacheableDependencyInterface {
    +  public function map(callable $mapper) {
    +    $additional_cacheability = new CacheableMetadata();
    +    $mapped = new static($this, $mapper($this->getNormalization(), $additional_cacheability));
    +    return $mapped->withCacheableDependency($additional_cacheability);
    +  }
    

    🤔 Fascinating addition. But AFAICT that $additional_cacheability is not yet used anywhere. It may be better to not yet add this method in this issue, it seems to expand the scope unnecessarily, and given the second parameter is used, I'm not yet fully confident this will actually be used as intended?

  9. +++ b/src/Normalizer/JsonApiDocumentTopLevelNormalizer.php
    @@ -201,13 +224,20 @@ class JsonApiDocumentTopLevelNormalizer extends NormalizerBase implements Denorm
    +    $normalized_values = array_map(function (HttpExceptionInterface $exception) use ($format, $context) {
    ...
    +    foreach ($normalized_values as $normalized_error) {
    +      $cacheability->addCacheableDependency($normalized_error);
    +      $errors = array_merge($errors, $normalized_error->getNormalization());
    +    }
    

    🤔 This looks like we're re-normalizing $normalized_values. I think this is confusing.

    It feels like this is impossible to understand without stepping through it with a debugger?

  10. +++ b/src/Normalizer/JsonApiDocumentTopLevelNormalizer.php
    @@ -222,186 +252,76 @@ class JsonApiDocumentTopLevelNormalizer extends NormalizerBase implements Denorm
    -    return new CacheableNormalization($cacheability, $rasterized);
    +    return new CacheableNormalization($cacheability, $omitted);
    

    🤔 If I read this, I immediately think "why is this not using CacheableOmission?". But that's only for "NULL normalizations".

    This is where our naming could still use some love.

    Perhaps renaming $omitted to $communicate_omissions or something like that? (I still don't like that name, but that concept is what we should name it after.)

    Oh, how about $omission_links?

  11. +++ b/src/Normalizer/ResourceIdentifierNormalizer.php
    @@ -58,7 +57,7 @@ class ResourceIdentifierNormalizer extends NormalizerBase implements Denormalize
    -    return new CacheableNormalization(new CacheableMetadata(), $normalization);
    +    return CacheableNormalization::permanent($normalization);
    
    +++ b/src/Normalizer/ResourceObjectNormalizer.php
    @@ -103,7 +97,7 @@ class ResourceObjectNormalizer extends NormalizerBase {
    -      return new CacheableNormalization(new CacheableMetadata(), $field);
    +      return CacheableNormalization::permanent($field);
    
    +++ b/src/Normalizer/Value/CacheableNormalization.php
    @@ -42,6 +42,20 @@ class CacheableNormalization implements CacheableDependencyInterface {
    +  public static function permanent($normalization) {
    

    👍🤔 Nice addition, but makes this patch harder to review and technically out-of-scope. How about we split this off into a separate issue and land that first?

  12. +++ b/src/Normalizer/ResourceObjectNormalizer.php
    @@ -58,20 +58,14 @@ class ResourceObjectNormalizer extends NormalizerBase {
    +      'attributes' => CacheableNormalization::aggregate(array_diff_key($normalizer_values, array_flip($relationship_field_names)))->omitIfEmpty(),
    +      'relationships' => CacheableNormalization::aggregate(array_intersect_key($normalizer_values, array_flip($relationship_field_names)))->omitIfEmpty(),
    +      'links' => $this->serializer->normalize($object->getLinks(), $format, $context)->omitIfEmpty(),
    
    +++ b/src/Normalizer/Value/CacheableNormalization.php
    @@ -52,6 +66,39 @@ class CacheableNormalization implements CacheableDependencyInterface {
    +  public function omitIfEmpty() {
    +    return empty($this->normalization) ? new CacheableOmission($this) : $this;
    +  }
    

    👍 🤔 Hm, this means all these might use CacheableOmission. Does that make sense? Yes, it does, because it's not tied to "primary data omissions", \Drupal\jsonapi\Normalizer\Value\CacheableOmission is already generalized away from that (original) use case in HEAD.

    The more I think about it, the more elegant I think this is.

    This snippet of code is a huge step forward actually in terms of understandability! 🥳

gabesullice’s picture

Issue summary: View changes
StatusFileSize
new15.97 KB
new45.09 KB

1. I agree. I think we should just rescope the issue. Retitled, updated the IS slightly.
2. Thanks!
3. It sets up #3036285: Add a \JsonApiResource\Relationship object to carry relationship data, metadata and a link collection.. DataNormalizer normalizes data objects for: (1) primary data for individual, collections and relationships, (2) included and (3) relationship object data (within a resource object). I think that's enough reuse for it to be justified :)
4. :)
5. Sure. Done. That'll mean a 1 line Extras update. nbd.
6. :D
7. 🤔 hmm, I'm not sure if this will break something or not. I don't think it's been our behavior to omit empty fields. I'll add some code for it to see if it breaks tests. Tried this, it broke some tests. I think this would be an acceptable, or even desirable change, but I think it'd be better as a followup.
8. Yeah, you're right. This was used more widely in earlier iterations of the patch, but I think there's no longer a super clear justification for it.
9. Yeah, this is really confusing, even to me after working on this. Rather than figure this out and clean it up here, that's what #3036284: Clean-up: *Exception::buildErrors returns an array of errors, but never returns >1 error. is for. Errors are wrapped in some unnecessary arrays.
10. I used $ommission_links like you suggested. This is still ugly and, like #9, I'm leaving this largely alone so we can address it in #3036279: Clean-up: Use a LinkCollection for document omission links.. A major motivating goal of this issue is to detangle normalizeValues so that things can be made sensible. Right now, JsonApiDocumentTopLevel::normalizeValues has omissions, errors, links and metadata all tangled together so it's hard to make any changes to any of them without making changes to all of them.
11. Eh, I think the updated title and summary bring this into scope, no? I think you can appreciate that I'm a little hesitant to chain more issues together ATM ;)
12. Glad you like it! I was feeling pretty warm and fuzzy about it too.

gabesullice’s picture

StatusFileSize
new1.44 KB
new44.11 KB

Whoops, removed the usages of CacheableNormalization::map, but forgot to delete the method.

gabesullice’s picture

Title: Clean-up: JsonApiDocumentTopLevelNormalizer » Clean-up: JsonApiDocumentTopLevelNormalizer and children to prep for further clean-ups
wim leers’s picture

Status: Needs review » Reviewed & tested by the community

#13: good answers, thanks!

Final round:

  1. +++ b/src/Normalizer/JsonApiDocumentTopLevelNormalizer.php
    @@ -175,18 +173,44 @@ class JsonApiDocumentTopLevelNormalizer extends NormalizerBase implements Denorm
    +    $document['jsonapi'] = CacheableNormalization::permanent([
    ...
    +        $document['data'] = $this->normalizeEntityReferenceFieldItemList($object, $format, $context);
    ...
    +      $document['included'] = $this->serializer->normalize($object->getIncludes(), $format, $context)->omitIfEmpty();
    ...
    +      $document['meta'] = (new CacheableNormalization($normalized_omissions, $meta))->omitIfEmpty();
    

    👏🥳 I like how we're now truly explicitly constructing a document, and the top-level keys prescribed by https://jsonapi.org/format are clearly here: that makes it crystal clear even to any developer reading this source code what's going on here :)

    (Which definitely cannot be said of the code in HEAD.)

    This is also the heart of the patch.

  2. +++ b/src/Normalizer/JsonApiDocumentTopLevelNormalizer.php
    @@ -175,18 +173,44 @@ class JsonApiDocumentTopLevelNormalizer extends NormalizerBase implements Denorm
    +        // Always deal with a collection.
    +        $document['data'] = $this->serializer->normalize($data, $format, $context);
    

    I don't think this comment is accurate anymore. Could be removed on commit.

    (Also: this is where DataNormalizer is used, which together with #13.3 forms the answer to my scepticism in #12.1: the DataNormalizer is there to normalize whichever primary data the generated JSON:API document contains.)

gabesullice’s picture

StatusFileSize
new44.15 KB

Needed a reroll after #3037452: Clean-up: ResourceObject should not be coupled to entities. I also did #16.2.

Will commit if tests pass.

  • gabesullice committed 2563ccd on 8.x-2.x
    Issue #3036286 by gabesullice, Wim Leers: Clean-up:...
gabesullice’s picture

Status: Reviewed & tested by the community » Fixed
Issue tags: -blocker +Needs follow-up

This needs to update Extras in #3035134: Compatibility with upcoming JSON:API 2.4 release: JSON:API Extras 3.5 because of the change of NullData to NullIncludedData.

dww’s picture

Issue tags: -Needs follow-up +Needs followup

This was the only issue with "Needs follow-up" including hyphen.
Consolidating tags.

Status: Fixed » Closed (fixed)

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