annotate core/modules/jsonapi/src/JsonApiResource/LinkCollection.php @ 5:12f9dff5fda9 tip

Update to Drupal core 8.7.1
author Chris Cannam
date Thu, 09 May 2019 15:34:47 +0100
parents
children
rev   line source
Chris@5 1 <?php
Chris@5 2
Chris@5 3 namespace Drupal\jsonapi\JsonApiResource;
Chris@5 4
Chris@5 5 use Drupal\Component\Assertion\Inspector;
Chris@5 6
Chris@5 7 /**
Chris@5 8 * Contains a set of JSON:API Link objects.
Chris@5 9 *
Chris@5 10 * @internal JSON:API maintains no PHP API. The API is the HTTP API. This class
Chris@5 11 * may change at any time and could break any dependencies on it.
Chris@5 12 *
Chris@5 13 * @see https://www.drupal.org/project/jsonapi/issues/3032787
Chris@5 14 * @see jsonapi.api.php
Chris@5 15 */
Chris@5 16 final class LinkCollection implements \IteratorAggregate {
Chris@5 17
Chris@5 18 /**
Chris@5 19 * The links in the collection, keyed by unique strings.
Chris@5 20 *
Chris@5 21 * @var \Drupal\jsonapi\JsonApiResource\Link[]
Chris@5 22 */
Chris@5 23 protected $links;
Chris@5 24
Chris@5 25 /**
Chris@5 26 * The link context.
Chris@5 27 *
Chris@5 28 * All links objects exist within a context object. Links form a relationship
Chris@5 29 * between a source IRI and target IRI. A context is the link's source.
Chris@5 30 *
Chris@5 31 * @var \Drupal\jsonapi\JsonApiResource\JsonApiDocumentTopLevel|\Drupal\jsonapi\JsonApiResource\ResourceObject
Chris@5 32 *
Chris@5 33 * @see https://tools.ietf.org/html/rfc8288#section-3.2
Chris@5 34 */
Chris@5 35 protected $context;
Chris@5 36
Chris@5 37 /**
Chris@5 38 * LinkCollection constructor.
Chris@5 39 *
Chris@5 40 * @param \Drupal\jsonapi\JsonApiResource\Link[] $links
Chris@5 41 * An associated array of key names and JSON:API Link objects.
Chris@5 42 * @param \Drupal\jsonapi\JsonApiResource\JsonApiDocumentTopLevel|\Drupal\jsonapi\JsonApiResource\ResourceObject $context
Chris@5 43 * (internal use only) The context object. Use the self::withContext()
Chris@5 44 * method to establish a context. This should be done automatically when
Chris@5 45 * a LinkCollection is passed into a context object.
Chris@5 46 */
Chris@5 47 public function __construct(array $links, $context = NULL) {
Chris@5 48 assert(Inspector::assertAll(function ($key) {
Chris@5 49 return static::validKey($key);
Chris@5 50 }, array_keys($links)));
Chris@5 51 assert(Inspector::assertAll(function ($link) {
Chris@5 52 return $link instanceof Link || is_array($link) && Inspector::assertAllObjects($link, Link::class);
Chris@5 53 }, $links));
Chris@5 54 assert(is_null($context) || Inspector::assertAllObjects([$context], JsonApiDocumentTopLevel::class, ResourceObject::class));
Chris@5 55 ksort($links);
Chris@5 56 $this->links = array_map(function ($link) {
Chris@5 57 return is_array($link) ? $link : [$link];
Chris@5 58 }, $links);
Chris@5 59 $this->context = $context;
Chris@5 60 }
Chris@5 61
Chris@5 62 /**
Chris@5 63 * {@inheritdoc}
Chris@5 64 */
Chris@5 65 public function getIterator() {
Chris@5 66 assert(!is_null($this->context), 'A LinkCollection is invalid unless a context has been established.');
Chris@5 67 return new \ArrayIterator($this->links);
Chris@5 68 }
Chris@5 69
Chris@5 70 /**
Chris@5 71 * Gets a new LinkCollection with the given link inserted.
Chris@5 72 *
Chris@5 73 * @param string $key
Chris@5 74 * A key for the link. If the key already exists and the link shares an href
Chris@5 75 * with an existing link with that key, those links will be merged together.
Chris@5 76 * @param \Drupal\jsonapi\JsonApiResource\Link $new_link
Chris@5 77 * The link to insert.
Chris@5 78 *
Chris@5 79 * @return static
Chris@5 80 * A new LinkCollection with the given link inserted or merged with the
Chris@5 81 * current set of links.
Chris@5 82 */
Chris@5 83 public function withLink($key, Link $new_link) {
Chris@5 84 assert(static::validKey($key));
Chris@5 85 $merged = $this->links;
Chris@5 86 if (isset($merged[$key])) {
Chris@5 87 foreach ($merged[$key] as $index => $existing_link) {
Chris@5 88 if (Link::compare($existing_link, $new_link) === 0) {
Chris@5 89 $merged[$key][$index] = Link::merge($existing_link, $new_link);
Chris@5 90 return new static($merged, $this->context);
Chris@5 91 }
Chris@5 92 }
Chris@5 93 }
Chris@5 94 $merged[$key][] = $new_link;
Chris@5 95 return new static($merged, $this->context);
Chris@5 96 }
Chris@5 97
Chris@5 98 /**
Chris@5 99 * Whether a link with the given key exists.
Chris@5 100 *
Chris@5 101 * @param string $key
Chris@5 102 * The key.
Chris@5 103 *
Chris@5 104 * @return bool
Chris@5 105 * TRUE if a link with the given key exist, FALSE otherwise.
Chris@5 106 */
Chris@5 107 public function hasLinkWithKey($key) {
Chris@5 108 return array_key_exists($key, $this->links);
Chris@5 109 }
Chris@5 110
Chris@5 111 /**
Chris@5 112 * Establishes a new context for a LinkCollection.
Chris@5 113 *
Chris@5 114 * @param \Drupal\jsonapi\JsonApiResource\JsonApiDocumentTopLevel|\Drupal\jsonapi\JsonApiResource\ResourceObject $context
Chris@5 115 * The new context object.
Chris@5 116 *
Chris@5 117 * @return static
Chris@5 118 * A new LinkCollection with the given context.
Chris@5 119 */
Chris@5 120 public function withContext($context) {
Chris@5 121 return new static($this->links, $context);
Chris@5 122 }
Chris@5 123
Chris@5 124 /**
Chris@5 125 * Gets the LinkCollection's context object.
Chris@5 126 *
Chris@5 127 * @return \Drupal\jsonapi\JsonApiResource\JsonApiDocumentTopLevel|\Drupal\jsonapi\JsonApiResource\ResourceObject
Chris@5 128 * The LinkCollection's context.
Chris@5 129 */
Chris@5 130 public function getContext() {
Chris@5 131 assert(!is_null($this->context), 'A LinkCollection is invalid unless a context has been established.');
Chris@5 132 return $this->context;
Chris@5 133 }
Chris@5 134
Chris@5 135 /**
Chris@5 136 * Filters a LinkCollection using the provided callback.
Chris@5 137 *
Chris@5 138 * @param callable $f
Chris@5 139 * The filter callback. The callback has the signature below.
Chris@5 140 *
Chris@5 141 * @code
Chris@5 142 * boolean callback(string $key, \Drupal\jsonapi\JsonApiResource\Link $link, mixed $context))
Chris@5 143 * @endcode
Chris@5 144 *
Chris@5 145 * @return \Drupal\jsonapi\JsonApiResource\LinkCollection
Chris@5 146 * A new, filtered LinkCollection.
Chris@5 147 */
Chris@5 148 public function filter(callable $f) {
Chris@5 149 $links = iterator_to_array($this);
Chris@5 150 $filtered = array_reduce(array_keys($links), function ($filtered, $key) use ($links, $f) {
Chris@5 151 if ($f($key, $links[$key], $this->context)) {
Chris@5 152 $filtered[$key] = $links[$key];
Chris@5 153 }
Chris@5 154 return $filtered;
Chris@5 155 }, []);
Chris@5 156 return new LinkCollection($filtered, $this->context);
Chris@5 157 }
Chris@5 158
Chris@5 159 /**
Chris@5 160 * Merges two LinkCollections.
Chris@5 161 *
Chris@5 162 * @param \Drupal\jsonapi\JsonApiResource\LinkCollection $a
Chris@5 163 * The first link collection.
Chris@5 164 * @param \Drupal\jsonapi\JsonApiResource\LinkCollection $b
Chris@5 165 * The second link collection.
Chris@5 166 *
Chris@5 167 * @return \Drupal\jsonapi\JsonApiResource\LinkCollection
Chris@5 168 * A new LinkCollection with the links of both inputs.
Chris@5 169 */
Chris@5 170 public static function merge(LinkCollection $a, LinkCollection $b) {
Chris@5 171 assert($a->getContext() === $b->getContext());
Chris@5 172 $merged = new LinkCollection([], $a->getContext());
Chris@5 173 foreach ($a as $key => $links) {
Chris@5 174 $merged = array_reduce($links, function (self $merged, Link $link) use ($key) {
Chris@5 175 return $merged->withLink($key, $link);
Chris@5 176 }, $merged);
Chris@5 177 }
Chris@5 178 foreach ($b as $key => $links) {
Chris@5 179 $merged = array_reduce($links, function (self $merged, Link $link) use ($key) {
Chris@5 180 return $merged->withLink($key, $link);
Chris@5 181 }, $merged);
Chris@5 182 }
Chris@5 183 return $merged;
Chris@5 184 }
Chris@5 185
Chris@5 186 /**
Chris@5 187 * Ensures that a link key is valid.
Chris@5 188 *
Chris@5 189 * @param string $key
Chris@5 190 * A key name.
Chris@5 191 *
Chris@5 192 * @return bool
Chris@5 193 * TRUE if the key is valid, FALSE otherwise.
Chris@5 194 */
Chris@5 195 protected static function validKey($key) {
Chris@5 196 return is_string($key) && !is_numeric($key) && strpos($key, ':') === FALSE;
Chris@5 197 }
Chris@5 198
Chris@5 199 }