You are here

public function JsonapiHypermediaLinkCollectionNormalizer::normalize in JSON:API Hypermedia 8

Overrides LinkCollectionNormalizer::normalize

File

src/Normalizer/JsonapiHypermediaImpostor/JsonapiHypermediaLinkCollectionNormalizer.php, line 59

Class

JsonapiHypermediaLinkCollectionNormalizer
Class JsonApiHypermediaLinkCollectionNormalizer.

Namespace

Drupal\jsonapi\Normalizer\JsonapiHypermediaImpostor

Code

public function normalize($link_collection, $format = NULL, array $context = []) {
  assert($link_collection instanceof LinkCollection);

  // @todo: remove this render context once https://www.drupal.org/project/drupal/issues/3055889 lands.
  $render_context = new RenderContext();
  $custom_links = $this->renderer
    ->executeInRenderContext($render_context, function () use ($link_collection) {
    return $this->linkProviderManager
      ->getLinkCollection($link_collection
      ->getContext());
  });
  $normalized = [];
  foreach (LinkCollection::merge($link_collection, $custom_links) as $key => $links) {
    $normalizations = [];
    foreach ($links as $link) {
      assert($link instanceof Link);
      $normalization = [];
      $rel = (array) $link
        ->getLinkRelationType();

      // Workaround to fix the JSON:API entrypoint.
      if (empty($rel)) {
        $rel = [
          $key,
        ];
      }
      $attributes = $link
        ->getTargetAttributes();
      $normalization['href'] = $link
        ->getHref();
      if (!empty($attributes)) {
        $normalization['meta']['linkParams'] = $this->serializer
          ->normalize($attributes, $format, $context);
      }
      if (!empty($rel)) {

        // We have to call array_values() on $rel to reset the array's
        // numerical index. Otherwise, it can output arrays with missing
        // indices and json_encode will serialize these as objects. This is a
        // band-aid. The real fix probably needs to be made in Link::merge(),
        // Link::__construct() or LinkCollection::withLink() because one of
        // these methods is likely where the array's pointer is getting out of
        // place.
        $rel = array_values($rel);
        if (count($rel) > 1 || reset($rel) !== $key) {
          $normalization['meta']['linkParams']['rel'] = $rel;
        }
        $normalizations[] = $normalization;
      }
    }

    // Links that share the same link target and target attributes can be
    // compacted by combining their link relation types so that a single
    // link can represent multiple links (one per link relationship type).
    // This saves bytes in a JSON:API response.
    $compacted = array_reduce($normalizations, function ($compacted, $normalization) use ($key) {
      foreach ($compacted as &$previous) {
        $same_href = $previous['href'] === $normalization['href'];
        $previous_attributes = array_diff_key($previous['meta']['linkParams'] ?? [], array_flip([
          'rel',
        ]));
        $normalization_attributes = array_diff_key($normalization['meta']['linkParams'] ?? [], array_flip([
          'rel',
        ]));
        $same_attributes = Json::encode($previous_attributes) === Json::encode($normalization_attributes);
        if ($same_href && $same_attributes) {
          $combined_link_relation_types = array_unique(array_merge($previous['meta']['linkParams']['rel'] ?? [
            $key,
          ], $normalization['meta']['linkParams']['rel'] ?? []));

          // When the link relation type matches the link key, it can be
          // omitted, since it's implied. This also saves bytes in a
          // JSON:API response and is more readable.
          if ($combined_link_relation_types > 1 || reset($combined_link_relation_types) !== $key) {
            $previous['meta']['linkParams']['rel'] = $combined_link_relation_types;
          }
          return $compacted;
        }
      }
      $compacted[] = $normalization;
      return $compacted;
    }, []);
    $is_multiple = count($compacted) > 1;

    // Finally, the compacted link normalizations are turned into cacheable
    // normalizations and a unique link object key is computed for them when
    // more than one link with the same key is present. This is a workaround
    // for the fact that the JSON:API spec does not permit arrays of links.
    $normalized = array_reduce($compacted, function ($normalized, $normalization) use ($link, $key, $is_multiple) {
      $link_key = $is_multiple ? sprintf('%s--%s', $key, $this
        ->hashLinkNormalization($normalization)) : $key;
      $normalized[$link_key] = new CacheableNormalization($link, $normalization);
      return $normalized;
    }, $normalized);
  }
  $normalized_links = CacheableNormalization::aggregate($normalized);
  return !$render_context
    ->isEmpty() ? $normalized_links
    ->withCacheableDependency($render_context
    ->pop()) : $normalized_links;
}