You are here

class LinkCollectionNormalizer in Drupal 9

Same name and namespace in other branches
  1. 8 core/modules/jsonapi/src/Normalizer/LinkCollectionNormalizer.php \Drupal\jsonapi\Normalizer\LinkCollectionNormalizer

Normalizes a LinkCollection object.

The JSON:API specification has the concept of a "links collection". A links collection is a JSON object where each member of the object is a "link object". Unfortunately, this means that it is not possible to have more than one link for a given key.

When normalizing more than one link in a LinkCollection with the same key, a unique and random string is appended to the link's key after a double dash (--) to differentiate the links. See this class's hashByHref() method for details.

This may change with a later version of the JSON:API specification.

@internal JSON:API maintains no PHP API since its API is the HTTP API. This class may change at any time and this will break any dependencies on it.

Hierarchy

Expanded class hierarchy of LinkCollectionNormalizer

See also

https://www.drupal.org/project/drupal/issues/3032787

jsonapi.api.php

1 file declares its use of LinkCollectionNormalizer
LinkCollectionNormalizerTest.php in core/modules/jsonapi/tests/src/Kernel/Normalizer/LinkCollectionNormalizerTest.php
1 string reference to 'LinkCollectionNormalizer'
jsonapi.services.yml in core/modules/jsonapi/jsonapi.services.yml
core/modules/jsonapi/jsonapi.services.yml
1 service uses LinkCollectionNormalizer
serializer.normalizer.link_collection.jsonapi in core/modules/jsonapi/jsonapi.services.yml
Drupal\jsonapi\Normalizer\LinkCollectionNormalizer

File

core/modules/jsonapi/src/Normalizer/LinkCollectionNormalizer.php, line 34

Namespace

Drupal\jsonapi\Normalizer
View source
class LinkCollectionNormalizer extends NormalizerBase {

  /**
   * The normalizer $context key name for the key of an individual link.
   *
   * @var string
   */
  const LINK_KEY = 'jsonapi_links_object_link_key';

  /**
   * The normalizer $context key name for the context object of the link.
   *
   * @var string
   */
  const LINK_CONTEXT = 'jsonapi_links_object_context';

  /**
   * {@inheritdoc}
   */
  protected $supportedInterfaceOrClass = LinkCollection::class;

  /**
   * A random string to use when hashing links.
   *
   * This string is unique per instance of a link collection, but always the
   * same within it. This means that link key hashes will be non-deterministic
   * for outside observers, but two links within the same collection will always
   * have the same hash value.
   *
   * This is not used for cryptographic purposes.
   *
   * @var string
   */
  protected $hashSalt;

  /**
   * The current user making the request.
   *
   * @var \Drupal\Core\Session\AccountInterface
   */
  protected $currentUser;

  /**
   * LinkCollectionNormalizer constructor.
   *
   * @param \Drupal\Core\Session\AccountInterface $current_user
   *   The current user.
   */
  public function __construct(AccountInterface $current_user = NULL) {
    if (is_null($current_user)) {
      @trigger_error('Calling ' . __METHOD__ . '() without the $current_user argument is deprecated in drupal:9.2.0 and will be required in drupal:10.0.0.', E_USER_DEPRECATED);
      $current_user = \Drupal::currentUser();
    }
    $this->currentUser = $current_user;
  }

  /**
   * {@inheritdoc}
   */
  public function normalize($object, $format = NULL, array $context = []) {
    assert($object instanceof LinkCollection);
    $normalized = [];

    /** @var \Drupal\jsonapi\JsonApiResource\Link $link */
    foreach ($object as $key => $links) {
      $is_multiple = count($links) > 1;
      foreach ($links as $link) {
        $link_key = $is_multiple ? sprintf('%s--%s', $key, $this
          ->hashByHref($link)) : $key;
        $attributes = $link
          ->getTargetAttributes();
        $normalization = array_merge([
          'href' => $link
            ->getHref(),
        ], !empty($attributes) ? [
          'meta' => $attributes,
        ] : []);

        // Checking access on links is not about access to the link itself;
        // it is about whether the current user has access to the route that is
        // *targeted* by the link. This is done on a "best effort" basis. That
        // is, some links target routes that depend on a request to determine if
        // they're accessible or not. Some other links might target routes to
        // which the current user will clearly not have access, in that case
        // this code proactively removes those links from the response.
        $access = $link
          ->getUri()
          ->access($this->currentUser, TRUE);
        $cacheability = CacheableMetadata::createFromObject($link)
          ->addCacheableDependency($access);
        $normalized[$link_key] = $access
          ->isAllowed() ? new CacheableNormalization($cacheability, $normalization) : new CacheableOmission($cacheability);
      }
    }
    return CacheableNormalization::aggregate($normalized);
  }

  /**
   * Hashes a link using its href and its target attributes, if any.
   *
   * This method generates an unpredictable, but deterministic, 7 character
   * alphanumeric hash for a given link.
   *
   * The hash is unpredictable because a random hash salt will be used for every
   * request. The hash is deterministic because, within a single request, links
   * with the same href and target attributes (i.o.w. duplicates) will generate
   * equivalent hash values.
   *
   * @param \Drupal\jsonapi\JsonApiResource\Link $link
   *   A link to be hashed.
   *
   * @return string
   *   A 7 character alphanumeric hash.
   */
  protected function hashByHref(Link $link) {

    // Generate a salt unique to each instance of this class.
    if (!$this->hashSalt) {
      $this->hashSalt = Crypt::randomBytesBase64();
    }

    // Create a dictionary of link parameters.
    $link_parameters = [
      'href' => $link
        ->getHref(),
    ] + $link
      ->getTargetAttributes();

    // Serialize the dictionary into a string.
    foreach ($link_parameters as $name => $value) {
      $serialized_parameters[] = sprintf('%s="%s"', $name, implode(' ', (array) $value));
    }

    // Hash the string.
    $b64_hash = Crypt::hashBase64($this->hashSalt . implode('; ', $serialized_parameters));

    // Remove any dashes and underscores from the base64 hash and then return
    // the first 7 characters.
    return substr(str_replace([
      '-',
      '_',
    ], '', $b64_hash), 0, 7);
  }

}

Members

Namesort descending Modifiers Type Description Overrides
CacheableNormalizerInterface::SERIALIZATION_CONTEXT_CACHEABILITY constant Name of key for bubbling cacheability metadata via serialization context.
LinkCollectionNormalizer::$currentUser protected property The current user making the request.
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 using its href and its target attributes, if any.
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.
LinkCollectionNormalizer::normalize public function
LinkCollectionNormalizer::__construct public function LinkCollectionNormalizer constructor.
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 1