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

api-platform / core / 9710836697

28 Jun 2024 09:35AM UTC coverage: 63.285% (+1.2%) from 62.122%
9710836697

push

github

soyuka
docs: changelog v3.3.7

11104 of 17546 relevant lines covered (63.29%)

52.26 hits per line

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

84.94
/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\ErrorResource;
24
use ApiPlatform\Metadata\HttpOperation;
25
use ApiPlatform\Metadata\Operation;
26
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
27
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
28
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
29
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
30
use ApiPlatform\Metadata\ResourceClassResolverInterface;
31
use ApiPlatform\Metadata\UrlGeneratorInterface;
32
use ApiPlatform\Serializer\CacheableSupportsMethodInterface;
33
use ApiPlatform\Symfony\Validator\Exception\ValidationException;
34
use Symfony\Component\PropertyInfo\Type;
35
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
36
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
37
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
38
use Symfony\Component\Serializer\Serializer;
39

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

49
    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)
50
    {
51
    }
271✔
52

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

61
        foreach ($object->getResourceNameCollection() as $resourceClass) {
12✔
62
            $resourceMetadataCollection = $this->resourceMetadataFactory->create($resourceClass);
12✔
63

64
            $resourceMetadata = $resourceMetadataCollection[0];
12✔
65
            if ($resourceMetadata instanceof ErrorResource && ValidationException::class === $resourceMetadata->getClass()) {
12✔
66
                continue;
×
67
            }
68

69
            $shortName = $resourceMetadata->getShortName();
12✔
70
            $prefixedShortName = $resourceMetadata->getTypes()[0] ?? "#$shortName";
12✔
71
            $this->populateEntrypointProperties($resourceMetadata, $shortName, $prefixedShortName, $entrypointProperties, $resourceMetadataCollection);
12✔
72
            $classes[] = $this->getClass($resourceClass, $resourceMetadata, $shortName, $prefixedShortName, $context, $resourceMetadataCollection);
12✔
73
        }
74

75
        return $this->computeDoc($object, $this->getClasses($entrypointProperties, $classes));
12✔
76
    }
77

78
    /**
79
     * Populates entrypoint properties.
80
     */
81
    private function populateEntrypointProperties(ApiResource $resourceMetadata, string $shortName, string $prefixedShortName, array &$entrypointProperties, ?ResourceMetadataCollection $resourceMetadataCollection = null): void
82
    {
83
        $hydraCollectionOperations = $this->getHydraOperations(true, $resourceMetadataCollection);
12✔
84
        if (empty($hydraCollectionOperations)) {
12✔
85
            return;
4✔
86
        }
87

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

111
        if ($resourceMetadata->getDeprecationReason()) {
8✔
112
            $entrypointProperty['owl:deprecated'] = true;
×
113
        }
114

115
        $entrypointProperties[] = $entrypointProperty;
8✔
116
    }
117

118
    /**
119
     * Gets a Hydra class.
120
     */
121
    private function getClass(string $resourceClass, ApiResource $resourceMetadata, string $shortName, string $prefixedShortName, array $context, ?ResourceMetadataCollection $resourceMetadataCollection = null): array
122
    {
123
        $description = $resourceMetadata->getDescription();
12✔
124
        $isDeprecated = $resourceMetadata->getDeprecationReason();
12✔
125

126
        $class = [
12✔
127
            '@id' => $prefixedShortName,
12✔
128
            '@type' => 'hydra:Class',
12✔
129
            'rdfs:label' => $shortName,
12✔
130
            'hydra:title' => $shortName,
12✔
131
            'hydra:supportedProperty' => $this->getHydraProperties($resourceClass, $resourceMetadata, $shortName, $prefixedShortName, $context),
12✔
132
            'hydra:supportedOperation' => $this->getHydraOperations(false, $resourceMetadataCollection),
12✔
133
        ];
12✔
134

135
        if (null !== $description) {
12✔
136
            $class['hydra:description'] = $description;
12✔
137
        }
138

139
        if ($isDeprecated) {
12✔
140
            $class['owl:deprecated'] = true;
×
141
        }
142

143
        return $class;
12✔
144
    }
145

146
    /**
147
     * Creates context for property metatata factories.
148
     */
149
    private function getPropertyMetadataFactoryContext(ApiResource $resourceMetadata): array
150
    {
151
        $normalizationGroups = $resourceMetadata->getNormalizationContext()[AbstractNormalizer::GROUPS] ?? null;
12✔
152
        $denormalizationGroups = $resourceMetadata->getDenormalizationContext()[AbstractNormalizer::GROUPS] ?? null;
12✔
153
        $propertyContext = [
12✔
154
            'normalization_groups' => $normalizationGroups,
12✔
155
            'denormalization_groups' => $denormalizationGroups,
12✔
156
        ];
12✔
157
        $propertyNameContext = [];
12✔
158

159
        if ($normalizationGroups) {
12✔
160
            $propertyNameContext['serializer_groups'] = $normalizationGroups;
×
161
        }
162

163
        if (!$denormalizationGroups) {
12✔
164
            return [$propertyNameContext, $propertyContext];
12✔
165
        }
166

167
        if (!isset($propertyNameContext['serializer_groups'])) {
×
168
            $propertyNameContext['serializer_groups'] = $denormalizationGroups;
×
169

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

173
        foreach ($denormalizationGroups as $group) {
×
174
            $propertyNameContext['serializer_groups'][] = $group;
×
175
        }
176

177
        return [$propertyNameContext, $propertyContext];
×
178
    }
179

180
    /**
181
     * Gets Hydra properties.
182
     */
183
    private function getHydraProperties(string $resourceClass, ApiResource $resourceMetadata, string $shortName, string $prefixedShortName, array $context): array
184
    {
185
        $classes = [];
12✔
186

187
        $classes[$resourceClass] = true;
12✔
188
        foreach ($resourceMetadata->getOperations() as $operation) {
12✔
189
            /** @var Operation $operation */
190
            if (!$operation instanceof CollectionOperationInterface) {
12✔
191
                continue;
12✔
192
            }
193

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

199
            $outputMetadata = $operation->getOutput();
8✔
200
            if (null !== $outputClass = $outputMetadata['class'] ?? null) {
8✔
201
                $classes[$outputClass] = true;
4✔
202
            }
203
        }
204

205
        /** @var string[] $classes */
206
        $classes = array_keys($classes);
12✔
207
        $properties = [];
12✔
208
        [$propertyNameContext, $propertyContext] = $this->getPropertyMetadataFactoryContext($resourceMetadata);
12✔
209

210
        foreach ($classes as $class) {
12✔
211
            foreach ($this->propertyNameCollectionFactory->create($class, $propertyNameContext) as $propertyName) {
12✔
212
                $propertyMetadata = $this->propertyMetadataFactory->create($class, $propertyName, $propertyContext);
12✔
213

214
                if (true === $propertyMetadata->isIdentifier() && false === $propertyMetadata->isWritable()) {
12✔
215
                    continue;
×
216
                }
217

218
                if ($this->nameConverter) {
12✔
219
                    $propertyName = $this->nameConverter->normalize($propertyName, $class, self::FORMAT, $context);
4✔
220
                }
221

222
                $properties[] = $this->getProperty($propertyMetadata, $propertyName, $prefixedShortName, $shortName);
12✔
223
            }
224
        }
225

226
        return $properties;
12✔
227
    }
228

229
    /**
230
     * Gets Hydra operations.
231
     */
232
    private function getHydraOperations(bool $collection, ?ResourceMetadataCollection $resourceMetadataCollection = null): array
233
    {
234
        $hydraOperations = [];
12✔
235
        foreach ($resourceMetadataCollection as $resourceMetadata) {
12✔
236
            foreach ($resourceMetadata->getOperations() as $operation) {
12✔
237
                if (('POST' === $operation->getMethod() || $operation instanceof CollectionOperationInterface) !== $collection) {
12✔
238
                    continue;
12✔
239
                }
240

241
                $hydraOperations[] = $this->getHydraOperation($operation, $operation->getTypes()[0] ?? "#{$operation->getShortName()}");
12✔
242
            }
243
        }
244

245
        return $hydraOperations;
12✔
246
    }
247

248
    /**
249
     * Gets and populates if applicable a Hydra operation.
250
     */
251
    private function getHydraOperation(HttpOperation $operation, string $prefixedShortName): array
252
    {
253
        $method = $operation->getMethod() ?: 'GET';
12✔
254

255
        $hydraOperation = $operation->getHydraContext() ?? [];
12✔
256
        if ($operation->getDeprecationReason()) {
12✔
257
            $hydraOperation['owl:deprecated'] = true;
×
258
        }
259

260
        $shortName = $operation->getShortName();
12✔
261
        $inputMetadata = $operation->getInput() ?? [];
12✔
262
        $outputMetadata = $operation->getOutput() ?? [];
12✔
263

264
        $inputClass = \array_key_exists('class', $inputMetadata) ? $inputMetadata['class'] : false;
12✔
265
        $outputClass = \array_key_exists('class', $outputMetadata) ? $outputMetadata['class'] : false;
12✔
266

267
        if ('GET' === $method && $operation instanceof CollectionOperationInterface) {
12✔
268
            $hydraOperation += [
8✔
269
                '@type' => ['hydra:Operation', 'schema:FindAction'],
8✔
270
                'hydra:title' => "Retrieves the collection of $shortName resources.",
8✔
271
                'returns' => null === $outputClass ? 'owl:Nothing' : 'hydra:Collection',
8✔
272
            ];
8✔
273
        } elseif ('GET' === $method) {
12✔
274
            $hydraOperation += [
12✔
275
                '@type' => ['hydra:Operation', 'schema:FindAction'],
12✔
276
                'hydra:title' => "Retrieves a $shortName resource.",
12✔
277
                'returns' => null === $outputClass ? 'owl:Nothing' : $prefixedShortName,
12✔
278
            ];
12✔
279
        } elseif ('PATCH' === $method) {
8✔
280
            $hydraOperation += [
×
281
                '@type' => 'hydra:Operation',
×
282
                'hydra:title' => "Updates the $shortName resource.",
×
283
                'returns' => null === $outputClass ? 'owl:Nothing' : $prefixedShortName,
×
284
                'expects' => null === $inputClass ? 'owl:Nothing' : $prefixedShortName,
×
285
            ];
×
286
        } elseif ('POST' === $method) {
8✔
287
            $hydraOperation += [
8✔
288
                '@type' => ['hydra:Operation', 'schema:CreateAction'],
8✔
289
                'hydra:title' => "Creates a $shortName resource.",
8✔
290
                'returns' => null === $outputClass ? 'owl:Nothing' : $prefixedShortName,
8✔
291
                'expects' => null === $inputClass ? 'owl:Nothing' : $prefixedShortName,
8✔
292
            ];
8✔
293
        } elseif ('PUT' === $method) {
8✔
294
            $hydraOperation += [
8✔
295
                '@type' => ['hydra:Operation', 'schema:ReplaceAction'],
8✔
296
                'hydra:title' => "Replaces the $shortName resource.",
8✔
297
                'returns' => null === $outputClass ? 'owl:Nothing' : $prefixedShortName,
8✔
298
                'expects' => null === $inputClass ? 'owl:Nothing' : $prefixedShortName,
8✔
299
            ];
8✔
300
        } elseif ('DELETE' === $method) {
×
301
            $hydraOperation += [
×
302
                '@type' => ['hydra:Operation', 'schema:DeleteAction'],
×
303
                'hydra:title' => "Deletes the $shortName resource.",
×
304
                'returns' => 'owl:Nothing',
×
305
            ];
×
306
        }
307

308
        $hydraOperation['hydra:method'] ?? $hydraOperation['hydra:method'] = $method;
12✔
309

310
        if (!isset($hydraOperation['rdfs:label']) && isset($hydraOperation['hydra:title'])) {
12✔
311
            $hydraOperation['rdfs:label'] = $hydraOperation['hydra:title'];
12✔
312
        }
313

314
        ksort($hydraOperation);
12✔
315

316
        return $hydraOperation;
12✔
317
    }
318

319
    /**
320
     * Gets the range of the property.
321
     */
322
    private function getRange(ApiProperty $propertyMetadata): array|string|null
323
    {
324
        $jsonldContext = $propertyMetadata->getJsonldContext();
12✔
325

326
        if (isset($jsonldContext['@type'])) {
12✔
327
            return $jsonldContext['@type'];
4✔
328
        }
329

330
        $builtInTypes = $propertyMetadata->getBuiltinTypes() ?? [];
12✔
331
        $types = [];
12✔
332

333
        foreach ($builtInTypes as $type) {
12✔
334
            if ($type->isCollection() && null !== $collectionType = $type->getCollectionValueTypes()[0] ?? null) {
12✔
335
                $type = $collectionType;
4✔
336
            }
337

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

364
                    if (is_a($className, \DateTimeInterface::class, true)) {
4✔
365
                        if (!\in_array('xmls:dateTime', $types, true)) {
×
366
                            $types[] = 'xmls:dateTime';
×
367
                        }
368
                        break;
×
369
                    }
370

371
                    if ($this->resourceClassResolver->isResourceClass($className)) {
4✔
372
                        $resourceMetadata = $this->resourceMetadataFactory->create($className);
4✔
373
                        $operation = $resourceMetadata->getOperation();
4✔
374

375
                        if (!$operation instanceof HttpOperation || !$operation->getTypes()) {
4✔
376
                            if (!\in_array("#{$operation->getShortName()}", $types, true)) {
4✔
377
                                $types[] = "#{$operation->getShortName()}";
4✔
378
                            }
379
                            break;
4✔
380
                        }
381

382
                        $types = array_unique(array_merge($types, $operation->getTypes()));
×
383
                        break;
×
384
                    }
385
            }
386
        }
387

388
        if ([] === $types) {
12✔
389
            return null;
4✔
390
        }
391

392
        return 1 === \count($types) ? $types[0] : $types;
12✔
393
    }
394

395
    private function isSingleRelation(ApiProperty $propertyMetadata): bool
396
    {
397
        $builtInTypes = $propertyMetadata->getBuiltinTypes() ?? [];
12✔
398

399
        foreach ($builtInTypes as $type) {
12✔
400
            $className = $type->getClassName();
12✔
401
            if (!$type->isCollection()
12✔
402
                && null !== $className
12✔
403
                && $this->resourceClassResolver->isResourceClass($className)
12✔
404
            ) {
405
                return true;
×
406
            }
407
        }
408

409
        return false;
12✔
410
    }
411

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

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

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

491
        return $classes;
12✔
492
    }
493

494
    /**
495
     * Gets a property definition.
496
     */
497
    private function getProperty(ApiProperty $propertyMetadata, string $propertyName, string $prefixedShortName, string $shortName): array
498
    {
499
        if ($iri = $propertyMetadata->getIris()) {
12✔
500
            $iri = 1 === (is_countable($iri) ? \count($iri) : 0) ? $iri[0] : $iri;
4✔
501
        }
502

503
        if (!isset($iri)) {
12✔
504
            $iri = "#$shortName/$propertyName";
12✔
505
        }
506

507
        $propertyData = ($propertyMetadata->getJsonldContext()['hydra:property'] ?? []) + [
12✔
508
            '@id' => $iri,
12✔
509
            '@type' => false === $propertyMetadata->isReadableLink() ? 'hydra:Link' : 'rdf:Property',
12✔
510
            'rdfs:label' => $propertyName,
12✔
511
            'domain' => $prefixedShortName,
12✔
512
        ];
12✔
513

514
        if (!isset($propertyData['owl:deprecated']) && $propertyMetadata->getDeprecationReason()) {
12✔
515
            $propertyData['owl:deprecated'] = true;
×
516
        }
517

518
        if (!isset($propertyData['owl:maxCardinality']) && $this->isSingleRelation($propertyMetadata)) {
12✔
519
            $propertyData['owl:maxCardinality'] = 1;
×
520
        }
521

522
        if (!isset($propertyData['range']) && null !== $range = $this->getRange($propertyMetadata)) {
12✔
523
            $propertyData['range'] = $range;
12✔
524
        }
525

526
        $property = [
12✔
527
            '@type' => 'hydra:SupportedProperty',
12✔
528
            'hydra:property' => $propertyData,
12✔
529
            'hydra:title' => $propertyName,
12✔
530
            'hydra:required' => $propertyMetadata->isRequired(),
12✔
531
            'hydra:readable' => $propertyMetadata->isReadable(),
12✔
532
            'hydra:writeable' => $propertyMetadata->isWritable() || $propertyMetadata->isInitializable(),
12✔
533
        ];
12✔
534

535
        if (null !== $description = $propertyMetadata->getDescription()) {
12✔
536
            $property['hydra:description'] = $description;
12✔
537
        }
538

539
        return $property;
12✔
540
    }
541

542
    /**
543
     * Computes the documentation.
544
     */
545
    private function computeDoc(Documentation $object, array $classes): array
546
    {
547
        $doc = ['@context' => $this->getContext(), '@id' => $this->urlGenerator->generate('api_doc', ['_format' => self::FORMAT]), '@type' => 'hydra:ApiDocumentation'];
12✔
548

549
        if ('' !== $object->getTitle()) {
12✔
550
            $doc['hydra:title'] = $object->getTitle();
12✔
551
        }
552

553
        if ('' !== $object->getDescription()) {
12✔
554
            $doc['hydra:description'] = $object->getDescription();
12✔
555
        }
556

557
        $doc['hydra:entrypoint'] = $this->urlGenerator->generate('api_entrypoint');
12✔
558
        $doc['hydra:supportedClass'] = $classes;
12✔
559

560
        return $doc;
12✔
561
    }
562

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

584
    /**
585
     * {@inheritdoc}
586
     */
587
    public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool
588
    {
589
        return self::FORMAT === $format && $data instanceof Documentation;
4✔
590
    }
591

592
    public function getSupportedTypes($format): array
593
    {
594
        return self::FORMAT === $format ? [Documentation::class => true] : [];
239✔
595
    }
596

597
    public function hasCacheableSupportsMethod(): bool
598
    {
599
        if (method_exists(Serializer::class, 'getSupportedTypes')) {
×
600
            trigger_deprecation(
×
601
                'api-platform/core',
×
602
                '3.1',
×
603
                'The "%s()" method is deprecated, use "getSupportedTypes()" instead.',
×
604
                __METHOD__
×
605
            );
×
606
        }
607

608
        return true;
×
609
    }
610
}
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