You are here

final class JsonapiHypermediaLinkCollectionNormalizer in JSON:API Hypermedia 8

Class JsonApiHypermediaLinkCollectionNormalizer.

@internal

Hierarchy

Expanded class hierarchy of JsonapiHypermediaLinkCollectionNormalizer

1 string reference to 'JsonapiHypermediaLinkCollectionNormalizer'
jsonapi_hypermedia.services.yml in ./jsonapi_hypermedia.services.yml
jsonapi_hypermedia.services.yml
1 service uses JsonapiHypermediaLinkCollectionNormalizer
serializer.normalizer.link_collection.jsonapi_hypermedia in ./jsonapi_hypermedia.services.yml
Drupal\jsonapi\Normalizer\JsonapiHypermediaImpostor\JsonapiHypermediaLinkCollectionNormalizer

File

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

Namespace

Drupal\jsonapi\Normalizer\JsonapiHypermediaImpostor
View source
final class JsonapiHypermediaLinkCollectionNormalizer extends LinkCollectionNormalizer {

  /**
   * The link provider plugin manager service.
   *
   * @var \Drupal\jsonapi_hypermedia\Plugin\LinkProviderManagerInterface
   */
  protected $linkProviderManager;

  /**
   * The renderer service.
   *
   * @var \Drupal\Core\Render\RendererInterface
   */
  protected $renderer;

  /**
   * Sets the link provider manager service.
   *
   * @param \Drupal\jsonapi_hypermedia\Plugin\LinkProviderManagerInterface $link_provider_manager
   *   The link provider manager.
   */
  public function setLinkProviderManager(LinkProviderManagerInterface $link_provider_manager) {
    $this->linkProviderManager = $link_provider_manager;
  }

  /**
   * Set the renderer.
   *
   * @param \Drupal\Core\Render\RendererInterface $renderer
   *   The renderer service.
   */
  public function setRenderer(RendererInterface $renderer) {
    $this->renderer = $renderer;
  }

  /**
   * {@inheritdoc}
   */
  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;
  }

  /**
   * Hashes a link normalization.
   *
   * @param array $link
   *   A link to be hashed.
   *
   * @return string
   *   A 7 character alphanumeric hash.
   */
  protected function hashLinkNormalization(array $link) {
    if (!$this->hashSalt) {
      $this->hashSalt = Crypt::randomBytesBase64();
    }
    return substr(str_replace([
      '-',
      '_',
    ], '', Crypt::hashBase64($this->hashSalt . Json::encode($link))), 0, 7);
  }

}

Members

Namesort descending Modifiers Type Description Overrides
CacheableNormalizerInterface::SERIALIZATION_CONTEXT_CACHEABILITY constant Name of key for bubbling cacheability metadata via serialization context.
JsonapiHypermediaLinkCollectionNormalizer::$linkProviderManager protected property The link provider plugin manager service.
JsonapiHypermediaLinkCollectionNormalizer::$renderer protected property The renderer service.
JsonapiHypermediaLinkCollectionNormalizer::hashLinkNormalization protected function Hashes a link normalization.
JsonapiHypermediaLinkCollectionNormalizer::normalize public function Normalizes an object into a set of arrays/scalars. Overrides LinkCollectionNormalizer::normalize
JsonapiHypermediaLinkCollectionNormalizer::setLinkProviderManager public function Sets the link provider manager service.
JsonapiHypermediaLinkCollectionNormalizer::setRenderer public function Set the renderer.
LinkCollectionNormalizer::$hashSalt protected property A random string to use when hashing links.
LinkCollectionNormalizer::$supportedInterfaceOrClass protected property The interface or class that this Normalizer supports. Overrides NormalizerBase::$supportedInterfaceOrClass
LinkCollectionNormalizer::hashByHref protected function Hashes a link by its href.
LinkCollectionNormalizer::LINK_CONTEXT constant The normalizer $context key name for the context object of the link.
LinkCollectionNormalizer::LINK_KEY constant The normalizer $context key name for the key of an individual link.
NormalizerBase::$format protected property List of formats which supports (de-)normalization. Overrides NormalizerBase::$format
NormalizerBase::addCacheableDependency protected function Adds cacheability if applicable.
NormalizerBase::checkFormat protected function Checks if the provided format is supported by this normalizer. Overrides NormalizerBase::checkFormat
NormalizerBase::rasterizeValueRecursive protected static function Rasterizes a value recursively.
NormalizerBase::supportsDenormalization public function Implements \Symfony\Component\Serializer\Normalizer\DenormalizerInterface::supportsDenormalization() 1
NormalizerBase::supportsNormalization public function Checks whether the given class is supported for normalization by this normalizer. 1