• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

api-platform / core / 7729798413

31 Jan 2024 05:12PM UTC coverage: 66.65% (+0.001%) from 66.649%
7729798413

push

github

web-flow
fix(hydra): move owl:maxCardinality from JsonSchema to Hydra (#6136)

8 of 10 new or added lines in 1 file covered. (80.0%)

36 existing lines in 2 files now uncovered.

16290 of 24441 relevant lines covered (66.65%)

37.36 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.85
/src/Hydra/Serializer/DocumentationNormalizer.php
1
<?php
2

3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <dunglas@gmail.com>
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\Hydra\Serializer;
15

16
use ApiPlatform\Api\ResourceClassResolverInterface as LegacyResourceClassResolverInterface;
17
use ApiPlatform\Api\UrlGeneratorInterface as LegacyUrlGeneratorInterface;
18
use ApiPlatform\Documentation\Documentation;
19
use ApiPlatform\JsonLd\ContextBuilderInterface;
20
use ApiPlatform\Metadata\ApiProperty;
21
use ApiPlatform\Metadata\ApiResource;
22
use ApiPlatform\Metadata\CollectionOperationInterface;
23
use ApiPlatform\Metadata\HttpOperation;
24
use ApiPlatform\Metadata\Operation;
25
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
26
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
27
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
28
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
29
use ApiPlatform\Metadata\ResourceClassResolverInterface;
30
use ApiPlatform\Metadata\UrlGeneratorInterface;
31
use ApiPlatform\Serializer\CacheableSupportsMethodInterface;
32
use Symfony\Component\PropertyInfo\Type;
33
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
34
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
35
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
36
use Symfony\Component\Serializer\Serializer;
37

38
/**
39
 * Creates a machine readable Hydra API documentation.
40
 *
41
 * @author Kévin Dunglas <dunglas@gmail.com>
42
 */
43
final class DocumentationNormalizer implements NormalizerInterface, CacheableSupportsMethodInterface
44
{
45
    public const FORMAT = 'jsonld';
46

47
    public function __construct(private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory, private readonly PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, private readonly PropertyMetadataFactoryInterface $propertyMetadataFactory, private readonly ResourceClassResolverInterface|LegacyResourceClassResolverInterface $resourceClassResolver, private readonly UrlGeneratorInterface|LegacyUrlGeneratorInterface $urlGenerator, private readonly ?NameConverterInterface $nameConverter = null)
48
    {
49
    }
128✔
50

51
    /**
52
     * {@inheritdoc}
53
     */
54
    public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null
55
    {
56
        $classes = [];
8✔
57
        $entrypointProperties = [];
8✔
58

59
        foreach ($object->getResourceNameCollection() as $resourceClass) {
8✔
60
            $resourceMetadataCollection = $this->resourceMetadataFactory->create($resourceClass);
8✔
61

62
            $resourceMetadata = $resourceMetadataCollection[0];
8✔
63
            $shortName = $resourceMetadata->getShortName();
8✔
64
            $prefixedShortName = $resourceMetadata->getTypes()[0] ?? "#$shortName";
8✔
65
            $this->populateEntrypointProperties($resourceMetadata, $shortName, $prefixedShortName, $entrypointProperties, $resourceMetadataCollection);
8✔
66
            $classes[] = $this->getClass($resourceClass, $resourceMetadata, $shortName, $prefixedShortName, $context, $resourceMetadataCollection);
8✔
67
        }
68

69
        return $this->computeDoc($object, $this->getClasses($entrypointProperties, $classes));
8✔
70
    }
71

72
    /**
73
     * Populates entrypoint properties.
74
     */
75
    private function populateEntrypointProperties(ApiResource $resourceMetadata, string $shortName, string $prefixedShortName, array &$entrypointProperties, ResourceMetadataCollection $resourceMetadataCollection = null): void
76
    {
77
        $hydraCollectionOperations = $this->getHydraOperations(true, $resourceMetadataCollection);
8✔
78
        if (empty($hydraCollectionOperations)) {
8✔
79
            return;
×
80
        }
81

82
        $entrypointProperty = [
8✔
83
            '@type' => 'hydra:SupportedProperty',
8✔
84
            'hydra:property' => [
8✔
85
                '@id' => sprintf('#Entrypoint/%s', lcfirst($shortName)),
8✔
86
                '@type' => 'hydra:Link',
8✔
87
                'domain' => '#Entrypoint',
8✔
88
                'rdfs:label' => "The collection of $shortName resources",
8✔
89
                'rdfs:range' => [
8✔
90
                    ['@id' => 'hydra:Collection'],
8✔
91
                    [
8✔
92
                        'owl:equivalentClass' => [
8✔
93
                            'owl:onProperty' => ['@id' => 'hydra:member'],
8✔
94
                            'owl:allValuesFrom' => ['@id' => $prefixedShortName],
8✔
95
                        ],
8✔
96
                    ],
8✔
97
                ],
8✔
98
                'hydra:supportedOperation' => $hydraCollectionOperations,
8✔
99
            ],
8✔
100
            'hydra:title' => "The collection of $shortName resources",
8✔
101
            'hydra:readable' => true,
8✔
102
            'hydra:writeable' => false,
8✔
103
        ];
8✔
104

105
        if ($resourceMetadata->getDeprecationReason()) {
8✔
106
            $entrypointProperty['owl:deprecated'] = true;
×
107
        }
108

109
        $entrypointProperties[] = $entrypointProperty;
8✔
110
    }
111

112
    /**
113
     * Gets a Hydra class.
114
     */
115
    private function getClass(string $resourceClass, ApiResource $resourceMetadata, string $shortName, string $prefixedShortName, array $context, ResourceMetadataCollection $resourceMetadataCollection = null): array
116
    {
117
        $description = $resourceMetadata->getDescription();
8✔
118
        $isDeprecated = $resourceMetadata->getDeprecationReason();
8✔
119

120
        $class = [
8✔
121
            '@id' => $prefixedShortName,
8✔
122
            '@type' => 'hydra:Class',
8✔
123
            'rdfs:label' => $shortName,
8✔
124
            'hydra:title' => $shortName,
8✔
125
            'hydra:supportedProperty' => $this->getHydraProperties($resourceClass, $resourceMetadata, $shortName, $prefixedShortName, $context),
8✔
126
            'hydra:supportedOperation' => $this->getHydraOperations(false, $resourceMetadataCollection),
8✔
127
        ];
8✔
128

129
        if (null !== $description) {
8✔
130
            $class['hydra:description'] = $description;
8✔
131
        }
132

133
        if ($isDeprecated) {
8✔
134
            $class['owl:deprecated'] = true;
×
135
        }
136

137
        return $class;
8✔
138
    }
139

140
    /**
141
     * Creates context for property metatata factories.
142
     */
143
    private function getPropertyMetadataFactoryContext(ApiResource $resourceMetadata): array
144
    {
145
        $normalizationGroups = $resourceMetadata->getNormalizationContext()[AbstractNormalizer::GROUPS] ?? null;
8✔
146
        $denormalizationGroups = $resourceMetadata->getDenormalizationContext()[AbstractNormalizer::GROUPS] ?? null;
8✔
147
        $propertyContext = [
8✔
148
            'normalization_groups' => $normalizationGroups,
8✔
149
            'denormalization_groups' => $denormalizationGroups,
8✔
150
        ];
8✔
151
        $propertyNameContext = [];
8✔
152

153
        if ($normalizationGroups) {
8✔
154
            $propertyNameContext['serializer_groups'] = $normalizationGroups;
×
155
        }
156

157
        if (!$denormalizationGroups) {
8✔
158
            return [$propertyNameContext, $propertyContext];
8✔
159
        }
160

161
        if (!isset($propertyNameContext['serializer_groups'])) {
×
162
            $propertyNameContext['serializer_groups'] = $denormalizationGroups;
×
163

164
            return [$propertyNameContext, $propertyContext];
×
165
        }
166

167
        foreach ($denormalizationGroups as $group) {
×
168
            $propertyNameContext['serializer_groups'][] = $group;
×
169
        }
170

171
        return [$propertyNameContext, $propertyContext];
×
172
    }
173

174
    /**
175
     * Gets Hydra properties.
176
     */
177
    private function getHydraProperties(string $resourceClass, ApiResource $resourceMetadata, string $shortName, string $prefixedShortName, array $context): array
178
    {
179
        $classes = [];
8✔
180

181
        $classes[$resourceClass] = true;
8✔
182
        foreach ($resourceMetadata->getOperations() as $operation) {
8✔
183
            /** @var Operation $operation */
184
            if (!$operation instanceof CollectionOperationInterface) {
8✔
185
                continue;
8✔
186
            }
187

188
            $inputMetadata = $operation->getInput();
8✔
189
            if (null !== $inputClass = $inputMetadata['class'] ?? null) {
8✔
190
                $classes[$inputClass] = true;
4✔
191
            }
192

193
            $outputMetadata = $operation->getOutput();
8✔
194
            if (null !== $outputClass = $outputMetadata['class'] ?? null) {
8✔
195
                $classes[$outputClass] = true;
4✔
196
            }
197
        }
198

199
        /** @var string[] $classes */
200
        $classes = array_keys($classes);
8✔
201
        $properties = [];
8✔
202
        [$propertyNameContext, $propertyContext] = $this->getPropertyMetadataFactoryContext($resourceMetadata);
8✔
203

204
        foreach ($classes as $class) {
8✔
205
            foreach ($this->propertyNameCollectionFactory->create($class, $propertyNameContext) as $propertyName) {
8✔
206
                $propertyMetadata = $this->propertyMetadataFactory->create($class, $propertyName, $propertyContext);
8✔
207

208
                if (true === $propertyMetadata->isIdentifier() && false === $propertyMetadata->isWritable()) {
8✔
209
                    continue;
×
210
                }
211

212
                if ($this->nameConverter) {
8✔
213
                    $propertyName = $this->nameConverter->normalize($propertyName, $class, self::FORMAT, $context);
4✔
214
                }
215

216
                $properties[] = $this->getProperty($propertyMetadata, $propertyName, $prefixedShortName, $shortName);
8✔
217
            }
218
        }
219

220
        return $properties;
8✔
221
    }
222

223
    /**
224
     * Gets Hydra operations.
225
     */
226
    private function getHydraOperations(bool $collection, ResourceMetadataCollection $resourceMetadataCollection = null): array
227
    {
228
        $hydraOperations = [];
8✔
229
        foreach ($resourceMetadataCollection as $resourceMetadata) {
8✔
230
            foreach ($resourceMetadata->getOperations() as $operation) {
8✔
231
                if (('POST' === $operation->getMethod() || $operation instanceof CollectionOperationInterface) !== $collection) {
8✔
232
                    continue;
8✔
233
                }
234

235
                $hydraOperations[] = $this->getHydraOperation($operation, $operation->getTypes()[0] ?? "#{$operation->getShortName()}");
8✔
236
            }
237
        }
238

239
        return $hydraOperations;
8✔
240
    }
241

242
    /**
243
     * Gets and populates if applicable a Hydra operation.
244
     */
245
    private function getHydraOperation(HttpOperation $operation, string $prefixedShortName): array
246
    {
247
        $method = $operation->getMethod() ?: 'GET';
8✔
248

249
        $hydraOperation = $operation->getHydraContext() ?? [];
8✔
250
        if ($operation->getDeprecationReason()) {
8✔
251
            $hydraOperation['owl:deprecated'] = true;
×
252
        }
253

254
        $shortName = $operation->getShortName();
8✔
255
        $inputMetadata = $operation->getInput() ?? [];
8✔
256
        $outputMetadata = $operation->getOutput() ?? [];
8✔
257

258
        $inputClass = \array_key_exists('class', $inputMetadata) ? $inputMetadata['class'] : false;
8✔
259
        $outputClass = \array_key_exists('class', $outputMetadata) ? $outputMetadata['class'] : false;
8✔
260

261
        if ('GET' === $method && $operation instanceof CollectionOperationInterface) {
8✔
262
            $hydraOperation += [
8✔
263
                '@type' => ['hydra:Operation', 'schema:FindAction'],
8✔
264
                'hydra:title' => "Retrieves the collection of $shortName resources.",
8✔
265
                'returns' => null === $outputClass ? 'owl:Nothing' : 'hydra:Collection',
8✔
266
            ];
8✔
267
        } elseif ('GET' === $method) {
8✔
268
            $hydraOperation += [
8✔
269
                '@type' => ['hydra:Operation', 'schema:FindAction'],
8✔
270
                'hydra:title' => "Retrieves a $shortName resource.",
8✔
271
                'returns' => null === $outputClass ? 'owl:Nothing' : $prefixedShortName,
8✔
272
            ];
8✔
273
        } elseif ('PATCH' === $method) {
8✔
274
            $hydraOperation += [
×
275
                '@type' => 'hydra:Operation',
×
276
                'hydra:title' => "Updates the $shortName resource.",
×
277
                'returns' => null === $outputClass ? 'owl:Nothing' : $prefixedShortName,
×
278
                'expects' => null === $inputClass ? 'owl:Nothing' : $prefixedShortName,
×
279
            ];
×
280
        } elseif ('POST' === $method) {
8✔
281
            $hydraOperation += [
8✔
282
                '@type' => ['hydra:Operation', 'schema:CreateAction'],
8✔
283
                'hydra:title' => "Creates a $shortName resource.",
8✔
284
                'returns' => null === $outputClass ? 'owl:Nothing' : $prefixedShortName,
8✔
285
                'expects' => null === $inputClass ? 'owl:Nothing' : $prefixedShortName,
8✔
286
            ];
8✔
287
        } elseif ('PUT' === $method) {
8✔
288
            $hydraOperation += [
8✔
289
                '@type' => ['hydra:Operation', 'schema:ReplaceAction'],
8✔
290
                'hydra:title' => "Replaces the $shortName resource.",
8✔
291
                'returns' => null === $outputClass ? 'owl:Nothing' : $prefixedShortName,
8✔
292
                'expects' => null === $inputClass ? 'owl:Nothing' : $prefixedShortName,
8✔
293
            ];
8✔
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;
8✔
303

304
        if (!isset($hydraOperation['rdfs:label']) && isset($hydraOperation['hydra:title'])) {
8✔
305
            $hydraOperation['rdfs:label'] = $hydraOperation['hydra:title'];
8✔
306
        }
307

308
        ksort($hydraOperation);
8✔
309

310
        return $hydraOperation;
8✔
311
    }
312

313
    /**
314
     * Gets the range of the property.
315
     */
316
    private function getRange(ApiProperty $propertyMetadata): array|string|null
317
    {
318
        $jsonldContext = $propertyMetadata->getJsonldContext();
8✔
319

320
        if (isset($jsonldContext['@type'])) {
8✔
321
            return $jsonldContext['@type'];
4✔
322
        }
323

324
        $builtInTypes = $propertyMetadata->getBuiltinTypes() ?? [];
8✔
325
        $types = [];
8✔
326

327
        foreach ($builtInTypes as $type) {
8✔
328
            if ($type->isCollection() && null !== $collectionType = $type->getCollectionValueTypes()[0] ?? null) {
8✔
329
                $type = $collectionType;
4✔
330
            }
331

332
            switch ($type->getBuiltinType()) {
8✔
333
                case Type::BUILTIN_TYPE_STRING:
334
                    if (!\in_array('xmls:string', $types, true)) {
8✔
335
                        $types[] = 'xmls:string';
8✔
336
                    }
337
                    break;
8✔
338
                case Type::BUILTIN_TYPE_INT:
339
                    if (!\in_array('xmls:integer', $types, true)) {
×
340
                        $types[] = 'xmls:integer';
×
341
                    }
342
                    break;
×
343
                case Type::BUILTIN_TYPE_FLOAT:
344
                    if (!\in_array('xmls:decimal', $types, true)) {
×
345
                        $types[] = 'xmls:decimal';
×
346
                    }
347
                    break;
×
348
                case Type::BUILTIN_TYPE_BOOL:
349
                    if (!\in_array('xmls:boolean', $types, true)) {
×
350
                        $types[] = 'xmls:boolean';
×
351
                    }
352
                    break;
×
353
                case Type::BUILTIN_TYPE_OBJECT:
354
                    if (null === $className = $type->getClassName()) {
4✔
355
                        continue 2;
×
356
                    }
357

358
                    if (is_a($className, \DateTimeInterface::class, true)) {
4✔
359
                        if (!\in_array('xmls:dateTime', $types, true)) {
×
360
                            $types[] = 'xmls:dateTime';
×
361
                        }
362
                        break;
×
363
                    }
364

365
                    if ($this->resourceClassResolver->isResourceClass($className)) {
4✔
366
                        $resourceMetadata = $this->resourceMetadataFactory->create($className);
4✔
367
                        $operation = $resourceMetadata->getOperation();
4✔
368

369
                        if (!$operation instanceof HttpOperation || !$operation->getTypes()) {
4✔
370
                            if (!\in_array("#{$operation->getShortName()}", $types, true)) {
4✔
371
                                $types[] = "#{$operation->getShortName()}";
4✔
372
                            }
373
                            break;
4✔
374
                        }
375

376
                        $types = array_unique(array_merge($types, $operation->getTypes()));
×
377
                        break;
×
378
                    }
379
            }
380
        }
381

382
        if ([] === $types) {
8✔
383
            return null;
4✔
384
        }
385

386
        return 1 === \count($types) ? $types[0] : $types;
8✔
387
    }
388

389
    private function isSingleRelation(ApiProperty $propertyMetadata): bool
390
    {
391
        $builtInTypes = $propertyMetadata->getBuiltinTypes() ?? [];
8✔
392

393
        foreach ($builtInTypes as $type) {
8✔
394
            $className = $type->getClassName();
8✔
395
            if (!$type->isCollection()
8✔
396
                && null !== $className
8✔
397
                && $this->resourceClassResolver->isResourceClass($className)
8✔
398
            ) {
NEW
399
                return true;
×
400
            }
401
        }
402

403
        return false;
8✔
404
    }
405

406
    /**
407
     * Builds the classes array.
408
     */
409
    private function getClasses(array $entrypointProperties, array $classes): array
410
    {
411
        $classes[] = [
8✔
412
            '@id' => '#Entrypoint',
8✔
413
            '@type' => 'hydra:Class',
8✔
414
            'hydra:title' => 'The API entrypoint',
8✔
415
            'hydra:supportedProperty' => $entrypointProperties,
8✔
416
            'hydra:supportedOperation' => [
8✔
417
                '@type' => 'hydra:Operation',
8✔
418
                'hydra:method' => 'GET',
8✔
419
                'rdfs:label' => 'The API entrypoint.',
8✔
420
                'returns' => '#EntryPoint',
8✔
421
            ],
8✔
422
        ];
8✔
423

424
        // Constraint violation
425
        $classes[] = [
8✔
426
            '@id' => '#ConstraintViolation',
8✔
427
            '@type' => 'hydra:Class',
8✔
428
            'hydra:title' => 'A constraint violation',
8✔
429
            'hydra:supportedProperty' => [
8✔
430
                [
8✔
431
                    '@type' => 'hydra:SupportedProperty',
8✔
432
                    'hydra:property' => [
8✔
433
                        '@id' => '#ConstraintViolation/propertyPath',
8✔
434
                        '@type' => 'rdf:Property',
8✔
435
                        'rdfs:label' => 'propertyPath',
8✔
436
                        'domain' => '#ConstraintViolation',
8✔
437
                        'range' => 'xmls:string',
8✔
438
                    ],
8✔
439
                    'hydra:title' => 'propertyPath',
8✔
440
                    'hydra:description' => 'The property path of the violation',
8✔
441
                    'hydra:readable' => true,
8✔
442
                    'hydra:writeable' => false,
8✔
443
                ],
8✔
444
                [
8✔
445
                    '@type' => 'hydra:SupportedProperty',
8✔
446
                    'hydra:property' => [
8✔
447
                        '@id' => '#ConstraintViolation/message',
8✔
448
                        '@type' => 'rdf:Property',
8✔
449
                        'rdfs:label' => 'message',
8✔
450
                        'domain' => '#ConstraintViolation',
8✔
451
                        'range' => 'xmls:string',
8✔
452
                    ],
8✔
453
                    'hydra:title' => 'message',
8✔
454
                    'hydra:description' => 'The message associated with the violation',
8✔
455
                    'hydra:readable' => true,
8✔
456
                    'hydra:writeable' => false,
8✔
457
                ],
8✔
458
            ],
8✔
459
        ];
8✔
460

461
        // Constraint violation list
462
        $classes[] = [
8✔
463
            '@id' => '#ConstraintViolationList',
8✔
464
            '@type' => 'hydra:Class',
8✔
465
            'subClassOf' => 'hydra:Error',
8✔
466
            'hydra:title' => 'A constraint violation list',
8✔
467
            'hydra:supportedProperty' => [
8✔
468
                [
8✔
469
                    '@type' => 'hydra:SupportedProperty',
8✔
470
                    'hydra:property' => [
8✔
471
                        '@id' => '#ConstraintViolationList/violations',
8✔
472
                        '@type' => 'rdf:Property',
8✔
473
                        'rdfs:label' => 'violations',
8✔
474
                        'domain' => '#ConstraintViolationList',
8✔
475
                        'range' => '#ConstraintViolation',
8✔
476
                    ],
8✔
477
                    'hydra:title' => 'violations',
8✔
478
                    'hydra:description' => 'The violations',
8✔
479
                    'hydra:readable' => true,
8✔
480
                    'hydra:writeable' => false,
8✔
481
                ],
8✔
482
            ],
8✔
483
        ];
8✔
484

485
        return $classes;
8✔
486
    }
487

488
    /**
489
     * Gets a property definition.
490
     */
491
    private function getProperty(ApiProperty $propertyMetadata, string $propertyName, string $prefixedShortName, string $shortName): array
492
    {
493
        if ($iri = $propertyMetadata->getIris()) {
8✔
494
            $iri = 1 === (is_countable($iri) ? \count($iri) : 0) ? $iri[0] : $iri;
4✔
495
        }
496

497
        if (!isset($iri)) {
8✔
498
            $iri = "#$shortName/$propertyName";
8✔
499
        }
500

501
        $propertyData = [
8✔
502
            '@id' => $iri,
8✔
503
            '@type' => false === $propertyMetadata->isReadableLink() ? 'hydra:Link' : 'rdf:Property',
8✔
504
            'rdfs:label' => $propertyName,
8✔
505
            'domain' => $prefixedShortName,
8✔
506
        ];
8✔
507

508
        $property = [
8✔
509
            '@type' => 'hydra:SupportedProperty',
8✔
510
            'hydra:property' => $propertyData,
8✔
511
            'hydra:title' => $propertyName,
8✔
512
            'hydra:required' => $propertyMetadata->isRequired(),
8✔
513
            'hydra:readable' => $propertyMetadata->isReadable(),
8✔
514
            'hydra:writeable' => $propertyMetadata->isWritable() || $propertyMetadata->isInitializable(),
8✔
515
        ];
8✔
516

517
        if (null !== $range = $this->getRange($propertyMetadata)) {
8✔
518
            $property['hydra:property']['range'] = $range;
8✔
519
        }
520

521
        if (null !== $description = $propertyMetadata->getDescription()) {
8✔
522
            $property['hydra:description'] = $description;
8✔
523
        }
524

525
        if ($propertyMetadata->getDeprecationReason()) {
8✔
526
            $property['owl:deprecated'] = true;
×
527
        }
528

529
        if ($this->isSingleRelation($propertyMetadata)) {
8✔
NEW
530
            $property['owl:maxCardinality'] = true;
×
531
        }
532

533
        return $property;
8✔
534
    }
535

536
    /**
537
     * Computes the documentation.
538
     */
539
    private function computeDoc(Documentation $object, array $classes): array
540
    {
541
        $doc = ['@context' => $this->getContext(), '@id' => $this->urlGenerator->generate('api_doc', ['_format' => self::FORMAT]), '@type' => 'hydra:ApiDocumentation'];
8✔
542

543
        if ('' !== $object->getTitle()) {
8✔
544
            $doc['hydra:title'] = $object->getTitle();
8✔
545
        }
546

547
        if ('' !== $object->getDescription()) {
8✔
548
            $doc['hydra:description'] = $object->getDescription();
8✔
549
        }
550

551
        $doc['hydra:entrypoint'] = $this->urlGenerator->generate('api_entrypoint');
8✔
552
        $doc['hydra:supportedClass'] = $classes;
8✔
553

554
        return $doc;
8✔
555
    }
556

557
    /**
558
     * Builds the JSON-LD context for the API documentation.
559
     */
560
    private function getContext(): array
561
    {
562
        return [
8✔
563
            '@vocab' => $this->urlGenerator->generate('api_doc', ['_format' => self::FORMAT], UrlGeneratorInterface::ABS_URL).'#',
8✔
564
            'hydra' => ContextBuilderInterface::HYDRA_NS,
8✔
565
            'rdf' => ContextBuilderInterface::RDF_NS,
8✔
566
            'rdfs' => ContextBuilderInterface::RDFS_NS,
8✔
567
            'xmls' => ContextBuilderInterface::XML_NS,
8✔
568
            'owl' => ContextBuilderInterface::OWL_NS,
8✔
569
            'schema' => ContextBuilderInterface::SCHEMA_ORG_NS,
8✔
570
            'domain' => ['@id' => 'rdfs:domain', '@type' => '@id'],
8✔
571
            'range' => ['@id' => 'rdfs:range', '@type' => '@id'],
8✔
572
            'subClassOf' => ['@id' => 'rdfs:subClassOf', '@type' => '@id'],
8✔
573
            'expects' => ['@id' => 'hydra:expects', '@type' => '@id'],
8✔
574
            'returns' => ['@id' => 'hydra:returns', '@type' => '@id'],
8✔
575
        ];
8✔
576
    }
577

578
    /**
579
     * {@inheritdoc}
580
     */
581
    public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool
582
    {
583
        return self::FORMAT === $format && $data instanceof Documentation;
4✔
584
    }
585

586
    public function getSupportedTypes($format): array
587
    {
588
        return self::FORMAT === $format ? [Documentation::class => true] : [];
104✔
589
    }
590

591
    public function hasCacheableSupportsMethod(): bool
592
    {
593
        if (method_exists(Serializer::class, 'getSupportedTypes')) {
×
594
            trigger_deprecation(
×
595
                'api-platform/core',
×
596
                '3.1',
×
597
                'The "%s()" method is deprecated, use "getSupportedTypes()" instead.',
×
598
                __METHOD__
×
599
            );
×
600
        }
601

602
        return true;
×
603
    }
604
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc