annotate core/modules/jsonapi/src/JsonApiResource/LinkCollection.php @ 19:fa3358dc1485 tip

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