Passed
Push — master ( 535f96...009e86 )
by Kévin
04:28
created

DocumentationNormalizer::getHydraOperation()   F

Complexity

Conditions 25
Paths 300

Size

Total Lines 75
Code Lines 55

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 55
dl 0
loc 75
rs 2.0833
c 0
b 0
f 0
cc 25
nc 300
nop 7

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 ApiPlatform\Core\Operation\Factory\SubresourceOperationFactoryInterface;
29
use Symfony\Component\PropertyInfo\Type;
30
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
31
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
32
use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface;
33
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
34
35
/**
36
 * Creates a machine readable Hydra API documentation.
37
 *
38
 * @author Kévin Dunglas <[email protected]>
39
 */
40
final class DocumentationNormalizer implements NormalizerInterface, CacheableSupportsMethodInterface
41
{
42
    const FORMAT = 'jsonld';
43
44
    private $resourceMetadataFactory;
45
    private $propertyNameCollectionFactory;
46
    private $propertyMetadataFactory;
47
    private $resourceClassResolver;
48
    private $operationMethodResolver;
49
    private $urlGenerator;
50
    private $subresourceOperationFactory;
51
    private $nameConverter;
52
53
    public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ResourceClassResolverInterface $resourceClassResolver, OperationMethodResolverInterface $operationMethodResolver, UrlGeneratorInterface $urlGenerator, SubresourceOperationFactoryInterface $subresourceOperationFactory = null, NameConverterInterface $nameConverter = null)
54
    {
55
        $this->resourceMetadataFactory = $resourceMetadataFactory;
56
        $this->propertyNameCollectionFactory = $propertyNameCollectionFactory;
57
        $this->propertyMetadataFactory = $propertyMetadataFactory;
58
        $this->resourceClassResolver = $resourceClassResolver;
59
        $this->operationMethodResolver = $operationMethodResolver;
60
        $this->urlGenerator = $urlGenerator;
61
        $this->subresourceOperationFactory = $subresourceOperationFactory;
62
        $this->nameConverter = $nameConverter;
63
    }
64
65
    /**
66
     * {@inheritdoc}
67
     */
68
    public function normalize($object, $format = null, array $context = [])
69
    {
70
        $classes = [];
71
        $entrypointProperties = [];
72
73
        foreach ($object->getResourceNameCollection() as $resourceClass) {
74
            $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
75
            $shortName = $resourceMetadata->getShortName();
76
            $prefixedShortName = $resourceMetadata->getIri() ?? "#$shortName";
77
78
            $this->populateEntrypointProperties($resourceClass, $resourceMetadata, $shortName, $prefixedShortName, $entrypointProperties);
0 ignored issues
show
Bug introduced by
It seems like $shortName can also be of type null; however, parameter $shortName of ApiPlatform\Core\Hydra\S...eEntrypointProperties() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

78
            $this->populateEntrypointProperties($resourceClass, $resourceMetadata, /** @scrutinizer ignore-type */ $shortName, $prefixedShortName, $entrypointProperties);
Loading history...
79
            $classes[] = $this->getClass($resourceClass, $resourceMetadata, $shortName, $prefixedShortName, $context);
0 ignored issues
show
Bug introduced by
It seems like $shortName can also be of type null; however, parameter $shortName of ApiPlatform\Core\Hydra\S...nNormalizer::getClass() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

79
            $classes[] = $this->getClass($resourceClass, $resourceMetadata, /** @scrutinizer ignore-type */ $shortName, $prefixedShortName, $context);
Loading history...
80
        }
81
82
        return $this->computeDoc($object, $this->getClasses($entrypointProperties, $classes));
83
    }
84
85
    /**
86
     * Populates entrypoint properties.
87
     */
88
    private function populateEntrypointProperties(string $resourceClass, ResourceMetadata $resourceMetadata, string $shortName, string $prefixedShortName, array &$entrypointProperties)
89
    {
90
        $hydraCollectionOperations = $this->getHydraOperations($resourceClass, $resourceMetadata, $prefixedShortName, true);
91
        if (empty($hydraCollectionOperations)) {
92
            return;
93
        }
94
95
        $entrypointProperty = [
96
            '@type' => 'hydra:SupportedProperty',
97
            'hydra:property' => [
98
                '@id' => sprintf('#Entrypoint/%s', lcfirst($shortName)),
99
                '@type' => 'hydra:Link',
100
                'domain' => '#Entrypoint',
101
                'rdfs:label' => "The collection of $shortName resources",
102
                'rdfs:range' => [
103
                    ['@id' => 'hydra:Collection'],
104
                    [
105
                        'owl:equivalentClass' => [
106
                            'owl:onProperty' => ['@id' => 'hydra:member'],
107
                            'owl:allValuesFrom' => ['@id' => $prefixedShortName],
108
                        ],
109
                    ],
110
                ],
111
                'hydra:supportedOperation' => $hydraCollectionOperations,
112
            ],
113
            'hydra:title' => "The collection of $shortName resources",
114
            'hydra:readable' => true,
115
            'hydra:writable' => false,
116
        ];
117
118
        if ($resourceMetadata->getCollectionOperationAttribute('GET', 'deprecation_reason', null, true)) {
119
            $entrypointProperty['owl:deprecated'] = true;
120
        }
121
122
        $entrypointProperties[] = $entrypointProperty;
123
    }
124
125
    /**
126
     * Gets a Hydra class.
127
     */
128
    private function getClass(string $resourceClass, ResourceMetadata $resourceMetadata, string $shortName, string $prefixedShortName, array $context): array
129
    {
130
        $class = [
131
            '@id' => $prefixedShortName,
132
            '@type' => 'hydra:Class',
133
            'rdfs:label' => $shortName,
134
            'hydra:title' => $shortName,
135
            'hydra:supportedProperty' => $this->getHydraProperties($resourceClass, $resourceMetadata, $shortName, $prefixedShortName, $context),
136
            'hydra:supportedOperation' => $this->getHydraOperations($resourceClass, $resourceMetadata, $prefixedShortName, false),
137
        ];
138
139
        if (null !== $description = $resourceMetadata->getDescription()) {
140
            $class['hydra:description'] = $description;
141
        }
142
143
        if ($resourceMetadata->getAttribute('deprecation_reason')) {
144
            $class['owl:deprecated'] = true;
145
        }
146
147
        return $class;
148
    }
149
150
    /**
151
     * Gets the context for the property name factory.
152
     */
153
    private function getPropertyNameCollectionFactoryContext(ResourceMetadata $resourceMetadata): array
154
    {
155
        $attributes = $resourceMetadata->getAttributes();
156
        $context = [];
157
158
        if (isset($attributes['normalization_context'][AbstractNormalizer::GROUPS])) {
159
            $context['serializer_groups'] = (array) $attributes['normalization_context'][AbstractNormalizer::GROUPS];
160
        }
161
162
        if (!isset($attributes['denormalization_context'][AbstractNormalizer::GROUPS])) {
163
            return $context;
164
        }
165
166
        if (isset($context['serializer_groups'])) {
167
            foreach ((array) $attributes['denormalization_context'][AbstractNormalizer::GROUPS] as $groupName) {
168
                $context['serializer_groups'][] = $groupName;
169
            }
170
171
            return $context;
172
        }
173
174
        $context['serializer_groups'] = (array) $attributes['denormalization_context'][AbstractNormalizer::GROUPS];
175
176
        return $context;
177
    }
178
179
    /**
180
     * Gets Hydra properties.
181
     */
182
    private function getHydraProperties(string $resourceClass, ResourceMetadata $resourceMetadata, string $shortName, string $prefixedShortName, array $context): array
183
    {
184
        $classes = [];
185
        foreach ($resourceMetadata->getCollectionOperations() as $operationName => $operation) {
186
            if (false !== $class = $resourceMetadata->getCollectionOperationAttribute($operationName, 'input_class', $resourceClass, true)) {
187
                $classes[$class] = true;
188
            }
189
190
            if (false !== $class = $resourceMetadata->getCollectionOperationAttribute($operationName, 'output_class', $resourceClass, true)) {
191
                $classes[$class] = true;
192
            }
193
        }
194
195
        $properties = [];
196
        foreach ($classes as $class => $v) {
197
            foreach ($this->propertyNameCollectionFactory->create($class, $this->getPropertyNameCollectionFactoryContext($resourceMetadata)) as $propertyName) {
198
                $propertyMetadata = $this->propertyMetadataFactory->create($class, $propertyName);
199
                if (true === $propertyMetadata->isIdentifier() && false === $propertyMetadata->isWritable()) {
200
                    continue;
201
                }
202
203
                if ($this->nameConverter) {
204
                    $propertyName = $this->nameConverter->normalize($propertyName, $class, self::FORMAT, $context);
0 ignored issues
show
Unused Code introduced by
The call to Symfony\Component\Serial...rInterface::normalize() has too many arguments starting with $class. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

204
                    /** @scrutinizer ignore-call */ 
205
                    $propertyName = $this->nameConverter->normalize($propertyName, $class, self::FORMAT, $context);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
205
                }
206
207
                $properties[] = $this->getProperty($propertyMetadata, $propertyName, $prefixedShortName, $shortName);
208
            }
209
        }
210
211
        return $properties;
212
    }
213
214
    /**
215
     * Gets Hydra operations.
216
     */
217
    private function getHydraOperations(string $resourceClass, ResourceMetadata $resourceMetadata, string $prefixedShortName, bool $collection): array
218
    {
219
        if (null === $operations = $collection ? $resourceMetadata->getCollectionOperations() : $resourceMetadata->getItemOperations()) {
220
            return [];
221
        }
222
223
        $hydraOperations = [];
224
        foreach ($operations as $operationName => $operation) {
225
            $hydraOperations[] = $this->getHydraOperation($resourceClass, $resourceMetadata, $operationName, $operation, $prefixedShortName, $collection ? OperationType::COLLECTION : OperationType::ITEM);
226
        }
227
228
        if (null !== $this->subresourceOperationFactory) {
229
            foreach ($this->subresourceOperationFactory->create($resourceClass) as $operationId => $operation) {
230
                $subresourceMetadata = $this->resourceMetadataFactory->create($operation['resource_class']);
231
                $propertyMetadata = $this->propertyMetadataFactory->create(end($operation['identifiers'])[1], $operation['property']);
232
                $hydraOperations[] = $this->getHydraOperation($resourceClass, $subresourceMetadata, $operation['route_name'], $operation, "#{$subresourceMetadata->getShortName()}", OperationType::SUBRESOURCE, $propertyMetadata->getSubresource());
233
            }
234
        }
235
236
        return $hydraOperations;
237
    }
238
239
    /**
240
     * Gets and populates if applicable a Hydra operation.
241
     *
242
     * @param SubresourceMetadata $subresourceMetadata
243
     */
244
    private function getHydraOperation(string $resourceClass, ResourceMetadata $resourceMetadata, string $operationName, array $operation, string $prefixedShortName, string $operationType, SubresourceMetadata $subresourceMetadata = null): array
245
    {
246
        if (OperationType::COLLECTION === $operationType) {
247
            $method = $this->operationMethodResolver->getCollectionOperationMethod($resourceClass, $operationName);
248
        } elseif (OperationType::ITEM === $operationType) {
249
            $method = $this->operationMethodResolver->getItemOperationMethod($resourceClass, $operationName);
250
        } else {
251
            $method = 'GET';
252
        }
253
254
        $hydraOperation = $operation['hydra_context'] ?? [];
255
        if ($resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'deprecation_reason', null, true)) {
256
            $hydraOperation['owl:deprecated'] = true;
257
        }
258
259
        $shortName = $resourceMetadata->getShortName();
260
        $inputClass = $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'input_class', null, true);
261
        $outputClass = $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'output_class', null, true);
262
263
        if ('GET' === $method && OperationType::COLLECTION === $operationType) {
264
            $hydraOperation += [
265
                '@type' => ['hydra:Operation', 'schema:FindAction'],
266
                'hydra:title' => "Retrieves the collection of $shortName resources.",
267
                'returns' => 'hydra:Collection',
268
            ];
269
        } elseif ('GET' === $method && OperationType::SUBRESOURCE === $operationType) {
270
            $hydraOperation += [
271
                '@type' => ['hydra:Operation', 'schema:FindAction'],
272
                'hydra:title' => $subresourceMetadata && $subresourceMetadata->isCollection() ? "Retrieves the collection of $shortName resources." : "Retrieves a $shortName resource.",
273
                'returns' => false === $outputClass ? 'owl:Nothing' : "#$shortName",
274
            ];
275
        } elseif ('GET' === $method) {
276
            $hydraOperation += [
277
                '@type' => ['hydra:Operation', 'schema:FindAction'],
278
                'hydra:title' => "Retrieves $shortName resource.",
279
                'returns' => false === $outputClass ? 'owl:Nothing' : $prefixedShortName,
280
            ];
281
        } elseif ('PATCH' === $method) {
282
            $hydraOperation += [
283
                '@type' => 'hydra:Operation',
284
                'hydra:title' => "Updates the $shortName resource.",
285
                'returns' => false === $outputClass ? 'owl:Nothing' : $prefixedShortName,
286
                'expects' => false === $inputClass ? 'owl:Nothing' : $prefixedShortName,
287
            ];
288
        } elseif ('POST' === $method) {
289
            $hydraOperation += [
290
                '@type' => ['hydra:Operation', 'schema:CreateAction'],
291
                'hydra:title' => "Creates a $shortName resource.",
292
                'returns' => false === $outputClass ? 'owl:Nothing' : $prefixedShortName,
293
                'expects' => false === $inputClass ? 'owl:Nothing' : $prefixedShortName,
294
            ];
295
        } elseif ('PUT' === $method) {
296
            $hydraOperation += [
297
                '@type' => ['hydra:Operation', 'schema:ReplaceAction'],
298
                'hydra:title' => "Replaces the $shortName resource.",
299
                'returns' => false === $outputClass ? 'owl:Nothing' : $prefixedShortName,
300
                'expects' => false === $inputClass ? 'owl:Nothing' : $prefixedShortName,
301
            ];
302
        } elseif ('DELETE' === $method) {
303
            $hydraOperation += [
304
                '@type' => ['hydra:Operation', 'schema:DeleteAction'],
305
                'hydra:title' => "Deletes the $shortName resource.",
306
                'returns' => 'owl:Nothing',
307
            ];
308
        }
309
310
        $hydraOperation['hydra:method'] ?? $hydraOperation['hydra:method'] = $method;
311
312
        if (!isset($hydraOperation['rdfs:label']) && isset($hydraOperation['hydra:title'])) {
313
            $hydraOperation['rdfs:label'] = $hydraOperation['hydra:title'];
314
        }
315
316
        ksort($hydraOperation);
317
318
        return $hydraOperation;
319
    }
320
321
    /**
322
     * Gets the range of the property.
323
     *
324
     * @return string|null
325
     */
326
    private function getRange(PropertyMetadata $propertyMetadata)
327
    {
328
        $jsonldContext = $propertyMetadata->getAttributes()['jsonld_context'] ?? [];
329
330
        if (isset($jsonldContext['@type'])) {
331
            return $jsonldContext['@type'];
332
        }
333
334
        if (null === $type = $propertyMetadata->getType()) {
335
            return null;
336
        }
337
338
        if ($type->isCollection() && null !== $collectionType = $type->getCollectionValueType()) {
339
            $type = $collectionType;
340
        }
341
342
        switch ($type->getBuiltinType()) {
343
            case Type::BUILTIN_TYPE_STRING:
344
                return 'xmls:string';
345
            case Type::BUILTIN_TYPE_INT:
346
                return 'xmls:integer';
347
            case Type::BUILTIN_TYPE_FLOAT:
348
                return 'xmls:decimal';
349
            case Type::BUILTIN_TYPE_BOOL:
350
                return 'xmls:boolean';
351
            case Type::BUILTIN_TYPE_OBJECT:
352
                if (null === $className = $type->getClassName()) {
353
                    return null;
354
                }
355
356
                if (is_a($className, \DateTimeInterface::class, true)) {
357
                    return 'xmls:dateTime';
358
                }
359
360
                if ($this->resourceClassResolver->isResourceClass($className)) {
361
                    $resourceMetadata = $this->resourceMetadataFactory->create($className);
362
363
                    return $resourceMetadata->getIri() ?? "#{$resourceMetadata->getShortName()}";
364
                }
365
                break;
366
        }
367
368
        return null;
369
    }
370
371
    /**
372
     * Builds the classes array.
373
     */
374
    private function getClasses(array $entrypointProperties, array $classes): array
375
    {
376
        $classes[] = [
377
            '@id' => '#Entrypoint',
378
            '@type' => 'hydra:Class',
379
            'hydra:title' => 'The API entrypoint',
380
            'hydra:supportedProperty' => $entrypointProperties,
381
            'hydra:supportedOperation' => [
382
                '@type' => 'hydra:Operation',
383
                'hydra:method' => 'GET',
384
                'rdfs:label' => 'The API entrypoint.',
385
                'returns' => '#EntryPoint',
386
            ],
387
        ];
388
389
        // Constraint violation
390
        $classes[] = [
391
            '@id' => '#ConstraintViolation',
392
            '@type' => 'hydra:Class',
393
            'hydra:title' => 'A constraint violation',
394
            'hydra:supportedProperty' => [
395
                [
396
                    '@type' => 'hydra:SupportedProperty',
397
                    'hydra:property' => [
398
                        '@id' => '#ConstraintViolation/propertyPath',
399
                        '@type' => 'rdf:Property',
400
                        'rdfs:label' => 'propertyPath',
401
                        'domain' => '#ConstraintViolation',
402
                        'range' => 'xmls:string',
403
                    ],
404
                    'hydra:title' => 'propertyPath',
405
                    'hydra:description' => 'The property path of the violation',
406
                    'hydra:readable' => true,
407
                    'hydra:writable' => false,
408
                ],
409
                [
410
                    '@type' => 'hydra:SupportedProperty',
411
                    'hydra:property' => [
412
                        '@id' => '#ConstraintViolation/message',
413
                        '@type' => 'rdf:Property',
414
                        'rdfs:label' => 'message',
415
                        'domain' => '#ConstraintViolation',
416
                        'range' => 'xmls:string',
417
                    ],
418
                    'hydra:title' => 'message',
419
                    'hydra:description' => 'The message associated with the violation',
420
                    'hydra:readable' => true,
421
                    'hydra:writable' => false,
422
                ],
423
            ],
424
        ];
425
426
        // Constraint violation list
427
        $classes[] = [
428
            '@id' => '#ConstraintViolationList',
429
            '@type' => 'hydra:Class',
430
            'subClassOf' => 'hydra:Error',
431
            'hydra:title' => 'A constraint violation list',
432
            'hydra:supportedProperty' => [
433
                [
434
                    '@type' => 'hydra:SupportedProperty',
435
                    'hydra:property' => [
436
                        '@id' => '#ConstraintViolationList/violations',
437
                        '@type' => 'rdf:Property',
438
                        'rdfs:label' => 'violations',
439
                        'domain' => '#ConstraintViolationList',
440
                        'range' => '#ConstraintViolation',
441
                    ],
442
                    'hydra:title' => 'violations',
443
                    'hydra:description' => 'The violations',
444
                    'hydra:readable' => true,
445
                    'hydra:writable' => false,
446
                ],
447
            ],
448
        ];
449
450
        return $classes;
451
    }
452
453
    /**
454
     * Gets a property definition.
455
     */
456
    private function getProperty(PropertyMetadata $propertyMetadata, string $propertyName, string $prefixedShortName, string $shortName): array
457
    {
458
        $propertyData = [
459
            '@id' => $propertyMetadata->getIri() ?? "#$shortName/$propertyName",
460
            '@type' => $propertyMetadata->isReadableLink() ? 'rdf:Property' : 'hydra:Link',
461
            'rdfs:label' => $propertyName,
462
            'domain' => $prefixedShortName,
463
        ];
464
465
        $type = $propertyMetadata->getType();
466
467
        if (null !== $type && !$type->isCollection() && (null !== $className = $type->getClassName()) && $this->resourceClassResolver->isResourceClass($className)) {
468
            $propertyData['owl:maxCardinality'] = 1;
469
        }
470
471
        $property = [
472
            '@type' => 'hydra:SupportedProperty',
473
            'hydra:property' => $propertyData,
474
            'hydra:title' => $propertyName,
475
            'hydra:required' => $propertyMetadata->isRequired(),
476
            'hydra:readable' => $propertyMetadata->isReadable(),
477
            'hydra:writable' => $propertyMetadata->isWritable() || $propertyMetadata->isInitializable(),
478
        ];
479
480
        if (null !== $range = $this->getRange($propertyMetadata)) {
481
            $property['hydra:property']['range'] = $range;
482
        }
483
484
        if (null !== $description = $propertyMetadata->getDescription()) {
485
            $property['hydra:description'] = $description;
486
        }
487
488
        if ($propertyMetadata->getAttribute('deprecation_reason')) {
489
            $property['owl:deprecated'] = true;
490
        }
491
492
        return $property;
493
    }
494
495
    /**
496
     * Computes the documentation.
497
     */
498
    private function computeDoc(Documentation $object, array $classes): array
499
    {
500
        $doc = ['@context' => $this->getContext(), '@id' => $this->urlGenerator->generate('api_doc', ['_format' => self::FORMAT]), '@type' => 'hydra:ApiDocumentation'];
501
502
        if ('' !== $object->getTitle()) {
503
            $doc['hydra:title'] = $object->getTitle();
504
        }
505
506
        if ('' !== $object->getDescription()) {
507
            $doc['hydra:description'] = $object->getDescription();
508
        }
509
510
        $doc['hydra:entrypoint'] = $this->urlGenerator->generate('api_entrypoint');
511
        $doc['hydra:supportedClass'] = $classes;
512
513
        return $doc;
514
    }
515
516
    /**
517
     * Builds the JSON-LD context for the API documentation.
518
     */
519
    private function getContext(): array
520
    {
521
        return [
522
            '@vocab' => $this->urlGenerator->generate('api_doc', ['_format' => self::FORMAT], UrlGeneratorInterface::ABS_URL).'#',
523
            'hydra' => ContextBuilderInterface::HYDRA_NS,
524
            'rdf' => ContextBuilderInterface::RDF_NS,
525
            'rdfs' => ContextBuilderInterface::RDFS_NS,
526
            'xmls' => ContextBuilderInterface::XML_NS,
527
            'owl' => ContextBuilderInterface::OWL_NS,
528
            'schema' => ContextBuilderInterface::SCHEMA_ORG_NS,
529
            'domain' => ['@id' => 'rdfs:domain', '@type' => '@id'],
530
            'range' => ['@id' => 'rdfs:range', '@type' => '@id'],
531
            'subClassOf' => ['@id' => 'rdfs:subClassOf', '@type' => '@id'],
532
            'expects' => ['@id' => 'hydra:expects', '@type' => '@id'],
533
            'returns' => ['@id' => 'hydra:returns', '@type' => '@id'],
534
        ];
535
    }
536
537
    /**
538
     * {@inheritdoc}
539
     */
540
    public function supportsNormalization($data, $format = null, array $context = [])
541
    {
542
        return self::FORMAT === $format && $data instanceof Documentation;
543
    }
544
545
    /**
546
     * {@inheritdoc}
547
     */
548
    public function hasCacheableSupportsMethod(): bool
549
    {
550
        return true;
551
    }
552
}
553