Completed
Push — master ( 290ee6...55851f )
by Kévin
15s
created

DocumentationNormalizer::getHydraOperations()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 26
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 14
nc 7
nop 4
dl 0
loc 26
rs 6.7272
c 0
b 0
f 0
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)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
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'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
158
            $context['serializer_groups'] = $attributes['normalization_context']['groups'];
159
        }
160
161
        if (isset($attributes['denormalization_context']['groups'])) {
162 View Code Duplication
            if (isset($context['serializer_groups'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
163
                $context['serializer_groups'] += $attributes['denormalization_context']['groups'];
164
            } else {
165
                $context['serializer_groups'] = $attributes['denormalization_context']['groups'];
166
            }
167
        }
168
169
        return $context;
170
    }
171
172
    /**
173
     * Gets Hydra properties.
174
     *
175
     * @param string           $resourceClass
176
     * @param ResourceMetadata $resourceMetadata
177
     * @param string           $shortName
178
     * @param string           $prefixedShortName
179
     *
180
     * @return array
181
     */
182
    private function getHydraProperties(string $resourceClass, ResourceMetadata $resourceMetadata, string $shortName, string $prefixedShortName): array
183
    {
184
        $properties = [];
185
        foreach ($this->propertyNameCollectionFactory->create($resourceClass, $this->getPropertyNameCollectionFactoryContext($resourceMetadata)) as $propertyName) {
186
            $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName);
187
            if (true === $propertyMetadata->isIdentifier() && false === $propertyMetadata->isWritable()) {
188
                continue;
189
            }
190
191
            $properties[] = $this->getProperty($propertyMetadata, $propertyName, $prefixedShortName, $shortName);
192
        }
193
194
        return $properties;
195
    }
196
197
    /**
198
     * Gets Hydra operations.
199
     *
200
     * @param string           $resourceClass
201
     * @param ResourceMetadata $resourceMetadata
202
     * @param string           $prefixedShortName
203
     * @param bool             $collection
204
     *
205
     * @return array
206
     */
207
    private function getHydraOperations(string $resourceClass, ResourceMetadata $resourceMetadata, string $prefixedShortName, bool $collection): array
208
    {
209
        if (null === $operations = $collection ? $resourceMetadata->getCollectionOperations() : $resourceMetadata->getItemOperations()) {
210
            return [];
211
        }
212
213
        $hydraOperations = [];
214
        foreach ($operations as $operationName => $operation) {
215
            $hydraOperations[] = $this->getHydraOperation($resourceClass, $resourceMetadata, $operationName, $operation, $prefixedShortName, $collection ? OperationType::COLLECTION : OperationType::ITEM);
216
        }
217
218
        foreach ($this->propertyNameCollectionFactory->create($resourceClass, $this->getPropertyNameCollectionFactoryContext($resourceMetadata)) as $propertyName) {
219
            $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName);
220
221
            if (!$propertyMetadata->hasSubresource()) {
222
                continue;
223
            }
224
225
            $subresourceMetadata = $this->resourceMetadataFactory->create($propertyMetadata->getSubresource()->getResourceClass());
226
            $prefixedShortName = "#{$subresourceMetadata->getShortName()}";
227
228
            $hydraOperations[] = $this->getHydraOperation($resourceClass, $subresourceMetadata, $operationName, $operation, $prefixedShortName, OperationType::SUBRESOURCE, $propertyMetadata->getSubresource());
0 ignored issues
show
Bug introduced by
The variable $operationName seems to be defined by a foreach iteration on line 214. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
Bug introduced by
The variable $operation seems to be defined by a foreach iteration on line 214. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
229
        }
230
231
        return $hydraOperations;
232
    }
233
234
    /**
235
     * Gets and populates if applicable a Hydra operation.
236
     *
237
     * @param string              $resourceClass
238
     * @param ResourceMetadata    $resourceMetadata
239
     * @param string              $operationName
240
     * @param array               $operation
241
     * @param string              $prefixedShortName
242
     * @param string              $operationType
243
     * @param SubresourceMetadata $operationType
244
     *
245
     * @return array
246
     */
247
    private function getHydraOperation(string $resourceClass, ResourceMetadata $resourceMetadata, string $operationName, array $operation, string $prefixedShortName, string $operationType, SubresourceMetadata $subresourceMetadata = null): array
248
    {
249
        if (OperationType::COLLECTION === $operationType) {
250
            $method = $this->operationMethodResolver->getCollectionOperationMethod($resourceClass, $operationName);
251
        } elseif (OperationType::ITEM === $operationType) {
252
            $method = $this->operationMethodResolver->getItemOperationMethod($resourceClass, $operationName);
253
        } else {
254
            $method = 'GET';
255
        }
256
257
        $hydraOperation = $operation['hydra_context'] ?? [];
258
        $shortName = $resourceMetadata->getShortName();
259
260
        if ('GET' === $method && OperationType::COLLECTION === $operationType) {
261
            $hydraOperation = [
262
                '@type' => ['hydra:Operation', 'schema:FindAction'],
263
                'hydra:title' => "Retrieves the collection of $shortName resources.",
264
                'returns' => 'hydra:Collection',
265
            ] + $hydraOperation;
266
        } elseif ('GET' === $method && OperationType::SUBRESOURCE === $operationType) {
267
            $hydraOperation = [
268
                '@type' => ['hydra:Operation', 'schema:FindAction'],
269
                'hydra:title' => $subresourceMetadata->isCollection() ? "Retrieves the collection of $shortName resources." : "Retrieves a $shortName resource.",
0 ignored issues
show
Bug introduced by
It seems like $subresourceMetadata is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
270
                'returns' => "#$shortName",
271
            ] + $hydraOperation;
272 View Code Duplication
        } elseif ('GET' === $method) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
273
            $hydraOperation = [
274
                '@type' => ['hydra:Operation', 'schema:FindAction'],
275
                'hydra:title' => "Retrieves $shortName resource.",
276
                'returns' => $prefixedShortName,
277
            ] + $hydraOperation;
278
        } elseif ('POST' === $method) {
279
            $hydraOperation = [
280
                '@type' => ['hydra:Operation', 'schema:CreateAction'],
281
                'hydra:title' => "Creates a $shortName resource.",
282
                'returns' => $prefixedShortName,
283
                'expects' => $prefixedShortName,
284
            ] + $hydraOperation;
285 View Code Duplication
        } elseif ('PUT' === $method) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
286
            $hydraOperation = [
287
                '@type' => ['hydra:Operation', 'schema:ReplaceAction'],
288
                'hydra:title' => "Replaces the $shortName resource.",
289
                'returns' => $prefixedShortName,
290
                'expects' => $prefixedShortName,
291
            ] + $hydraOperation;
292
        } elseif ('DELETE' === $method) {
293
            $hydraOperation = [
294
                '@type' => ['hydra:Operation', 'schema:DeleteAction'],
295
                'hydra:title' => "Deletes the $shortName resource.",
296
                'returns' => 'owl:Nothing',
297
            ] + $hydraOperation;
298
        }
299
300
        $hydraOperation['hydra:method'] ?? $hydraOperation['hydra:method'] = $method;
301
302
        if (!isset($hydraOperation['rdfs:label']) && isset($hydraOperation['hydra:title'])) {
303
            $hydraOperation['rdfs:label'] = $hydraOperation['hydra:title'];
304
        }
305
306
        ksort($hydraOperation);
307
308
        return $hydraOperation;
309
    }
310
311
    /**
312
     * Gets the range of the property.
313
     *
314
     * @param PropertyMetadata $propertyMetadata
315
     *
316
     * @return string|null
317
     */
318
    private function getRange(PropertyMetadata $propertyMetadata)
319
    {
320
        $jsonldContext = $propertyMetadata->getAttributes()['jsonld_context'] ?? [];
321
322
        if (isset($jsonldContext['@type'])) {
323
            return $jsonldContext['@type'];
324
        }
325
326
        if (null === $type = $propertyMetadata->getType()) {
327
            return null;
328
        }
329
330
        if ($type->isCollection() && null !== $collectionType = $type->getCollectionValueType()) {
331
            $type = $collectionType;
332
        }
333
334
        switch ($type->getBuiltinType()) {
335
            case Type::BUILTIN_TYPE_STRING:
336
                return 'xmls:string';
337
            case Type::BUILTIN_TYPE_INT:
338
                return 'xmls:integer';
339
            case Type::BUILTIN_TYPE_FLOAT:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
340
                return 'xmls:decimal';
341
            case Type::BUILTIN_TYPE_BOOL:
342
                return 'xmls:boolean';
343
            case Type::BUILTIN_TYPE_OBJECT:
344
                if (null === $className = $type->getClassName()) {
345
                    return null;
346
                }
347
348
                if (is_a($className, \DateTimeInterface::class, true)) {
349
                    return 'xmls:dateTime';
350
                }
351
352
                if ($this->resourceClassResolver->isResourceClass($className)) {
353
                    $resourceMetadata = $this->resourceMetadataFactory->create($className);
354
355
                    return $resourceMetadata->getIri() ?? "#{$resourceMetadata->getShortName()}";
356
                }
357
                break;
358
        }
359
    }
360
361
    /**
362
     * Builds the classes array.
363
     *
364
     * @param array $entrypointProperties
365
     * @param array $classes
366
     *
367
     * @return array
368
     */
369
    private function getClasses(array $entrypointProperties, array $classes): array
370
    {
371
        $classes[] = [
372
            '@id' => '#Entrypoint',
373
            '@type' => 'hydra:Class',
374
            'hydra:title' => 'The API entrypoint',
375
            'hydra:supportedProperty' => $entrypointProperties,
376
            'hydra:supportedOperation' => [
377
                '@type' => 'hydra:Operation',
378
                'hydra:method' => 'GET',
379
                'rdfs:label' => 'The API entrypoint.',
380
                'returns' => '#EntryPoint',
381
            ],
382
        ];
383
384
        // Constraint violation
385
        $classes[] = [
386
            '@id' => '#ConstraintViolation',
387
            '@type' => 'hydra:Class',
388
            'hydra:title' => 'A constraint violation',
389
            'hydra:supportedProperty' => [
390
                [
391
                    '@type' => 'hydra:SupportedProperty',
392
                    'hydra:property' => [
393
                        '@id' => '#ConstraintViolation/propertyPath',
394
                        '@type' => 'rdf:Property',
395
                        'rdfs:label' => 'propertyPath',
396
                        'domain' => '#ConstraintViolation',
397
                        'range' => 'xmls:string',
398
                    ],
399
                    'hydra:title' => 'propertyPath',
400
                    'hydra:description' => 'The property path of the violation',
401
                    'hydra:readable' => true,
402
                    'hydra:writable' => false,
403
                ],
404
                [
405
                    '@type' => 'hydra:SupportedProperty',
406
                    'hydra:property' => [
407
                        '@id' => '#ConstraintViolation/message',
408
                        '@type' => 'rdf:Property',
409
                        'rdfs:label' => 'message',
410
                        'domain' => '#ConstraintViolation',
411
                        'range' => 'xmls:string',
412
                    ],
413
                    'hydra:title' => 'message',
414
                    'hydra:description' => 'The message associated with the violation',
415
                    'hydra:readable' => true,
416
                    'hydra:writable' => false,
417
                ],
418
            ],
419
        ];
420
421
        // Constraint violation list
422
        $classes[] = [
423
            '@id' => '#ConstraintViolationList',
424
            '@type' => 'hydra:Class',
425
            'subClassOf' => 'hydra:Error',
426
            'hydra:title' => 'A constraint violation list',
427
            'hydra:supportedProperty' => [
428
                [
429
                    '@type' => 'hydra:SupportedProperty',
430
                    'hydra:property' => [
431
                        '@id' => '#ConstraintViolationList/violations',
432
                        '@type' => 'rdf:Property',
433
                        'rdfs:label' => 'violations',
434
                        'domain' => '#ConstraintViolationList',
435
                        'range' => '#ConstraintViolation',
436
                    ],
437
                    'hydra:title' => 'violations',
438
                    'hydra:description' => 'The violations',
439
                    'hydra:readable' => true,
440
                    'hydra:writable' => false,
441
                ],
442
            ],
443
        ];
444
445
        return $classes;
446
    }
447
448
    /**
449
     * Gets a property definition.
450
     *
451
     * @param PropertyMetadata $propertyMetadata
452
     * @param string           $propertyName
453
     * @param string           $prefixedShortName
454
     * @param string           $shortName
455
     *
456
     * @return array
457
     */
458
    private function getProperty(PropertyMetadata $propertyMetadata, string $propertyName, string $prefixedShortName, string $shortName): array
459
    {
460
        $propertyData = [
461
            '@id' => $propertyMetadata->getIri() ?? "#$shortName/$propertyName",
462
            '@type' => $propertyMetadata->isReadableLink() ? 'rdf:Property' : 'hydra:Link',
463
            'rdfs:label' => $propertyName,
464
            'domain' => $prefixedShortName,
465
        ];
466
467
        $type = $propertyMetadata->getType();
468
469
        if (null !== $type && !$type->isCollection() && (null !== $className = $type->getClassName()) && $this->resourceClassResolver->isResourceClass($className)) {
470
            $propertyData['owl:maxCardinality'] = 1;
471
        }
472
473
        $property = [
474
            '@type' => 'hydra:SupportedProperty',
475
            'hydra:property' => $propertyData,
476
            'hydra:title' => $propertyName,
477
            'hydra:required' => $propertyMetadata->isRequired(),
478
            'hydra:readable' => $propertyMetadata->isReadable(),
479
            'hydra:writable' => $propertyMetadata->isWritable(),
480
        ];
481
482
        if (null !== $range = $this->getRange($propertyMetadata)) {
483
            $property['hydra:property']['range'] = $range;
484
        }
485
486
        if (null !== $description = $propertyMetadata->getDescription()) {
487
            $property['hydra:description'] = $description;
488
        }
489
490
        return $property;
491
    }
492
493
    /**
494
     * Computes the documentation.
495
     *
496
     * @param Documentation $object
497
     * @param array         $classes
498
     *
499
     * @return array
500
     */
501
    private function computeDoc(Documentation $object, array $classes): array
502
    {
503
        $doc = ['@context' => $this->getContext(), '@id' => $this->urlGenerator->generate('api_doc', ['_format' => self::FORMAT]), '@type' => 'hydra:ApiDocumentation'];
504
505
        if ('' !== $object->getTitle()) {
506
            $doc['hydra:title'] = $object->getTitle();
507
        }
508
509
        if ('' !== $object->getDescription()) {
510
            $doc['hydra:description'] = $object->getDescription();
511
        }
512
513
        $doc['hydra:entrypoint'] = $this->urlGenerator->generate('api_entrypoint');
514
        $doc['hydra:supportedClass'] = $classes;
515
516
        return $doc;
517
    }
518
519
    /**
520
     * Builds the JSON-LD context for the API documentation.
521
     *
522
     * @return array
523
     */
524
    private function getContext(): array
525
    {
526
        return [
527
            '@vocab' => $this->urlGenerator->generate('api_doc', ['_format' => self::FORMAT], UrlGeneratorInterface::ABS_URL).'#',
528
            'hydra' => ContextBuilderInterface::HYDRA_NS,
529
            'rdf' => ContextBuilderInterface::RDF_NS,
530
            'rdfs' => ContextBuilderInterface::RDFS_NS,
531
            'xmls' => ContextBuilderInterface::XML_NS,
532
            'owl' => ContextBuilderInterface::OWL_NS,
533
            'schema' => ContextBuilderInterface::SCHEMA_ORG_NS,
534
            'domain' => ['@id' => 'rdfs:domain', '@type' => '@id'],
535
            'range' => ['@id' => 'rdfs:range', '@type' => '@id'],
536
            'subClassOf' => ['@id' => 'rdfs:subClassOf', '@type' => '@id'],
537
            'expects' => ['@id' => 'hydra:expects', '@type' => '@id'],
538
            'returns' => ['@id' => 'hydra:returns', '@type' => '@id'],
539
        ];
540
    }
541
542
    /**
543
     * {@inheritdoc}
544
     */
545
    public function supportsNormalization($data, $format = null, array $context = [])
546
    {
547
        return self::FORMAT === $format && $data instanceof Documentation;
548
    }
549
}
550