1 | <?php |
||
82 | class RelationFactory |
||
83 | { |
||
84 | /** |
||
85 | * URL component key |
||
86 | * |
||
87 | * @string |
||
88 | */ |
||
89 | const PARSE_URL = 'url'; |
||
90 | /** |
||
91 | * Label component key |
||
92 | * |
||
93 | * @string |
||
94 | */ |
||
95 | const PARSE_LABEL = 'label'; |
||
96 | /** |
||
97 | * Email component key |
||
98 | * |
||
99 | * @string |
||
100 | */ |
||
101 | const PARSE_EMAIL = 'email'; |
||
102 | /** |
||
103 | * Component relation coupling |
||
104 | * |
||
105 | * @string |
||
106 | */ |
||
107 | const PARSE_COUPLING = 'coupling'; |
||
108 | /** |
||
109 | * Relation types |
||
110 | * |
||
111 | * @var array |
||
112 | */ |
||
113 | public static $relationTypes = [ |
||
114 | ContributesRelation::TYPE => ContributesRelation::class, |
||
115 | ContributedByRelation::TYPE => ContributedByRelation::class, |
||
116 | EmbedsRelation::TYPE => EmbedsRelation::class, |
||
117 | EmbeddedByRelation::TYPE => EmbeddedByRelation::class, |
||
118 | LikesRelation::TYPE => LikesRelation::class, |
||
119 | LikedByRelation::TYPE => LikedByRelation::class, |
||
120 | RefersToRelation::TYPE => RefersToRelation::class, |
||
121 | ReferredByRelation::TYPE => ReferredByRelation::class, |
||
122 | RepliesToRelation::TYPE => RepliesToRelation::class, |
||
123 | RepliedByRelation::TYPE => RepliedByRelation::class, |
||
124 | RepostsRelation::TYPE => RepostsRelation::class, |
||
125 | RepostedByRelation::TYPE => RepostedByRelation::class, |
||
126 | SyndicatedToRelation::TYPE => SyndicatedToRelation::class, |
||
127 | SyndicatedFromRelation::TYPE => SyndicatedFromRelation::class, |
||
128 | ]; |
||
129 | /** |
||
130 | * Proxy relation types |
||
131 | * |
||
132 | * @var array |
||
133 | */ |
||
134 | public static $proxyRelationTypes = [ |
||
135 | ContributesRelation::TYPE => ContributesProxyRelation::class, |
||
136 | ContributedByRelation::TYPE => ContributedByProxyRelation::class, |
||
137 | EmbedsRelation::TYPE => EmbedsProxyRelation::class, |
||
138 | EmbeddedByRelation::TYPE => EmbeddedByProxyRelation::class, |
||
139 | LikesRelation::TYPE => LikesProxyRelation::class, |
||
140 | LikedByRelation::TYPE => LikedByProxyRelation::class, |
||
141 | RefersToRelation::TYPE => RefersToProxyRelation::class, |
||
142 | ReferredByRelation::TYPE => ReferredByProxyRelation::class, |
||
143 | RepliesToRelation::TYPE => RepliesToProxyRelation::class, |
||
144 | RepliedByRelation::TYPE => RepliedByProxyRelation::class, |
||
145 | RepostsRelation::TYPE => RepostsProxyRelation::class, |
||
146 | RepostedByRelation::TYPE => RepostedByProxyRelation::class, |
||
147 | SyndicatedToRelation::TYPE => SyndicatedToProxyRelation::class, |
||
148 | SyndicatedFromRelation::TYPE => SyndicatedFromProxyRelation::class, |
||
149 | ]; |
||
150 | |||
151 | /** |
||
152 | * Parse a relation serialization and instantiate the relation |
||
153 | * |
||
154 | * @param string $relationType Relation type |
||
155 | * @param string $relation Relation serialization |
||
156 | * @param RepositoryInterface $contextRepository Context repository |
||
157 | * @return RelationInterface Relation object |
||
158 | */ |
||
159 | 41 | public static function createFromString($relationType, $relation, RepositoryInterface $contextRepository) |
|
160 | { |
||
161 | // Validate the relation type |
||
162 | 41 | self::validateRelationType($relationType); |
|
163 | |||
164 | // Parse the relation string |
||
165 | 41 | $relationComponents = self::parseRelationString($relation, $contextRepository); |
|
166 | 38 | $isProxyRelation = $relationComponents[self::PARSE_URL] instanceof ApparatUrl; |
|
167 | |||
168 | // Create the relation instance |
||
169 | 38 | return Kernel::create( |
|
170 | 38 | $isProxyRelation ? self::$proxyRelationTypes[$relationType] : self::$relationTypes[$relationType], |
|
171 | array_values($relationComponents) |
||
172 | ); |
||
173 | } |
||
174 | |||
175 | /** |
||
176 | * Validate a relation type |
||
177 | * |
||
178 | * @param string $relationType Relation type |
||
179 | * @throws InvalidArgumentException If the relation type is invalid |
||
180 | */ |
||
181 | 45 | public static function validateRelationType($relationType) |
|
182 | { |
||
183 | // If the relation type is invalid |
||
184 | 45 | if (empty($relationType) || empty(self::$relationTypes[$relationType])) { |
|
185 | 1 | throw new OutOfBoundsException( |
|
186 | 1 | sprintf('Invalid object relation type "%s"', $relationType), |
|
187 | 1 | OutOfBoundsException::INVALID_OBJECT_RELATION_TYPE |
|
188 | ); |
||
189 | } |
||
190 | 45 | } |
|
191 | |||
192 | /** |
||
193 | * Parse a relation serialization and instantiate the relation object |
||
194 | * |
||
195 | * @param string $relation Relation serialization |
||
196 | * @param RepositoryInterface $contextRepository Context repository |
||
197 | * @return array Parsed relation components |
||
198 | * @throws InvalidArgumentException If the email component has already been registered |
||
199 | * @throws InvalidArgumentException If the URL component has already been registered |
||
200 | */ |
||
201 | 41 | protected static function parseRelationString($relation, RepositoryInterface $contextRepository) |
|
202 | { |
||
203 | $parsed = [ |
||
204 | 41 | self::PARSE_URL => null, |
|
205 | 41 | self::PARSE_LABEL => null, |
|
206 | 41 | self::PARSE_EMAIL => null, |
|
207 | 41 | self::PARSE_COUPLING => RelationInterface::LOOSE_COUPLING, |
|
208 | ]; |
||
209 | |||
210 | // Split the relation string and parse the components |
||
211 | 41 | foreach (preg_split('%\s+%', $relation) as $relationComponent) { |
|
212 | // If it's an email component |
||
213 | 41 | if (!strncmp('<', $relationComponent, 1)) { |
|
214 | // If the email component has already been registered |
||
215 | 34 | if (!empty($parsed[self::PARSE_EMAIL])) { |
|
216 | 1 | throw new InvalidArgumentException( |
|
217 | 1 | sprintf('Repeated relation email component "%s" not allowed', self::PARSE_EMAIL), |
|
218 | 1 | InvalidArgumentException::REPEATED_RELATION_COMPONENT_NOT_ALLOWED |
|
219 | ); |
||
220 | } |
||
221 | |||
222 | 34 | $parsed[self::PARSE_EMAIL] = self::parseRelationEmail($relationComponent); |
|
223 | 33 | continue; |
|
224 | } |
||
225 | |||
226 | // Next: Try to parse it as URL |
||
227 | try { |
||
228 | 39 | $parsed[self::PARSE_COUPLING] = intval($parsed[self::PARSE_COUPLING]); |
|
229 | 39 | $url = self::parseRelationUrl( |
|
230 | $relationComponent, |
||
231 | 39 | $parsed[self::PARSE_COUPLING], |
|
232 | $contextRepository |
||
233 | ); |
||
234 | |||
235 | // If the URL component has already been registered |
||
236 | 33 | if (!empty($parsed[self::PARSE_URL])) { |
|
237 | 1 | throw new InvalidArgumentException( |
|
238 | 1 | sprintf('Repeated relation url component "%s" not allowed', self::PARSE_URL), |
|
239 | 1 | InvalidArgumentException::REPEATED_RELATION_COMPONENT_NOT_ALLOWED |
|
240 | ); |
||
241 | } |
||
242 | |||
243 | 33 | $parsed[self::PARSE_URL] = $url; |
|
244 | |||
245 | // Else: Process as label component |
||
246 | 39 | } catch (\Exception $e) { |
|
247 | // If it's a repeated URL component |
||
248 | 39 | if (($e instanceof InvalidArgumentException) |
|
249 | 39 | && ($e->getCode() == InvalidArgumentException::REPEATED_RELATION_COMPONENT_NOT_ALLOWED) |
|
250 | ) { |
||
251 | 1 | throw $e; |
|
252 | } |
||
253 | |||
254 | 39 | $parsed[self::PARSE_LABEL] = trim($parsed[self::PARSE_LABEL].' '.$relationComponent); |
|
255 | } |
||
256 | } |
||
257 | |||
258 | 38 | return $parsed; |
|
259 | } |
||
260 | |||
261 | /** |
||
262 | * Parse and validate a relation email address component |
||
263 | * |
||
264 | * @param string $relationEmail Email address |
||
265 | * @return string Valid email address |
||
266 | * @throws InvalidArgumentException If the email address is invalid |
||
267 | */ |
||
268 | 34 | protected static function parseRelationEmail($relationEmail) |
|
280 | |||
281 | /** |
||
282 | * Parse and instantiate a relation URL |
||
283 | * |
||
284 | * @param string $url URL |
||
285 | * @param int $coupling Strong coupling |
||
286 | * @param RepositoryInterface $contextRepository Context repository |
||
287 | * @return Url URL |
||
288 | * @throws InvalidArgumentException If the relation URL is invalid |
||
289 | */ |
||
290 | 39 | protected static function parseRelationUrl($url, &$coupling, RepositoryInterface $contextRepository) |
|
318 | } |
||
319 |