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 use Drupal\Core\Cache\CacheableDependencyInterface;
|
Chris@18
|
7 use Drupal\Core\Cache\CacheableDependencyTrait;
|
Chris@18
|
8 use Drupal\Core\Cache\CacheableMetadata;
|
Chris@18
|
9 use Drupal\Core\Url;
|
Chris@18
|
10
|
Chris@18
|
11 /**
|
Chris@18
|
12 * Represents an RFC8288 based link.
|
Chris@18
|
13 *
|
Chris@18
|
14 * @internal JSON:API maintains no PHP API. The API is the HTTP API. This class
|
Chris@18
|
15 * may change at any time and could break any dependencies on it.
|
Chris@18
|
16 *
|
Chris@18
|
17 * @see https://www.drupal.org/project/jsonapi/issues/3032787
|
Chris@18
|
18 * @see jsonapi.api.php
|
Chris@18
|
19 *
|
Chris@18
|
20 * @see https://tools.ietf.org/html/rfc8288
|
Chris@18
|
21 */
|
Chris@18
|
22 final class Link implements CacheableDependencyInterface {
|
Chris@18
|
23
|
Chris@18
|
24 use CacheableDependencyTrait;
|
Chris@18
|
25
|
Chris@18
|
26 /**
|
Chris@18
|
27 * The link URI.
|
Chris@18
|
28 *
|
Chris@18
|
29 * @var \Drupal\Core\Url
|
Chris@18
|
30 */
|
Chris@18
|
31 protected $uri;
|
Chris@18
|
32
|
Chris@18
|
33 /**
|
Chris@18
|
34 * The URI, as a string.
|
Chris@18
|
35 *
|
Chris@18
|
36 * @var string
|
Chris@18
|
37 */
|
Chris@18
|
38 protected $href;
|
Chris@18
|
39
|
Chris@18
|
40 /**
|
Chris@18
|
41 * The link relation types.
|
Chris@18
|
42 *
|
Chris@18
|
43 * @var string[]
|
Chris@18
|
44 */
|
Chris@18
|
45 protected $rel;
|
Chris@18
|
46
|
Chris@18
|
47 /**
|
Chris@18
|
48 * The link target attributes.
|
Chris@18
|
49 *
|
Chris@18
|
50 * @var string[]
|
Chris@18
|
51 * An associative array where the keys are the attribute keys and values are
|
Chris@18
|
52 * either string or an array of strings.
|
Chris@18
|
53 */
|
Chris@18
|
54 protected $attributes;
|
Chris@18
|
55
|
Chris@18
|
56 /**
|
Chris@18
|
57 * JSON:API Link constructor.
|
Chris@18
|
58 *
|
Chris@18
|
59 * @param \Drupal\Core\Cache\CacheableMetadata $cacheability
|
Chris@18
|
60 * Any cacheability metadata associated with the link. For example, a
|
Chris@18
|
61 * 'call-to-action' link might reference a registration resource if an event
|
Chris@18
|
62 * has vacancies or a wait-list resource otherwise. Therefore, the link's
|
Chris@18
|
63 * cacheability might be depend on a certain entity's values other than the
|
Chris@18
|
64 * entity on which the link will appear.
|
Chris@18
|
65 * @param \Drupal\Core\Url $url
|
Chris@18
|
66 * The Url object for the link.
|
Chris@18
|
67 * @param string[] $link_relation_types
|
Chris@18
|
68 * An array of registered or extension RFC8288 link relation types.
|
Chris@18
|
69 * @param array $target_attributes
|
Chris@18
|
70 * An associative array of target attributes for the link.
|
Chris@18
|
71 *
|
Chris@18
|
72 * @see https://tools.ietf.org/html/rfc8288#section-2.1
|
Chris@18
|
73 */
|
Chris@18
|
74 public function __construct(CacheableMetadata $cacheability, Url $url, array $link_relation_types, array $target_attributes = []) {
|
Chris@18
|
75 // @todo: uncomment the extra assertion below when JSON:API begins to use its own extension relation types.
|
Chris@18
|
76 assert(/* !empty($link_relation_types) && */Inspector::assertAllStrings($link_relation_types));
|
Chris@18
|
77 assert(Inspector::assertAllStrings(array_keys($target_attributes)));
|
Chris@18
|
78 assert(Inspector::assertAll(function ($target_attribute_value) {
|
Chris@18
|
79 return is_string($target_attribute_value)
|
Chris@18
|
80 || is_array($target_attribute_value)
|
Chris@18
|
81 && Inspector::assertAllStrings($target_attribute_value);
|
Chris@18
|
82 }, array_values($target_attributes)));
|
Chris@18
|
83 $generated_url = $url->setAbsolute()->toString(TRUE);
|
Chris@18
|
84 $this->href = $generated_url->getGeneratedUrl();
|
Chris@18
|
85 $this->uri = $url;
|
Chris@18
|
86 $this->rel = $link_relation_types;
|
Chris@18
|
87 $this->attributes = $target_attributes;
|
Chris@18
|
88 $this->setCacheability($cacheability->addCacheableDependency($generated_url));
|
Chris@18
|
89 }
|
Chris@18
|
90
|
Chris@18
|
91 /**
|
Chris@18
|
92 * Gets the link's URI.
|
Chris@18
|
93 *
|
Chris@18
|
94 * @return \Drupal\Core\Url
|
Chris@18
|
95 * The link's URI as a Url object.
|
Chris@18
|
96 */
|
Chris@18
|
97 public function getUri() {
|
Chris@18
|
98 return $this->uri;
|
Chris@18
|
99 }
|
Chris@18
|
100
|
Chris@18
|
101 /**
|
Chris@18
|
102 * Gets the link's URI as a string.
|
Chris@18
|
103 *
|
Chris@18
|
104 * @return string
|
Chris@18
|
105 * The link's URI as a string.
|
Chris@18
|
106 */
|
Chris@18
|
107 public function getHref() {
|
Chris@18
|
108 return $this->href;
|
Chris@18
|
109 }
|
Chris@18
|
110
|
Chris@18
|
111 /**
|
Chris@18
|
112 * Gets the link's relation types.
|
Chris@18
|
113 *
|
Chris@18
|
114 * @return string[]
|
Chris@18
|
115 * The link's relation types.
|
Chris@18
|
116 */
|
Chris@18
|
117 public function getLinkRelationTypes() {
|
Chris@18
|
118 return $this->rel;
|
Chris@18
|
119 }
|
Chris@18
|
120
|
Chris@18
|
121 /**
|
Chris@18
|
122 * Gets the link's target attributes.
|
Chris@18
|
123 *
|
Chris@18
|
124 * @return string[]
|
Chris@18
|
125 * The link's target attributes.
|
Chris@18
|
126 */
|
Chris@18
|
127 public function getTargetAttributes() {
|
Chris@18
|
128 return $this->attributes;
|
Chris@18
|
129 }
|
Chris@18
|
130
|
Chris@18
|
131 /**
|
Chris@18
|
132 * Compares two links by their href.
|
Chris@18
|
133 *
|
Chris@18
|
134 * @param \Drupal\jsonapi\JsonApiResource\Link $a
|
Chris@18
|
135 * The first link.
|
Chris@18
|
136 * @param \Drupal\jsonapi\JsonApiResource\Link $b
|
Chris@18
|
137 * The second link.
|
Chris@18
|
138 *
|
Chris@18
|
139 * @return int
|
Chris@18
|
140 * The result of strcmp() on the links' hrefs.
|
Chris@18
|
141 */
|
Chris@18
|
142 public static function compare(Link $a, Link $b) {
|
Chris@18
|
143 return strcmp($a->getHref(), $b->getHref());
|
Chris@18
|
144 }
|
Chris@18
|
145
|
Chris@18
|
146 /**
|
Chris@18
|
147 * Merges two link objects' relation types and target attributes.
|
Chris@18
|
148 *
|
Chris@18
|
149 * The links must share the same URI.
|
Chris@18
|
150 *
|
Chris@18
|
151 * @param \Drupal\jsonapi\JsonApiResource\Link $a
|
Chris@18
|
152 * The first link.
|
Chris@18
|
153 * @param \Drupal\jsonapi\JsonApiResource\Link $b
|
Chris@18
|
154 * The second link.
|
Chris@18
|
155 *
|
Chris@18
|
156 * @return static
|
Chris@18
|
157 * A new JSON:API Link object with the link relation type and target
|
Chris@18
|
158 * attributes merged.
|
Chris@18
|
159 */
|
Chris@18
|
160 public static function merge(Link $a, Link $b) {
|
Chris@18
|
161 assert(static::compare($a, $b) === 0);
|
Chris@18
|
162 $merged_rels = array_unique(array_merge($a->getLinkRelationTypes(), $b->getLinkRelationTypes()));
|
Chris@18
|
163 $merged_attributes = $a->getTargetAttributes();
|
Chris@18
|
164 foreach ($b->getTargetAttributes() as $key => $value) {
|
Chris@18
|
165 if (isset($merged_attributes[$key])) {
|
Chris@18
|
166 // The attribute values can be either a string or an array of strings.
|
Chris@18
|
167 $value = array_unique(array_merge(
|
Chris@18
|
168 is_string($merged_attributes[$key]) ? [$merged_attributes[$key]] : $merged_attributes[$key],
|
Chris@18
|
169 is_string($value) ? [$value] : $value
|
Chris@18
|
170 ));
|
Chris@18
|
171 }
|
Chris@18
|
172 $merged_attributes[$key] = count($value) === 1 ? reset($value) : $value;
|
Chris@18
|
173 }
|
Chris@18
|
174 $merged_cacheability = (new CacheableMetadata())->addCacheableDependency($a)->addCacheableDependency($b);
|
Chris@18
|
175 return new static($merged_cacheability, $a->getUri(), $merged_rels, $merged_attributes);
|
Chris@18
|
176 }
|
Chris@18
|
177
|
Chris@18
|
178 }
|