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

api-platform / core / 15133993414

20 May 2025 09:30AM UTC coverage: 26.313% (-1.2%) from 27.493%
15133993414

Pull #7161

github

web-flow
Merge e2c03d45f into 5459ba375
Pull Request #7161: fix(metadata): infer parameter string type from schema

0 of 2 new or added lines in 1 file covered. (0.0%)

11019 existing lines in 363 files now uncovered.

12898 of 49018 relevant lines covered (26.31%)

34.33 hits per line

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

99.07
/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\Documentation\Documentation;
17
use ApiPlatform\JsonLd\ContextBuilder;
18
use ApiPlatform\JsonLd\ContextBuilderInterface;
19
use ApiPlatform\JsonLd\Serializer\HydraPrefixTrait;
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 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

37
use const ApiPlatform\JsonLd\HYDRA_CONTEXT;
38

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

49
    public function __construct(
50
        private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory,
51
        private readonly PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory,
52
        private readonly PropertyMetadataFactoryInterface $propertyMetadataFactory,
53
        private readonly ResourceClassResolverInterface $resourceClassResolver,
54
        private readonly UrlGeneratorInterface $urlGenerator,
55
        private readonly ?NameConverterInterface $nameConverter = null,
56
        private readonly ?array $defaultContext = [],
57
        private readonly ?bool $entrypointEnabled = true,
58
    ) {
UNCOV
59
    }
950✔
60

61
    /**
62
     * {@inheritdoc}
63
     */
64
    public function normalize(mixed $object, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null
65
    {
UNCOV
66
        $classes = [];
5✔
UNCOV
67
        $entrypointProperties = [];
5✔
UNCOV
68
        $hydraPrefix = $this->getHydraPrefix($context + $this->defaultContext);
5✔
69

UNCOV
70
        foreach ($object->getResourceNameCollection() as $resourceClass) {
5✔
UNCOV
71
            $resourceMetadataCollection = $this->resourceMetadataFactory->create($resourceClass);
5✔
72

UNCOV
73
            $resourceMetadata = $resourceMetadataCollection[0];
5✔
UNCOV
74
            if (true === $resourceMetadata->getHideHydraOperation()) {
5✔
UNCOV
75
                continue;
5✔
76
            }
77

UNCOV
78
            $shortName = $resourceMetadata->getShortName();
5✔
UNCOV
79
            $prefixedShortName = $resourceMetadata->getTypes()[0] ?? "#$shortName";
5✔
80

UNCOV
81
            $this->populateEntrypointProperties($resourceMetadata, $shortName, $prefixedShortName, $entrypointProperties, $hydraPrefix, $resourceMetadataCollection);
5✔
UNCOV
82
            $classes[] = $this->getClass($resourceClass, $resourceMetadata, $shortName, $prefixedShortName, $context, $hydraPrefix, $resourceMetadataCollection);
5✔
83
        }
84

UNCOV
85
        return $this->computeDoc($object, $this->getClasses($entrypointProperties, $classes, $hydraPrefix), $hydraPrefix);
5✔
86
    }
87

88
    /**
89
     * Populates entrypoint properties.
90
     */
91
    private function populateEntrypointProperties(ApiResource $resourceMetadata, string $shortName, string $prefixedShortName, array &$entrypointProperties, string $hydraPrefix, ?ResourceMetadataCollection $resourceMetadataCollection = null): void
92
    {
UNCOV
93
        $hydraCollectionOperations = $this->getHydraOperations(true, $resourceMetadataCollection, $hydraPrefix);
5✔
UNCOV
94
        if (empty($hydraCollectionOperations)) {
5✔
UNCOV
95
            return;
5✔
96
        }
97

98
        $entrypointProperty = [
4✔
99
            '@type' => $hydraPrefix.'SupportedProperty',
4✔
100
            $hydraPrefix.'property' => [
4✔
101
                '@id' => \sprintf('#Entrypoint/%s', lcfirst($shortName)),
4✔
102
                '@type' => $hydraPrefix.'Link',
4✔
103
                'domain' => '#Entrypoint',
4✔
104
                'owl:maxCardinality' => 1,
4✔
105
                'range' => [
4✔
106
                    ['@id' => $hydraPrefix.'Collection'],
4✔
107
                    [
4✔
108
                        'owl:equivalentClass' => [
4✔
109
                            'owl:onProperty' => ['@id' => $hydraPrefix.'member'],
4✔
110
                            'owl:allValuesFrom' => ['@id' => $prefixedShortName],
4✔
111
                        ],
4✔
112
                    ],
4✔
113
                ],
4✔
114
                $hydraPrefix.'supportedOperation' => $hydraCollectionOperations,
4✔
115
            ],
4✔
116
            $hydraPrefix.'title' => "get{$shortName}Collection",
4✔
117
            $hydraPrefix.'description' => "The collection of $shortName resources",
4✔
118
            $hydraPrefix.'readable' => true,
4✔
119
            $hydraPrefix.'writeable' => false,
4✔
120
        ];
4✔
121

122
        if ($resourceMetadata->getDeprecationReason()) {
4✔
123
            $entrypointProperty['owl:deprecated'] = true;
4✔
124
        }
125

126
        $entrypointProperties[] = $entrypointProperty;
4✔
127
    }
128

129
    /**
130
     * Gets a Hydra class.
131
     */
132
    private function getClass(string $resourceClass, ApiResource $resourceMetadata, string $shortName, string $prefixedShortName, array $context, string $hydraPrefix, ?ResourceMetadataCollection $resourceMetadataCollection = null): array
133
    {
UNCOV
134
        $description = $resourceMetadata->getDescription();
5✔
UNCOV
135
        $isDeprecated = $resourceMetadata->getDeprecationReason();
5✔
136

UNCOV
137
        $class = [
5✔
UNCOV
138
            '@id' => $prefixedShortName,
5✔
UNCOV
139
            '@type' => $hydraPrefix.'Class',
5✔
UNCOV
140
            $hydraPrefix.'title' => $shortName,
5✔
UNCOV
141
            $hydraPrefix.'supportedProperty' => $this->getHydraProperties($resourceClass, $resourceMetadata, $shortName, $prefixedShortName, $context, $hydraPrefix),
5✔
UNCOV
142
            $hydraPrefix.'supportedOperation' => $this->getHydraOperations(false, $resourceMetadataCollection, $hydraPrefix),
5✔
UNCOV
143
        ];
5✔
144

UNCOV
145
        if (null !== $description) {
5✔
UNCOV
146
            $class[$hydraPrefix.'description'] = $description;
5✔
147
        }
148

UNCOV
149
        if ($resourceMetadata instanceof ErrorResource) {
5✔
UNCOV
150
            $class['subClassOf'] = 'Error';
5✔
151
        }
152

UNCOV
153
        if ($isDeprecated) {
5✔
154
            $class['owl:deprecated'] = true;
4✔
155
        }
156

UNCOV
157
        return $class;
5✔
158
    }
159

160
    /**
161
     * Creates context for property metatata factories.
162
     */
163
    private function getPropertyMetadataFactoryContext(ApiResource $resourceMetadata): array
164
    {
UNCOV
165
        $normalizationGroups = $resourceMetadata->getNormalizationContext()[AbstractNormalizer::GROUPS] ?? null;
5✔
UNCOV
166
        $denormalizationGroups = $resourceMetadata->getDenormalizationContext()[AbstractNormalizer::GROUPS] ?? null;
5✔
UNCOV
167
        $propertyContext = [
5✔
UNCOV
168
            'normalization_groups' => $normalizationGroups,
5✔
UNCOV
169
            'denormalization_groups' => $denormalizationGroups,
5✔
UNCOV
170
        ];
5✔
UNCOV
171
        $propertyNameContext = [];
5✔
172

UNCOV
173
        if ($normalizationGroups) {
5✔
174
            $propertyNameContext['serializer_groups'] = $normalizationGroups;
4✔
175
        }
176

UNCOV
177
        if (!$denormalizationGroups) {
5✔
UNCOV
178
            return [$propertyNameContext, $propertyContext];
5✔
179
        }
180

181
        if (!isset($propertyNameContext['serializer_groups'])) {
4✔
182
            $propertyNameContext['serializer_groups'] = $denormalizationGroups;
×
183

184
            return [$propertyNameContext, $propertyContext];
×
185
        }
186

187
        foreach ($denormalizationGroups as $group) {
4✔
188
            $propertyNameContext['serializer_groups'][] = $group;
4✔
189
        }
190

191
        return [$propertyNameContext, $propertyContext];
4✔
192
    }
193

194
    /**
195
     * Gets Hydra properties.
196
     */
197
    private function getHydraProperties(string $resourceClass, ApiResource $resourceMetadata, string $shortName, string $prefixedShortName, array $context, string $hydraPrefix = ContextBuilder::HYDRA_PREFIX): array
198
    {
UNCOV
199
        $classes = [];
5✔
200

UNCOV
201
        $classes[$resourceClass] = true;
5✔
UNCOV
202
        foreach ($resourceMetadata->getOperations() as $operation) {
5✔
203
            /** @var Operation $operation */
UNCOV
204
            if (!$operation instanceof CollectionOperationInterface) {
5✔
UNCOV
205
                continue;
5✔
206
            }
207

UNCOV
208
            $inputMetadata = $operation->getInput();
5✔
UNCOV
209
            if (null !== $inputClass = $inputMetadata['class'] ?? null) {
5✔
210
                $classes[$inputClass] = true;
4✔
211
            }
212

UNCOV
213
            $outputMetadata = $operation->getOutput();
5✔
UNCOV
214
            if (null !== $outputClass = $outputMetadata['class'] ?? null) {
5✔
215
                $classes[$outputClass] = true;
4✔
216
            }
217
        }
218

219
        /** @var string[] $classes */
UNCOV
220
        $classes = array_keys($classes);
5✔
UNCOV
221
        $properties = [];
5✔
UNCOV
222
        [$propertyNameContext, $propertyContext] = $this->getPropertyMetadataFactoryContext($resourceMetadata);
5✔
UNCOV
223
        foreach ($classes as $class) {
5✔
UNCOV
224
            foreach ($this->propertyNameCollectionFactory->create($class, $propertyNameContext) as $propertyName) {
5✔
UNCOV
225
                $propertyMetadata = $this->propertyMetadataFactory->create($class, $propertyName, $propertyContext);
5✔
226

UNCOV
227
                if (true === $propertyMetadata->isIdentifier() && false === $propertyMetadata->isWritable()) {
5✔
UNCOV
228
                    continue;
5✔
229
                }
230

UNCOV
231
                if ($this->nameConverter) {
5✔
UNCOV
232
                    $propertyName = $this->nameConverter->normalize($propertyName, $class, self::FORMAT, $context);
5✔
233
                }
234

UNCOV
235
                if (false === $propertyMetadata->getHydra()) {
5✔
UNCOV
236
                    continue;
5✔
237
                }
238

UNCOV
239
                $properties[] = $this->getProperty($propertyMetadata, $propertyName, $prefixedShortName, $shortName, $hydraPrefix);
5✔
240
            }
241
        }
242

UNCOV
243
        return $properties;
5✔
244
    }
245

246
    /**
247
     * Gets Hydra operations.
248
     */
249
    private function getHydraOperations(bool $collection, ?ResourceMetadataCollection $resourceMetadataCollection = null, string $hydraPrefix = ContextBuilder::HYDRA_PREFIX): array
250
    {
UNCOV
251
        $hydraOperations = [];
5✔
UNCOV
252
        foreach ($resourceMetadataCollection as $resourceMetadata) {
5✔
UNCOV
253
            foreach ($resourceMetadata->getOperations() as $operation) {
5✔
UNCOV
254
                if (true === $operation->getHideHydraOperation()) {
5✔
UNCOV
255
                    continue;
5✔
256
                }
257

UNCOV
258
                if (('POST' === $operation->getMethod() || $operation instanceof CollectionOperationInterface) !== $collection) {
5✔
UNCOV
259
                    continue;
5✔
260
                }
261

UNCOV
262
                $hydraOperations[] = $this->getHydraOperation($operation, $operation->getShortName(), $hydraPrefix);
5✔
263
            }
264
        }
265

UNCOV
266
        return $hydraOperations;
5✔
267
    }
268

269
    /**
270
     * Gets and populates if applicable a Hydra operation.
271
     */
272
    private function getHydraOperation(HttpOperation $operation, string $prefixedShortName, string $hydraPrefix): array
273
    {
UNCOV
274
        $method = $operation->getMethod() ?: 'GET';
5✔
275

UNCOV
276
        $hydraOperation = $operation->getHydraContext() ?? [];
5✔
UNCOV
277
        if ($operation->getDeprecationReason()) {
5✔
278
            $hydraOperation['owl:deprecated'] = true;
4✔
279
        }
280

UNCOV
281
        $shortName = $operation->getShortName();
5✔
UNCOV
282
        $inputMetadata = $operation->getInput() ?? [];
5✔
UNCOV
283
        $outputMetadata = $operation->getOutput() ?? [];
5✔
284

UNCOV
285
        $inputClass = \array_key_exists('class', $inputMetadata) ? $inputMetadata['class'] : false;
5✔
UNCOV
286
        $outputClass = \array_key_exists('class', $outputMetadata) ? $outputMetadata['class'] : false;
5✔
287

UNCOV
288
        if ('GET' === $method && $operation instanceof CollectionOperationInterface) {
5✔
289
            $hydraOperation += [
4✔
290
                '@type' => [$hydraPrefix.'Operation', 'schema:FindAction'],
4✔
291
                $hydraPrefix.'description' => "Retrieves the collection of $shortName resources.",
4✔
292
                'returns' => null === $outputClass ? 'owl:Nothing' : $hydraPrefix.'Collection',
4✔
293
            ];
4✔
UNCOV
294
        } elseif ('GET' === $method) {
5✔
UNCOV
295
            $hydraOperation += [
5✔
UNCOV
296
                '@type' => [$hydraPrefix.'Operation', 'schema:FindAction'],
5✔
UNCOV
297
                $hydraPrefix.'description' => "Retrieves a $shortName resource.",
5✔
UNCOV
298
                'returns' => null === $outputClass ? 'owl:Nothing' : $prefixedShortName,
5✔
UNCOV
299
            ];
5✔
300
        } elseif ('PATCH' === $method) {
4✔
301
            $hydraOperation += [
4✔
302
                '@type' => $hydraPrefix.'Operation',
4✔
303
                $hydraPrefix.'description' => "Updates the $shortName resource.",
4✔
304
                'returns' => null === $outputClass ? 'owl:Nothing' : $prefixedShortName,
4✔
305
                'expects' => null === $inputClass ? 'owl:Nothing' : $prefixedShortName,
4✔
306
            ];
4✔
307

308
            if (null !== $inputClass) {
4✔
309
                $possibleValue = [];
4✔
310
                foreach ($operation->getInputFormats() as $mimeTypes) {
4✔
311
                    foreach ($mimeTypes as $mimeType) {
4✔
312
                        $possibleValue[] = $mimeType;
4✔
313
                    }
314
                }
315

316
                $hydraOperation['expectsHeader'] = [['headerName' => 'Content-Type', 'possibleValue' => $possibleValue]];
4✔
317
            }
318
        } elseif ('POST' === $method) {
4✔
319
            $hydraOperation += [
4✔
320
                '@type' => [$hydraPrefix.'Operation', 'schema:CreateAction'],
4✔
321
                $hydraPrefix.'description' => "Creates a $shortName resource.",
4✔
322
                'returns' => null === $outputClass ? 'owl:Nothing' : $prefixedShortName,
4✔
323
                'expects' => null === $inputClass ? 'owl:Nothing' : $prefixedShortName,
4✔
324
            ];
4✔
325
        } elseif ('PUT' === $method) {
4✔
326
            $hydraOperation += [
4✔
327
                '@type' => [$hydraPrefix.'Operation', 'schema:ReplaceAction'],
4✔
328
                $hydraPrefix.'description' => "Replaces the $shortName resource.",
4✔
329
                'returns' => null === $outputClass ? 'owl:Nothing' : $prefixedShortName,
4✔
330
                'expects' => null === $inputClass ? 'owl:Nothing' : $prefixedShortName,
4✔
331
            ];
4✔
332
        } elseif ('DELETE' === $method) {
4✔
333
            $hydraOperation += [
4✔
334
                '@type' => [$hydraPrefix.'Operation', 'schema:DeleteAction'],
4✔
335
                $hydraPrefix.'description' => "Deletes the $shortName resource.",
4✔
336
                'returns' => 'owl:Nothing',
4✔
337
            ];
4✔
338
        }
339

UNCOV
340
        $hydraOperation[$hydraPrefix.'method'] ??= $method;
5✔
UNCOV
341
        $hydraOperation[$hydraPrefix.'title'] ??= strtolower($method).$shortName.($operation instanceof CollectionOperationInterface ? 'Collection' : '');
5✔
342

UNCOV
343
        ksort($hydraOperation);
5✔
344

UNCOV
345
        return $hydraOperation;
5✔
346
    }
347

348
    /**
349
     * Gets the range of the property.
350
     */
351
    private function getRange(ApiProperty $propertyMetadata): array|string|null
352
    {
UNCOV
353
        $jsonldContext = $propertyMetadata->getJsonldContext();
5✔
354

UNCOV
355
        if (isset($jsonldContext['@type'])) {
5✔
UNCOV
356
            return $jsonldContext['@type'];
5✔
357
        }
358

UNCOV
359
        $builtInTypes = $propertyMetadata->getBuiltinTypes() ?? [];
5✔
UNCOV
360
        $types = [];
5✔
361

UNCOV
362
        foreach ($builtInTypes as $type) {
5✔
UNCOV
363
            if ($type->isCollection() && null !== $collectionType = $type->getCollectionValueTypes()[0] ?? null) {
5✔
364
                $type = $collectionType;
4✔
365
            }
366

UNCOV
367
            switch ($type->getBuiltinType()) {
5✔
UNCOV
368
                case Type::BUILTIN_TYPE_STRING:
4✔
UNCOV
369
                    if (!\in_array('xmls:string', $types, true)) {
5✔
UNCOV
370
                        $types[] = 'xmls:string';
5✔
371
                    }
UNCOV
372
                    break;
5✔
UNCOV
373
                case Type::BUILTIN_TYPE_INT:
4✔
UNCOV
374
                    if (!\in_array('xmls:integer', $types, true)) {
5✔
UNCOV
375
                        $types[] = 'xmls:integer';
5✔
376
                    }
UNCOV
377
                    break;
5✔
UNCOV
378
                case Type::BUILTIN_TYPE_FLOAT:
4✔
379
                    if (!\in_array('xmls:decimal', $types, true)) {
4✔
380
                        $types[] = 'xmls:decimal';
4✔
381
                    }
382
                    break;
4✔
UNCOV
383
                case Type::BUILTIN_TYPE_BOOL:
4✔
384
                    if (!\in_array('xmls:boolean', $types, true)) {
4✔
385
                        $types[] = 'xmls:boolean';
4✔
386
                    }
387
                    break;
4✔
UNCOV
388
                case Type::BUILTIN_TYPE_OBJECT:
4✔
UNCOV
389
                    if (null === $className = $type->getClassName()) {
5✔
390
                        continue 2;
×
391
                    }
392

UNCOV
393
                    if (is_a($className, \DateTimeInterface::class, true)) {
5✔
394
                        if (!\in_array('xmls:dateTime', $types, true)) {
4✔
395
                            $types[] = 'xmls:dateTime';
4✔
396
                        }
397
                        break;
4✔
398
                    }
399

UNCOV
400
                    if ($this->resourceClassResolver->isResourceClass($className)) {
5✔
401
                        $resourceMetadata = $this->resourceMetadataFactory->create($className);
4✔
402
                        $operation = $resourceMetadata->getOperation();
4✔
403

404
                        if (!$operation instanceof HttpOperation || !$operation->getTypes()) {
4✔
405
                            if (!\in_array("#{$operation->getShortName()}", $types, true)) {
4✔
406
                                $types[] = "#{$operation->getShortName()}";
4✔
407
                            }
408
                            break;
4✔
409
                        }
410

411
                        $types = array_unique(array_merge($types, $operation->getTypes()));
4✔
412
                        break;
4✔
413
                    }
414
            }
415
        }
416

UNCOV
417
        if ([] === $types) {
5✔
UNCOV
418
            return null;
5✔
419
        }
420

UNCOV
421
        return 1 === \count($types) ? $types[0] : $types;
5✔
422
    }
423

424
    private function isSingleRelation(ApiProperty $propertyMetadata): bool
425
    {
UNCOV
426
        $builtInTypes = $propertyMetadata->getBuiltinTypes() ?? [];
5✔
427

UNCOV
428
        foreach ($builtInTypes as $type) {
5✔
UNCOV
429
            $className = $type->getClassName();
5✔
430
            if (
UNCOV
431
                !$type->isCollection()
5✔
UNCOV
432
                && null !== $className
5✔
UNCOV
433
                && $this->resourceClassResolver->isResourceClass($className)
5✔
434
            ) {
435
                return true;
4✔
436
            }
437
        }
438

UNCOV
439
        return false;
5✔
440
    }
441

442
    /**
443
     * Builds the classes array.
444
     */
445
    private function getClasses(array $entrypointProperties, array $classes, string $hydraPrefix = ContextBuilder::HYDRA_PREFIX): array
446
    {
UNCOV
447
        if ($this->entrypointEnabled) {
5✔
UNCOV
448
            $classes[] = [
5✔
UNCOV
449
                '@id' => '#Entrypoint',
5✔
UNCOV
450
                '@type' => $hydraPrefix.'Class',
5✔
UNCOV
451
                $hydraPrefix.'title' => 'Entrypoint',
5✔
UNCOV
452
                $hydraPrefix.'supportedProperty' => $entrypointProperties,
5✔
UNCOV
453
                $hydraPrefix.'supportedOperation' => [
5✔
UNCOV
454
                    '@type' => $hydraPrefix.'Operation',
5✔
UNCOV
455
                    $hydraPrefix.'method' => 'GET',
5✔
UNCOV
456
                    $hydraPrefix.'title' => 'index',
5✔
UNCOV
457
                    $hydraPrefix.'description' => 'The API Entrypoint.',
5✔
UNCOV
458
                    $hydraPrefix.'returns' => 'Entrypoint',
5✔
UNCOV
459
                ],
5✔
UNCOV
460
            ];
5✔
461
        }
462

UNCOV
463
        $classes[] = [
5✔
UNCOV
464
            '@id' => '#ConstraintViolationList',
5✔
UNCOV
465
            '@type' => $hydraPrefix.'Class',
5✔
UNCOV
466
            $hydraPrefix.'title' => 'ConstraintViolationList',
5✔
UNCOV
467
            $hydraPrefix.'description' => 'A constraint violation List.',
5✔
UNCOV
468
            $hydraPrefix.'supportedProperty' => [
5✔
UNCOV
469
                [
5✔
UNCOV
470
                    '@type' => $hydraPrefix.'SupportedProperty',
5✔
UNCOV
471
                    $hydraPrefix.'property' => [
5✔
UNCOV
472
                        '@id' => '#ConstraintViolationList/propertyPath',
5✔
UNCOV
473
                        '@type' => 'rdf:Property',
5✔
UNCOV
474
                        'rdfs:label' => 'propertyPath',
5✔
UNCOV
475
                        'domain' => '#ConstraintViolationList',
5✔
UNCOV
476
                        'range' => 'xmls:string',
5✔
UNCOV
477
                    ],
5✔
UNCOV
478
                    $hydraPrefix.'title' => 'propertyPath',
5✔
UNCOV
479
                    $hydraPrefix.'description' => 'The property path of the violation',
5✔
UNCOV
480
                    $hydraPrefix.'readable' => true,
5✔
UNCOV
481
                    $hydraPrefix.'writeable' => false,
5✔
UNCOV
482
                ],
5✔
UNCOV
483
                [
5✔
UNCOV
484
                    '@type' => $hydraPrefix.'SupportedProperty',
5✔
UNCOV
485
                    $hydraPrefix.'property' => [
5✔
UNCOV
486
                        '@id' => '#ConstraintViolationList/message',
5✔
UNCOV
487
                        '@type' => 'rdf:Property',
5✔
UNCOV
488
                        'rdfs:label' => 'message',
5✔
UNCOV
489
                        'domain' => '#ConstraintViolationList',
5✔
UNCOV
490
                        'range' => 'xmls:string',
5✔
UNCOV
491
                    ],
5✔
UNCOV
492
                    $hydraPrefix.'title' => 'message',
5✔
UNCOV
493
                    $hydraPrefix.'description' => 'The message associated with the violation',
5✔
UNCOV
494
                    $hydraPrefix.'readable' => true,
5✔
UNCOV
495
                    $hydraPrefix.'writeable' => false,
5✔
UNCOV
496
                ],
5✔
UNCOV
497
            ],
5✔
UNCOV
498
        ];
5✔
499

UNCOV
500
        return $classes;
5✔
501
    }
502

503
    /**
504
     * Gets a property definition.
505
     */
506
    private function getProperty(ApiProperty $propertyMetadata, string $propertyName, string $prefixedShortName, string $shortName, string $hydraPrefix): array
507
    {
UNCOV
508
        if ($iri = $propertyMetadata->getIris()) {
5✔
509
            $iri = 1 === (is_countable($iri) ? \count($iri) : 0) ? $iri[0] : $iri;
4✔
510
        }
511

UNCOV
512
        if (!isset($iri)) {
5✔
UNCOV
513
            $iri = "#$shortName/$propertyName";
5✔
514
        }
515

UNCOV
516
        $propertyData = ($propertyMetadata->getJsonldContext()[$hydraPrefix.'property'] ?? []) + [
5✔
UNCOV
517
            '@id' => $iri,
5✔
UNCOV
518
            '@type' => false === $propertyMetadata->isReadableLink() ? $hydraPrefix.'Link' : 'rdf:Property',
5✔
UNCOV
519
            'domain' => $prefixedShortName,
5✔
UNCOV
520
            'label' => $propertyName,
5✔
UNCOV
521
        ];
5✔
522

UNCOV
523
        if (!isset($propertyData['owl:deprecated']) && $propertyMetadata->getDeprecationReason()) {
5✔
524
            $propertyData['owl:deprecated'] = true;
4✔
525
        }
526

UNCOV
527
        if (!isset($propertyData['owl:maxCardinality']) && $this->isSingleRelation($propertyMetadata)) {
5✔
528
            $propertyData['owl:maxCardinality'] = 1;
4✔
529
        }
530

UNCOV
531
        if (!isset($propertyData['range']) && null !== $range = $this->getRange($propertyMetadata)) {
5✔
UNCOV
532
            $propertyData['range'] = $range;
5✔
533
        }
534

UNCOV
535
        $property = [
5✔
UNCOV
536
            '@type' => $hydraPrefix.'SupportedProperty',
5✔
UNCOV
537
            $hydraPrefix.'property' => $propertyData,
5✔
UNCOV
538
            $hydraPrefix.'title' => $propertyName,
5✔
UNCOV
539
            $hydraPrefix.'required' => $propertyMetadata->isRequired() ?? false,
5✔
UNCOV
540
            $hydraPrefix.'readable' => $propertyMetadata->isReadable(),
5✔
UNCOV
541
            $hydraPrefix.'writeable' => $propertyMetadata->isWritable() || $propertyMetadata->isInitializable(),
5✔
UNCOV
542
        ];
5✔
543

UNCOV
544
        if (null !== $description = $propertyMetadata->getDescription()) {
5✔
UNCOV
545
            $property[$hydraPrefix.'description'] = $description;
5✔
546
        }
547

UNCOV
548
        return $property;
5✔
549
    }
550

551
    /**
552
     * Computes the documentation.
553
     */
554
    private function computeDoc(Documentation $object, array $classes, string $hydraPrefix = ContextBuilder::HYDRA_PREFIX): array
555
    {
UNCOV
556
        $doc = ['@context' => $this->getContext($hydraPrefix), '@id' => $this->urlGenerator->generate('api_doc', ['_format' => self::FORMAT]), '@type' => $hydraPrefix.'ApiDocumentation'];
5✔
557

UNCOV
558
        if ('' !== $object->getTitle()) {
5✔
UNCOV
559
            $doc[$hydraPrefix.'title'] = $object->getTitle();
5✔
560
        }
561

UNCOV
562
        if ('' !== $object->getDescription()) {
5✔
UNCOV
563
            $doc[$hydraPrefix.'description'] = $object->getDescription();
5✔
564
        }
565

UNCOV
566
        if ($this->entrypointEnabled) {
5✔
UNCOV
567
            $doc[$hydraPrefix.'entrypoint'] = $this->urlGenerator->generate('api_entrypoint');
5✔
568
        }
UNCOV
569
        $doc[$hydraPrefix.'supportedClass'] = $classes;
5✔
570

UNCOV
571
        return $doc;
5✔
572
    }
573

574
    /**
575
     * Builds the JSON-LD context for the API documentation.
576
     */
577
    private function getContext(string $hydraPrefix = ContextBuilder::HYDRA_PREFIX): array
578
    {
UNCOV
579
        return [
5✔
UNCOV
580
            HYDRA_CONTEXT,
5✔
UNCOV
581
            [
5✔
UNCOV
582
                '@vocab' => $this->urlGenerator->generate('api_doc', ['_format' => self::FORMAT], UrlGeneratorInterface::ABS_URL).'#',
5✔
UNCOV
583
                'hydra' => ContextBuilderInterface::HYDRA_NS,
5✔
UNCOV
584
                'rdf' => ContextBuilderInterface::RDF_NS,
5✔
UNCOV
585
                'rdfs' => ContextBuilderInterface::RDFS_NS,
5✔
UNCOV
586
                'xmls' => ContextBuilderInterface::XML_NS,
5✔
UNCOV
587
                'owl' => ContextBuilderInterface::OWL_NS,
5✔
UNCOV
588
                'schema' => ContextBuilderInterface::SCHEMA_ORG_NS,
5✔
UNCOV
589
                'domain' => ['@id' => 'rdfs:domain', '@type' => '@id'],
5✔
UNCOV
590
                'range' => ['@id' => 'rdfs:range', '@type' => '@id'],
5✔
UNCOV
591
                'subClassOf' => ['@id' => 'rdfs:subClassOf', '@type' => '@id'],
5✔
UNCOV
592
            ],
5✔
UNCOV
593
        ];
5✔
594
    }
595

596
    /**
597
     * {@inheritdoc}
598
     */
599
    public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool
600
    {
UNCOV
601
        return self::FORMAT === $format && $data instanceof Documentation;
5✔
602
    }
603

604
    public function getSupportedTypes($format): array
605
    {
UNCOV
606
        return self::FORMAT === $format ? [Documentation::class => true] : [];
869✔
607
    }
608
}
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