comparison core/modules/jsonapi/src/JsonApiResource/LinkCollection.php @ 18:af1871eacc83

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