diff --git a/src/JsonApiResource/JsonApiDocumentTopLevel.php b/src/JsonApiResource/JsonApiDocumentTopLevel.php index 4767d35..703850d 100644 --- a/src/JsonApiResource/JsonApiDocumentTopLevel.php +++ b/src/JsonApiResource/JsonApiDocumentTopLevel.php @@ -59,7 +59,7 @@ class JsonApiDocumentTopLevel { assert(!$data instanceof ErrorCollection || $includes instanceof NullEntityCollection); $this->data = $data; $this->includes = $includes; - $this->links = $links; + $this->links = $links->withContext($this); $this->meta = $meta; } diff --git a/src/JsonApiResource/LinkCollection.php b/src/JsonApiResource/LinkCollection.php index 1a5519a..b6bd88c 100644 --- a/src/JsonApiResource/LinkCollection.php +++ b/src/JsonApiResource/LinkCollection.php @@ -18,13 +18,27 @@ class LinkCollection implements \IteratorAggregate { */ protected $links; + /** + * The link context. + * + * All links objects exist within a context object. Links form a relationship + * between a source IRI and target IRI. A context is the link's source. + * + * @var \Drupal\jsonapi\JsonApiResource\JsonApiDocumentTopLevel + */ + protected $context; + /** * LinkCollection constructor. * * @param \Drupal\jsonapi\JsonApiResource\Link[] $links * An associated array of key names and JSON:API Link objects. + * @param \Drupal\jsonapi\JsonApiResource\JsonApiDocumentTopLevel $context + * (internal use only) The context object. Use the self::withContext() + * method to establish a context. This should be done automatically when + * a LinkCollection is passed into a context object. */ - public function __construct(array $links) { + public function __construct(array $links, JsonApiDocumentTopLevel $context = NULL) { assert(Inspector::assertAll(function ($key) { return static::validKey($key); }, array_keys($links))); @@ -35,12 +49,14 @@ class LinkCollection implements \IteratorAggregate { $this->links = array_map(function ($link) { return is_array($link) ? $link : [$link]; }, $links); + $this->context = $context; } /** * {@inheritdoc} */ public function getIterator() { + assert(!is_null($this->context), 'A LinkCollection is invalid unless a context has been established.'); return new \ArrayIterator($this->links); } @@ -64,12 +80,35 @@ class LinkCollection implements \IteratorAggregate { foreach ($merged[$key] as $index => $existing_link) { if (Link::compare($existing_link, $new_link) === 0) { $merged[$key][$index] = Link::merge($existing_link, $new_link); - return new static($merged); + return new static($merged, $this->context); } } } $merged[$key][] = $new_link; - return new static($merged); + return new static($merged, $this->context); + } + + /** + * Establishes a new context for a LinkCollection. + * + * @param \Drupal\jsonapi\JsonApiResource\JsonApiDocumentTopLevel $context + * The new context object. + * + * @return static + * A new LinkCollection with the given context. + */ + public function withContext(JsonApiDocumentTopLevel $context) { + return new static($this->links, $context); + } + + /** + * Gets the LinkCollection's context object. + * + * @return \Drupal\jsonapi\JsonApiResource\JsonApiDocumentTopLevel + * The LinkCollection's context. + */ + public function getContext() { + return $this->context; } /** @@ -79,7 +118,7 @@ class LinkCollection implements \IteratorAggregate { * The filter callback. The callback has the signature below. * * @code - * boolean callback(string $key, \Drupal\jsonapi\JsonApiResource\Link $link)) + * boolean callback(string $key, \Drupal\jsonapi\JsonApiResource\Link $link, mixed $context)) * @endcode * * @return \Drupal\jsonapi\JsonApiResource\LinkCollection @@ -88,12 +127,12 @@ class LinkCollection implements \IteratorAggregate { public function filter(callable $f) { $links = iterator_to_array($this); $filtered = array_reduce(array_keys($links), function ($filtered, $key) use ($links, $f) { - if ($f($key, $links[$key])) { + if ($f($key, $links[$key], $this->context)) { $filtered[$key] = $links[$key]; } return $filtered; }, []); - return new LinkCollection($filtered); + return new LinkCollection($filtered, $this->context); } /** @@ -108,7 +147,8 @@ class LinkCollection implements \IteratorAggregate { * A new LinkCollection with the links of both inputs. */ public static function merge(LinkCollection $a, LinkCollection $b) { - $merged = new LinkCollection([]); + assert($a->getContext() === $b->getContext()); + $merged = new LinkCollection([], $a->getContext()); foreach ($a as $key => $link) { $merged = $merged->withLink($key, $link); } diff --git a/src/Normalizer/LinkCollectionNormalizer.php b/src/Normalizer/LinkCollectionNormalizer.php index 5a1a726..071bc67 100644 --- a/src/Normalizer/LinkCollectionNormalizer.php +++ b/src/Normalizer/LinkCollectionNormalizer.php @@ -26,6 +26,20 @@ use Drupal\jsonapi\Normalizer\Value\NormalizedValue; */ 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} */ @@ -49,6 +63,7 @@ class LinkCollectionNormalizer extends NormalizerBase { * {@inheritdoc} */ public function normalize($object, $format = NULL, array $context = []) { + assert($object instanceof LinkCollection); $normalized = []; $cacheability = new CacheableMetadata(); /* @var \Drupal\jsonapi\JsonApiResource\Link $link */ @@ -57,7 +72,7 @@ class LinkCollectionNormalizer extends NormalizerBase { foreach ($links as $link) { /* @var \Drupal\jsonapi\Normalizer\Value\NormalizedValue $normalized_link */ $link_key = $is_multiple ? sprintf('%s:%s', $key, $this->hashByHref($link)) : $key; - $context += ['link_object_key' => $link_key]; + $context += [static::LINK_KEY => $link_key, static::LINK_CONTEXT => $object->getContext()]; $normalized_link = $this->serializer->normalize($link, $format, $context); $cacheability->addCacheableDependency($normalized_link); $normalized[$link_key] = $normalized_link->rasterizeValue(); diff --git a/src/Normalizer/LinkNormalizer.php b/src/Normalizer/LinkNormalizer.php index 9ac4a12..b9ae078 100644 --- a/src/Normalizer/LinkNormalizer.php +++ b/src/Normalizer/LinkNormalizer.php @@ -3,6 +3,7 @@ namespace Drupal\jsonapi\Normalizer; use Drupal\jsonapi\JsonApiResource\Link; +use Drupal\jsonapi\JsonApiResource\LinkCollection; use Drupal\jsonapi\Normalizer\Value\NormalizedValue; /** @@ -26,7 +27,7 @@ class LinkNormalizer extends NormalizerBase { $link_relation_types = $object->getLinkRelationTypes(); // Omit 'meta.rel' if the link object's key there is one link relation type // and it is the same as the link object's key. - if (count($link_relation_types) > 1 || ($primary_link_relation = reset($link_relation_types)) && $primary_link_relation !== $context['link_object_key']) { + if (count($link_relation_types) > 1 || ($primary_link_relation = reset($link_relation_types)) && $primary_link_relation !== $context[LinkCollectionNormalizer::LINK_KEY]) { foreach ($link_relation_types as $link_relation_type) { $normalized['meta']['rel'][] = $link_relation_type; }