1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/* |
4
|
|
|
* This file is part of the API Platform project. |
5
|
|
|
* |
6
|
|
|
* (c) Kévin Dunglas <[email protected]> |
7
|
|
|
* |
8
|
|
|
* For the full copyright and license information, please view the LICENSE |
9
|
|
|
* file that was distributed with this source code. |
10
|
|
|
*/ |
11
|
|
|
|
12
|
|
|
declare(strict_types=1); |
13
|
|
|
|
14
|
|
|
namespace ApiPlatform\Core\Hydra\Serializer; |
15
|
|
|
|
16
|
|
|
use ApiPlatform\Core\Api\OperationMethodResolverInterface; |
17
|
|
|
use ApiPlatform\Core\Api\OperationType; |
18
|
|
|
use ApiPlatform\Core\Api\ResourceClassResolverInterface; |
19
|
|
|
use ApiPlatform\Core\Api\UrlGeneratorInterface; |
20
|
|
|
use ApiPlatform\Core\Documentation\Documentation; |
21
|
|
|
use ApiPlatform\Core\JsonLd\ContextBuilderInterface; |
22
|
|
|
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; |
23
|
|
|
use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; |
24
|
|
|
use ApiPlatform\Core\Metadata\Property\PropertyMetadata; |
25
|
|
|
use ApiPlatform\Core\Metadata\Property\SubresourceMetadata; |
26
|
|
|
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; |
27
|
|
|
use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; |
28
|
|
|
use Symfony\Component\PropertyInfo\Type; |
29
|
|
|
use Symfony\Component\Serializer\Normalizer\NormalizerInterface; |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* Creates a machine readable Hydra API documentation. |
33
|
|
|
* |
34
|
|
|
* @author Kévin Dunglas <[email protected]> |
35
|
|
|
*/ |
36
|
|
|
final class DocumentationNormalizer implements NormalizerInterface |
37
|
|
|
{ |
38
|
|
|
const FORMAT = 'jsonld'; |
39
|
|
|
|
40
|
|
|
private $resourceMetadataFactory; |
41
|
|
|
private $propertyNameCollectionFactory; |
42
|
|
|
private $propertyMetadataFactory; |
43
|
|
|
private $resourceClassResolver; |
44
|
|
|
private $operationMethodResolver; |
45
|
|
|
private $urlGenerator; |
46
|
|
|
|
47
|
|
View Code Duplication |
public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ResourceClassResolverInterface $resourceClassResolver, OperationMethodResolverInterface $operationMethodResolver, UrlGeneratorInterface $urlGenerator) |
|
|
|
|
48
|
|
|
{ |
49
|
|
|
$this->resourceMetadataFactory = $resourceMetadataFactory; |
50
|
|
|
$this->propertyNameCollectionFactory = $propertyNameCollectionFactory; |
51
|
|
|
$this->propertyMetadataFactory = $propertyMetadataFactory; |
52
|
|
|
$this->resourceClassResolver = $resourceClassResolver; |
53
|
|
|
$this->operationMethodResolver = $operationMethodResolver; |
54
|
|
|
$this->urlGenerator = $urlGenerator; |
55
|
|
|
} |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* {@inheritdoc} |
59
|
|
|
*/ |
60
|
|
|
public function normalize($object, $format = null, array $context = []) |
61
|
|
|
{ |
62
|
|
|
$classes = []; |
63
|
|
|
$entrypointProperties = []; |
64
|
|
|
|
65
|
|
|
foreach ($object->getResourceNameCollection() as $resourceClass) { |
66
|
|
|
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); |
67
|
|
|
$shortName = $resourceMetadata->getShortName(); |
68
|
|
|
$prefixedShortName = $resourceMetadata->getIri() ?? "#$shortName"; |
69
|
|
|
|
70
|
|
|
$this->populateEntrypointProperties($resourceClass, $resourceMetadata, $shortName, $prefixedShortName, $entrypointProperties); |
71
|
|
|
$classes[] = $this->getClass($resourceClass, $resourceMetadata, $shortName, $prefixedShortName); |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
return $this->computeDoc($object, $this->getClasses($entrypointProperties, $classes)); |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* Populates entrypoint properties. |
79
|
|
|
* |
80
|
|
|
* @param string $resourceClass |
81
|
|
|
* @param ResourceMetadata $resourceMetadata |
82
|
|
|
* @param string $shortName |
83
|
|
|
* @param string $prefixedShortName |
84
|
|
|
* @param array $entrypointProperties |
85
|
|
|
*/ |
86
|
|
|
private function populateEntrypointProperties(string $resourceClass, ResourceMetadata $resourceMetadata, string $shortName, string $prefixedShortName, array &$entrypointProperties) |
87
|
|
|
{ |
88
|
|
|
$hydraCollectionOperations = $this->getHydraOperations($resourceClass, $resourceMetadata, $prefixedShortName, true); |
89
|
|
|
if (empty($hydraCollectionOperations)) { |
90
|
|
|
return; |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
$entrypointProperties[] = [ |
94
|
|
|
'@type' => 'hydra:SupportedProperty', |
95
|
|
|
'hydra:property' => [ |
96
|
|
|
'@id' => sprintf('#Entrypoint/%s', lcfirst($shortName)), |
97
|
|
|
'@type' => 'hydra:Link', |
98
|
|
|
'domain' => '#Entrypoint', |
99
|
|
|
'rdfs:label' => "The collection of $shortName resources", |
100
|
|
|
'rdfs:range' => [ |
101
|
|
|
'hydra:Collection', |
102
|
|
|
[ |
103
|
|
|
'owl:equivalentClass' => [ |
104
|
|
|
'owl:onProperty' => 'hydra:member', |
105
|
|
|
'owl:allValuesFrom' => "#$shortName", |
106
|
|
|
], |
107
|
|
|
], |
108
|
|
|
], |
109
|
|
|
'hydra:supportedOperation' => $hydraCollectionOperations, |
110
|
|
|
], |
111
|
|
|
'hydra:title' => "The collection of $shortName resources", |
112
|
|
|
'hydra:readable' => true, |
113
|
|
|
'hydra:writable' => false, |
114
|
|
|
]; |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
/** |
118
|
|
|
* Gets a Hydra class. |
119
|
|
|
* |
120
|
|
|
* @param string $resourceClass |
121
|
|
|
* @param ResourceMetadata $resourceMetadata |
122
|
|
|
* @param string $shortName |
123
|
|
|
* @param string $prefixedShortName |
124
|
|
|
* |
125
|
|
|
* @return array |
126
|
|
|
*/ |
127
|
|
|
private function getClass(string $resourceClass, ResourceMetadata $resourceMetadata, string $shortName, string $prefixedShortName): array |
128
|
|
|
{ |
129
|
|
|
$class = [ |
130
|
|
|
'@id' => $prefixedShortName, |
131
|
|
|
'@type' => 'hydra:Class', |
132
|
|
|
'rdfs:label' => $shortName, |
133
|
|
|
'hydra:title' => $shortName, |
134
|
|
|
'hydra:supportedProperty' => $this->getHydraProperties($resourceClass, $resourceMetadata, $shortName, $prefixedShortName), |
135
|
|
|
'hydra:supportedOperation' => $this->getHydraOperations($resourceClass, $resourceMetadata, $prefixedShortName, false), |
136
|
|
|
]; |
137
|
|
|
|
138
|
|
|
if (null !== $description = $resourceMetadata->getDescription()) { |
139
|
|
|
$class['hydra:description'] = $description; |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
return $class; |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
/** |
146
|
|
|
* Gets the context for the property name factory. |
147
|
|
|
* |
148
|
|
|
* @param ResourceMetadata $resourceMetadata |
149
|
|
|
* |
150
|
|
|
* @return array |
151
|
|
|
*/ |
152
|
|
|
private function getPropertyNameCollectionFactoryContext(ResourceMetadata $resourceMetadata): array |
153
|
|
|
{ |
154
|
|
|
$attributes = $resourceMetadata->getAttributes(); |
155
|
|
|
$context = []; |
156
|
|
|
|
157
|
|
View Code Duplication |
if (isset($attributes['normalization_context']['groups'])) { |
|
|
|
|
158
|
|
|
$context['serializer_groups'] = $attributes['normalization_context']['groups']; |
159
|
|
|
} |
160
|
|
|
|
161
|
|
View Code Duplication |
if (isset($attributes['denormalization_context']['groups'])) { |
|
|
|
|
162
|
|
|
if (isset($context['serializer_groups'])) { |
163
|
|
|
foreach ($attributes['denormalization_context']['groups'] as $groupName) { |
164
|
|
|
$context['serializer_groups'][] = $groupName; |
165
|
|
|
} |
166
|
|
|
} else { |
167
|
|
|
$context['serializer_groups'] = $attributes['denormalization_context']['groups']; |
168
|
|
|
} |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
return $context; |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
/** |
175
|
|
|
* Gets Hydra properties. |
176
|
|
|
* |
177
|
|
|
* @param string $resourceClass |
178
|
|
|
* @param ResourceMetadata $resourceMetadata |
179
|
|
|
* @param string $shortName |
180
|
|
|
* @param string $prefixedShortName |
181
|
|
|
* |
182
|
|
|
* @return array |
183
|
|
|
*/ |
184
|
|
|
private function getHydraProperties(string $resourceClass, ResourceMetadata $resourceMetadata, string $shortName, string $prefixedShortName): array |
185
|
|
|
{ |
186
|
|
|
$properties = []; |
187
|
|
|
foreach ($this->propertyNameCollectionFactory->create($resourceClass, $this->getPropertyNameCollectionFactoryContext($resourceMetadata)) as $propertyName) { |
188
|
|
|
$propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName); |
189
|
|
|
if (true === $propertyMetadata->isIdentifier() && false === $propertyMetadata->isWritable()) { |
190
|
|
|
continue; |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
$properties[] = $this->getProperty($propertyMetadata, $propertyName, $prefixedShortName, $shortName); |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
return $properties; |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
/** |
200
|
|
|
* Gets Hydra operations. |
201
|
|
|
* |
202
|
|
|
* @param string $resourceClass |
203
|
|
|
* @param ResourceMetadata $resourceMetadata |
204
|
|
|
* @param string $prefixedShortName |
205
|
|
|
* @param bool $collection |
206
|
|
|
* |
207
|
|
|
* @return array |
208
|
|
|
*/ |
209
|
|
|
private function getHydraOperations(string $resourceClass, ResourceMetadata $resourceMetadata, string $prefixedShortName, bool $collection): array |
210
|
|
|
{ |
211
|
|
|
if (null === $operations = $collection ? $resourceMetadata->getCollectionOperations() : $resourceMetadata->getItemOperations()) { |
212
|
|
|
return []; |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
$hydraOperations = []; |
216
|
|
|
foreach ($operations as $operationName => $operation) { |
217
|
|
|
$hydraOperations[] = $this->getHydraOperation($resourceClass, $resourceMetadata, $operationName, $operation, $prefixedShortName, $collection ? OperationType::COLLECTION : OperationType::ITEM); |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
foreach ($this->propertyNameCollectionFactory->create($resourceClass, $this->getPropertyNameCollectionFactoryContext($resourceMetadata)) as $propertyName) { |
221
|
|
|
$propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName); |
222
|
|
|
|
223
|
|
|
if (!$propertyMetadata->hasSubresource()) { |
224
|
|
|
continue; |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
$subresourceMetadata = $this->resourceMetadataFactory->create($propertyMetadata->getSubresource()->getResourceClass()); |
228
|
|
|
$prefixedShortName = "#{$subresourceMetadata->getShortName()}"; |
229
|
|
|
|
230
|
|
|
$hydraOperations[] = $this->getHydraOperation($resourceClass, $subresourceMetadata, $operationName, $operation, $prefixedShortName, OperationType::SUBRESOURCE, $propertyMetadata->getSubresource()); |
|
|
|
|
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
return $hydraOperations; |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
/** |
237
|
|
|
* Gets and populates if applicable a Hydra operation. |
238
|
|
|
* |
239
|
|
|
* @param string $resourceClass |
240
|
|
|
* @param ResourceMetadata $resourceMetadata |
241
|
|
|
* @param string $operationName |
242
|
|
|
* @param array $operation |
243
|
|
|
* @param string $prefixedShortName |
244
|
|
|
* @param string $operationType |
245
|
|
|
* @param SubresourceMetadata $operationType |
246
|
|
|
* |
247
|
|
|
* @return array |
248
|
|
|
*/ |
249
|
|
|
private function getHydraOperation(string $resourceClass, ResourceMetadata $resourceMetadata, string $operationName, array $operation, string $prefixedShortName, string $operationType, SubresourceMetadata $subresourceMetadata = null): array |
250
|
|
|
{ |
251
|
|
|
if (OperationType::COLLECTION === $operationType) { |
252
|
|
|
$method = $this->operationMethodResolver->getCollectionOperationMethod($resourceClass, $operationName); |
253
|
|
|
} elseif (OperationType::ITEM === $operationType) { |
254
|
|
|
$method = $this->operationMethodResolver->getItemOperationMethod($resourceClass, $operationName); |
255
|
|
|
} else { |
256
|
|
|
$method = 'GET'; |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
$hydraOperation = $operation['hydra_context'] ?? []; |
260
|
|
|
$shortName = $resourceMetadata->getShortName(); |
261
|
|
|
|
262
|
|
|
if ('GET' === $method && OperationType::COLLECTION === $operationType) { |
263
|
|
|
$hydraOperation += [ |
264
|
|
|
'@type' => ['hydra:Operation', 'schema:FindAction'], |
265
|
|
|
'hydra:title' => "Retrieves the collection of $shortName resources.", |
266
|
|
|
'returns' => 'hydra:Collection', |
267
|
|
|
]; |
268
|
|
|
} elseif ('GET' === $method && OperationType::SUBRESOURCE === $operationType) { |
269
|
|
|
$hydraOperation += [ |
270
|
|
|
'@type' => ['hydra:Operation', 'schema:FindAction'], |
271
|
|
|
'hydra:title' => $subresourceMetadata->isCollection() ? "Retrieves the collection of $shortName resources." : "Retrieves a $shortName resource.", |
|
|
|
|
272
|
|
|
'returns' => "#$shortName", |
273
|
|
|
]; |
274
|
|
View Code Duplication |
} elseif ('GET' === $method) { |
|
|
|
|
275
|
|
|
$hydraOperation += [ |
276
|
|
|
'@type' => ['hydra:Operation', 'schema:FindAction'], |
277
|
|
|
'hydra:title' => "Retrieves $shortName resource.", |
278
|
|
|
'returns' => $prefixedShortName, |
279
|
|
|
]; |
280
|
|
|
} elseif ('POST' === $method) { |
281
|
|
|
$hydraOperation += [ |
282
|
|
|
'@type' => ['hydra:Operation', 'schema:CreateAction'], |
283
|
|
|
'hydra:title' => "Creates a $shortName resource.", |
284
|
|
|
'returns' => $prefixedShortName, |
285
|
|
|
'expects' => $prefixedShortName, |
286
|
|
|
]; |
287
|
|
View Code Duplication |
} elseif ('PUT' === $method) { |
|
|
|
|
288
|
|
|
$hydraOperation += [ |
289
|
|
|
'@type' => ['hydra:Operation', 'schema:ReplaceAction'], |
290
|
|
|
'hydra:title' => "Replaces the $shortName resource.", |
291
|
|
|
'returns' => $prefixedShortName, |
292
|
|
|
'expects' => $prefixedShortName, |
293
|
|
|
]; |
294
|
|
|
} elseif ('DELETE' === $method) { |
295
|
|
|
$hydraOperation += [ |
296
|
|
|
'@type' => ['hydra:Operation', 'schema:DeleteAction'], |
297
|
|
|
'hydra:title' => "Deletes the $shortName resource.", |
298
|
|
|
'returns' => 'owl:Nothing', |
299
|
|
|
]; |
300
|
|
|
} |
301
|
|
|
|
302
|
|
|
$hydraOperation['hydra:method'] ?? $hydraOperation['hydra:method'] = $method; |
303
|
|
|
|
304
|
|
|
if (!isset($hydraOperation['rdfs:label']) && isset($hydraOperation['hydra:title'])) { |
305
|
|
|
$hydraOperation['rdfs:label'] = $hydraOperation['hydra:title']; |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
ksort($hydraOperation); |
309
|
|
|
|
310
|
|
|
return $hydraOperation; |
311
|
|
|
} |
312
|
|
|
|
313
|
|
|
/** |
314
|
|
|
* Gets the range of the property. |
315
|
|
|
* |
316
|
|
|
* @param PropertyMetadata $propertyMetadata |
317
|
|
|
* |
318
|
|
|
* @return string|null |
319
|
|
|
*/ |
320
|
|
|
private function getRange(PropertyMetadata $propertyMetadata) |
321
|
|
|
{ |
322
|
|
|
$jsonldContext = $propertyMetadata->getAttributes()['jsonld_context'] ?? []; |
323
|
|
|
|
324
|
|
|
if (isset($jsonldContext['@type'])) { |
325
|
|
|
return $jsonldContext['@type']; |
326
|
|
|
} |
327
|
|
|
|
328
|
|
|
if (null === $type = $propertyMetadata->getType()) { |
329
|
|
|
return null; |
330
|
|
|
} |
331
|
|
|
|
332
|
|
|
if ($type->isCollection() && null !== $collectionType = $type->getCollectionValueType()) { |
333
|
|
|
$type = $collectionType; |
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
switch ($type->getBuiltinType()) { |
337
|
|
|
case Type::BUILTIN_TYPE_STRING: |
338
|
|
|
return 'xmls:string'; |
339
|
|
|
case Type::BUILTIN_TYPE_INT: |
340
|
|
|
return 'xmls:integer'; |
341
|
|
|
case Type::BUILTIN_TYPE_FLOAT: |
|
|
|
|
342
|
|
|
return 'xmls:decimal'; |
343
|
|
|
case Type::BUILTIN_TYPE_BOOL: |
344
|
|
|
return 'xmls:boolean'; |
345
|
|
|
case Type::BUILTIN_TYPE_OBJECT: |
346
|
|
|
if (null === $className = $type->getClassName()) { |
347
|
|
|
return null; |
348
|
|
|
} |
349
|
|
|
|
350
|
|
|
if (is_a($className, \DateTimeInterface::class, true)) { |
351
|
|
|
return 'xmls:dateTime'; |
352
|
|
|
} |
353
|
|
|
|
354
|
|
|
if ($this->resourceClassResolver->isResourceClass($className)) { |
355
|
|
|
$resourceMetadata = $this->resourceMetadataFactory->create($className); |
356
|
|
|
|
357
|
|
|
return $resourceMetadata->getIri() ?? "#{$resourceMetadata->getShortName()}"; |
358
|
|
|
} |
359
|
|
|
break; |
360
|
|
|
} |
361
|
|
|
} |
362
|
|
|
|
363
|
|
|
/** |
364
|
|
|
* Builds the classes array. |
365
|
|
|
* |
366
|
|
|
* @param array $entrypointProperties |
367
|
|
|
* @param array $classes |
368
|
|
|
* |
369
|
|
|
* @return array |
370
|
|
|
*/ |
371
|
|
|
private function getClasses(array $entrypointProperties, array $classes): array |
372
|
|
|
{ |
373
|
|
|
$classes[] = [ |
374
|
|
|
'@id' => '#Entrypoint', |
375
|
|
|
'@type' => 'hydra:Class', |
376
|
|
|
'hydra:title' => 'The API entrypoint', |
377
|
|
|
'hydra:supportedProperty' => $entrypointProperties, |
378
|
|
|
'hydra:supportedOperation' => [ |
379
|
|
|
'@type' => 'hydra:Operation', |
380
|
|
|
'hydra:method' => 'GET', |
381
|
|
|
'rdfs:label' => 'The API entrypoint.', |
382
|
|
|
'returns' => '#EntryPoint', |
383
|
|
|
], |
384
|
|
|
]; |
385
|
|
|
|
386
|
|
|
// Constraint violation |
387
|
|
|
$classes[] = [ |
388
|
|
|
'@id' => '#ConstraintViolation', |
389
|
|
|
'@type' => 'hydra:Class', |
390
|
|
|
'hydra:title' => 'A constraint violation', |
391
|
|
|
'hydra:supportedProperty' => [ |
392
|
|
|
[ |
393
|
|
|
'@type' => 'hydra:SupportedProperty', |
394
|
|
|
'hydra:property' => [ |
395
|
|
|
'@id' => '#ConstraintViolation/propertyPath', |
396
|
|
|
'@type' => 'rdf:Property', |
397
|
|
|
'rdfs:label' => 'propertyPath', |
398
|
|
|
'domain' => '#ConstraintViolation', |
399
|
|
|
'range' => 'xmls:string', |
400
|
|
|
], |
401
|
|
|
'hydra:title' => 'propertyPath', |
402
|
|
|
'hydra:description' => 'The property path of the violation', |
403
|
|
|
'hydra:readable' => true, |
404
|
|
|
'hydra:writable' => false, |
405
|
|
|
], |
406
|
|
|
[ |
407
|
|
|
'@type' => 'hydra:SupportedProperty', |
408
|
|
|
'hydra:property' => [ |
409
|
|
|
'@id' => '#ConstraintViolation/message', |
410
|
|
|
'@type' => 'rdf:Property', |
411
|
|
|
'rdfs:label' => 'message', |
412
|
|
|
'domain' => '#ConstraintViolation', |
413
|
|
|
'range' => 'xmls:string', |
414
|
|
|
], |
415
|
|
|
'hydra:title' => 'message', |
416
|
|
|
'hydra:description' => 'The message associated with the violation', |
417
|
|
|
'hydra:readable' => true, |
418
|
|
|
'hydra:writable' => false, |
419
|
|
|
], |
420
|
|
|
], |
421
|
|
|
]; |
422
|
|
|
|
423
|
|
|
// Constraint violation list |
424
|
|
|
$classes[] = [ |
425
|
|
|
'@id' => '#ConstraintViolationList', |
426
|
|
|
'@type' => 'hydra:Class', |
427
|
|
|
'subClassOf' => 'hydra:Error', |
428
|
|
|
'hydra:title' => 'A constraint violation list', |
429
|
|
|
'hydra:supportedProperty' => [ |
430
|
|
|
[ |
431
|
|
|
'@type' => 'hydra:SupportedProperty', |
432
|
|
|
'hydra:property' => [ |
433
|
|
|
'@id' => '#ConstraintViolationList/violations', |
434
|
|
|
'@type' => 'rdf:Property', |
435
|
|
|
'rdfs:label' => 'violations', |
436
|
|
|
'domain' => '#ConstraintViolationList', |
437
|
|
|
'range' => '#ConstraintViolation', |
438
|
|
|
], |
439
|
|
|
'hydra:title' => 'violations', |
440
|
|
|
'hydra:description' => 'The violations', |
441
|
|
|
'hydra:readable' => true, |
442
|
|
|
'hydra:writable' => false, |
443
|
|
|
], |
444
|
|
|
], |
445
|
|
|
]; |
446
|
|
|
|
447
|
|
|
return $classes; |
448
|
|
|
} |
449
|
|
|
|
450
|
|
|
/** |
451
|
|
|
* Gets a property definition. |
452
|
|
|
* |
453
|
|
|
* @param PropertyMetadata $propertyMetadata |
454
|
|
|
* @param string $propertyName |
455
|
|
|
* @param string $prefixedShortName |
456
|
|
|
* @param string $shortName |
457
|
|
|
* |
458
|
|
|
* @return array |
459
|
|
|
*/ |
460
|
|
|
private function getProperty(PropertyMetadata $propertyMetadata, string $propertyName, string $prefixedShortName, string $shortName): array |
461
|
|
|
{ |
462
|
|
|
$propertyData = [ |
463
|
|
|
'@id' => $propertyMetadata->getIri() ?? "#$shortName/$propertyName", |
464
|
|
|
'@type' => $propertyMetadata->isReadableLink() ? 'rdf:Property' : 'hydra:Link', |
465
|
|
|
'rdfs:label' => $propertyName, |
466
|
|
|
'domain' => $prefixedShortName, |
467
|
|
|
]; |
468
|
|
|
|
469
|
|
|
$type = $propertyMetadata->getType(); |
470
|
|
|
|
471
|
|
|
if (null !== $type && !$type->isCollection() && (null !== $className = $type->getClassName()) && $this->resourceClassResolver->isResourceClass($className)) { |
472
|
|
|
$propertyData['owl:maxCardinality'] = 1; |
473
|
|
|
} |
474
|
|
|
|
475
|
|
|
$property = [ |
476
|
|
|
'@type' => 'hydra:SupportedProperty', |
477
|
|
|
'hydra:property' => $propertyData, |
478
|
|
|
'hydra:title' => $propertyName, |
479
|
|
|
'hydra:required' => $propertyMetadata->isRequired(), |
480
|
|
|
'hydra:readable' => $propertyMetadata->isReadable(), |
481
|
|
|
'hydra:writable' => $propertyMetadata->isWritable(), |
482
|
|
|
]; |
483
|
|
|
|
484
|
|
|
if (null !== $range = $this->getRange($propertyMetadata)) { |
485
|
|
|
$property['hydra:property']['range'] = $range; |
486
|
|
|
} |
487
|
|
|
|
488
|
|
|
if (null !== $description = $propertyMetadata->getDescription()) { |
489
|
|
|
$property['hydra:description'] = $description; |
490
|
|
|
} |
491
|
|
|
|
492
|
|
|
return $property; |
493
|
|
|
} |
494
|
|
|
|
495
|
|
|
/** |
496
|
|
|
* Computes the documentation. |
497
|
|
|
* |
498
|
|
|
* @param Documentation $object |
499
|
|
|
* @param array $classes |
500
|
|
|
* |
501
|
|
|
* @return array |
502
|
|
|
*/ |
503
|
|
|
private function computeDoc(Documentation $object, array $classes): array |
504
|
|
|
{ |
505
|
|
|
$doc = ['@context' => $this->getContext(), '@id' => $this->urlGenerator->generate('api_doc', ['_format' => self::FORMAT]), '@type' => 'hydra:ApiDocumentation']; |
506
|
|
|
|
507
|
|
|
if ('' !== $object->getTitle()) { |
508
|
|
|
$doc['hydra:title'] = $object->getTitle(); |
509
|
|
|
} |
510
|
|
|
|
511
|
|
|
if ('' !== $object->getDescription()) { |
512
|
|
|
$doc['hydra:description'] = $object->getDescription(); |
513
|
|
|
} |
514
|
|
|
|
515
|
|
|
$doc['hydra:entrypoint'] = $this->urlGenerator->generate('api_entrypoint'); |
516
|
|
|
$doc['hydra:supportedClass'] = $classes; |
517
|
|
|
|
518
|
|
|
return $doc; |
519
|
|
|
} |
520
|
|
|
|
521
|
|
|
/** |
522
|
|
|
* Builds the JSON-LD context for the API documentation. |
523
|
|
|
* |
524
|
|
|
* @return array |
525
|
|
|
*/ |
526
|
|
|
private function getContext(): array |
527
|
|
|
{ |
528
|
|
|
return [ |
529
|
|
|
'@vocab' => $this->urlGenerator->generate('api_doc', ['_format' => self::FORMAT], UrlGeneratorInterface::ABS_URL).'#', |
530
|
|
|
'hydra' => ContextBuilderInterface::HYDRA_NS, |
531
|
|
|
'rdf' => ContextBuilderInterface::RDF_NS, |
532
|
|
|
'rdfs' => ContextBuilderInterface::RDFS_NS, |
533
|
|
|
'xmls' => ContextBuilderInterface::XML_NS, |
534
|
|
|
'owl' => ContextBuilderInterface::OWL_NS, |
535
|
|
|
'schema' => ContextBuilderInterface::SCHEMA_ORG_NS, |
536
|
|
|
'domain' => ['@id' => 'rdfs:domain', '@type' => '@id'], |
537
|
|
|
'range' => ['@id' => 'rdfs:range', '@type' => '@id'], |
538
|
|
|
'subClassOf' => ['@id' => 'rdfs:subClassOf', '@type' => '@id'], |
539
|
|
|
'expects' => ['@id' => 'hydra:expects', '@type' => '@id'], |
540
|
|
|
'returns' => ['@id' => 'hydra:returns', '@type' => '@id'], |
541
|
|
|
]; |
542
|
|
|
} |
543
|
|
|
|
544
|
|
|
/** |
545
|
|
|
* {@inheritdoc} |
546
|
|
|
*/ |
547
|
|
|
public function supportsNormalization($data, $format = null, array $context = []) |
548
|
|
|
{ |
549
|
|
|
return self::FORMAT === $format && $data instanceof Documentation; |
550
|
|
|
} |
551
|
|
|
} |
552
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.