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

api-platform / core / 10903050455

17 Sep 2024 12:29PM UTC coverage: 7.684% (+0.7%) from 6.96%
10903050455

push

github

web-flow
fix: swagger ui with route identifier (#6616)

2 of 6 new or added lines in 6 files covered. (33.33%)

9000 existing lines in 286 files now uncovered.

12668 of 164863 relevant lines covered (7.68%)

22.93 hits per line

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

99.09
/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 ApiPlatform\Validator\Exception\ValidationException;
33
use Symfony\Component\PropertyInfo\Type;
34
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
35
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
36
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
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
44
{
45
    use HydraPrefixTrait;
46
    public const FORMAT = 'jsonld';
47

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

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

UNCOV
68
        foreach ($object->getResourceNameCollection() as $resourceClass) {
12✔
UNCOV
69
            $resourceMetadataCollection = $this->resourceMetadataFactory->create($resourceClass);
12✔
70

UNCOV
71
            $resourceMetadata = $resourceMetadataCollection[0];
12✔
UNCOV
72
            if ($resourceMetadata instanceof ErrorResource && ValidationException::class === $resourceMetadata->getClass()) {
12✔
UNCOV
73
                continue;
12✔
74
            }
75

UNCOV
76
            $shortName = $resourceMetadata->getShortName();
12✔
UNCOV
77
            $prefixedShortName = $resourceMetadata->getTypes()[0] ?? "#$shortName";
12✔
UNCOV
78
            $this->populateEntrypointProperties($resourceMetadata, $shortName, $prefixedShortName, $entrypointProperties, $hydraPrefix, $resourceMetadataCollection);
12✔
UNCOV
79
            $classes[] = $this->getClass($resourceClass, $resourceMetadata, $shortName, $prefixedShortName, $context, $hydraPrefix, $resourceMetadataCollection);
12✔
80
        }
81

UNCOV
82
        return $this->computeDoc($object, $this->getClasses($entrypointProperties, $classes, $hydraPrefix), $hydraPrefix);
12✔
83
    }
84

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

UNCOV
95
        $entrypointProperty = [
12✔
UNCOV
96
            '@type' => $hydraPrefix.'SupportedProperty',
12✔
UNCOV
97
            $hydraPrefix.'property' => [
12✔
UNCOV
98
                '@id' => \sprintf('#Entrypoint/%s', lcfirst($shortName)),
12✔
UNCOV
99
                '@type' => $hydraPrefix.'Link',
12✔
UNCOV
100
                'domain' => '#Entrypoint',
12✔
UNCOV
101
                'rdfs:label' => "The collection of $shortName resources",
12✔
UNCOV
102
                'rdfs:range' => [
12✔
UNCOV
103
                    ['@id' => $hydraPrefix.'Collection'],
12✔
UNCOV
104
                    [
12✔
UNCOV
105
                        'owl:equivalentClass' => [
12✔
UNCOV
106
                            'owl:onProperty' => ['@id' => $hydraPrefix.'member'],
12✔
UNCOV
107
                            'owl:allValuesFrom' => ['@id' => $prefixedShortName],
12✔
UNCOV
108
                        ],
12✔
UNCOV
109
                    ],
12✔
UNCOV
110
                ],
12✔
UNCOV
111
                $hydraPrefix.'supportedOperation' => $hydraCollectionOperations,
12✔
UNCOV
112
            ],
12✔
UNCOV
113
            $hydraPrefix.'title' => "The collection of $shortName resources",
12✔
UNCOV
114
            $hydraPrefix.'readable' => true,
12✔
UNCOV
115
            $hydraPrefix.'writeable' => false,
12✔
UNCOV
116
        ];
12✔
117

UNCOV
118
        if ($resourceMetadata->getDeprecationReason()) {
12✔
UNCOV
119
            $entrypointProperty['owl:deprecated'] = true;
12✔
120
        }
121

UNCOV
122
        $entrypointProperties[] = $entrypointProperty;
12✔
123
    }
124

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

UNCOV
133
        $class = [
12✔
UNCOV
134
            '@id' => $prefixedShortName,
12✔
UNCOV
135
            '@type' => $hydraPrefix.'Class',
12✔
UNCOV
136
            'rdfs:label' => $shortName,
12✔
UNCOV
137
            $hydraPrefix.'title' => $shortName,
12✔
UNCOV
138
            $hydraPrefix.'supportedProperty' => $this->getHydraProperties($resourceClass, $resourceMetadata, $shortName, $prefixedShortName, $context, $hydraPrefix),
12✔
UNCOV
139
            $hydraPrefix.'supportedOperation' => $this->getHydraOperations(false, $resourceMetadataCollection, $hydraPrefix),
12✔
UNCOV
140
        ];
12✔
141

UNCOV
142
        if (null !== $description) {
12✔
UNCOV
143
            $class[$hydraPrefix.'description'] = $description;
12✔
144
        }
145

UNCOV
146
        if ($isDeprecated) {
12✔
UNCOV
147
            $class['owl:deprecated'] = true;
12✔
148
        }
149

UNCOV
150
        return $class;
12✔
151
    }
152

153
    /**
154
     * Creates context for property metatata factories.
155
     */
156
    private function getPropertyMetadataFactoryContext(ApiResource $resourceMetadata): array
157
    {
UNCOV
158
        $normalizationGroups = $resourceMetadata->getNormalizationContext()[AbstractNormalizer::GROUPS] ?? null;
12✔
UNCOV
159
        $denormalizationGroups = $resourceMetadata->getDenormalizationContext()[AbstractNormalizer::GROUPS] ?? null;
12✔
UNCOV
160
        $propertyContext = [
12✔
UNCOV
161
            'normalization_groups' => $normalizationGroups,
12✔
UNCOV
162
            'denormalization_groups' => $denormalizationGroups,
12✔
UNCOV
163
        ];
12✔
UNCOV
164
        $propertyNameContext = [];
12✔
165

UNCOV
166
        if ($normalizationGroups) {
12✔
UNCOV
167
            $propertyNameContext['serializer_groups'] = $normalizationGroups;
12✔
168
        }
169

UNCOV
170
        if (!$denormalizationGroups) {
12✔
UNCOV
171
            return [$propertyNameContext, $propertyContext];
12✔
172
        }
173

UNCOV
174
        if (!isset($propertyNameContext['serializer_groups'])) {
12✔
175
            $propertyNameContext['serializer_groups'] = $denormalizationGroups;
×
176

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

UNCOV
180
        foreach ($denormalizationGroups as $group) {
12✔
UNCOV
181
            $propertyNameContext['serializer_groups'][] = $group;
12✔
182
        }
183

UNCOV
184
        return [$propertyNameContext, $propertyContext];
12✔
185
    }
186

187
    /**
188
     * Gets Hydra properties.
189
     */
190
    private function getHydraProperties(string $resourceClass, ApiResource $resourceMetadata, string $shortName, string $prefixedShortName, array $context, string $hydraPrefix = ContextBuilder::HYDRA_PREFIX): array
191
    {
UNCOV
192
        $classes = [];
12✔
193

UNCOV
194
        $classes[$resourceClass] = true;
12✔
UNCOV
195
        foreach ($resourceMetadata->getOperations() as $operation) {
12✔
196
            /** @var Operation $operation */
UNCOV
197
            if (!$operation instanceof CollectionOperationInterface) {
12✔
UNCOV
198
                continue;
12✔
199
            }
200

UNCOV
201
            $inputMetadata = $operation->getInput();
12✔
UNCOV
202
            if (null !== $inputClass = $inputMetadata['class'] ?? null) {
12✔
UNCOV
203
                $classes[$inputClass] = true;
12✔
204
            }
205

UNCOV
206
            $outputMetadata = $operation->getOutput();
12✔
UNCOV
207
            if (null !== $outputClass = $outputMetadata['class'] ?? null) {
12✔
UNCOV
208
                $classes[$outputClass] = true;
12✔
209
            }
210
        }
211

212
        /** @var string[] $classes */
UNCOV
213
        $classes = array_keys($classes);
12✔
UNCOV
214
        $properties = [];
12✔
UNCOV
215
        [$propertyNameContext, $propertyContext] = $this->getPropertyMetadataFactoryContext($resourceMetadata);
12✔
UNCOV
216
        foreach ($classes as $class) {
12✔
UNCOV
217
            foreach ($this->propertyNameCollectionFactory->create($class, $propertyNameContext) as $propertyName) {
12✔
UNCOV
218
                $propertyMetadata = $this->propertyMetadataFactory->create($class, $propertyName, $propertyContext);
12✔
219

UNCOV
220
                if (true === $propertyMetadata->isIdentifier() && false === $propertyMetadata->isWritable()) {
12✔
UNCOV
221
                    continue;
12✔
222
                }
223

UNCOV
224
                if ($this->nameConverter) {
12✔
UNCOV
225
                    $propertyName = $this->nameConverter->normalize($propertyName, $class, self::FORMAT, $context);
12✔
226
                }
227

UNCOV
228
                $properties[] = $this->getProperty($propertyMetadata, $propertyName, $prefixedShortName, $shortName, $hydraPrefix);
12✔
229
            }
230
        }
231

UNCOV
232
        return $properties;
12✔
233
    }
234

235
    /**
236
     * Gets Hydra operations.
237
     */
238
    private function getHydraOperations(bool $collection, ?ResourceMetadataCollection $resourceMetadataCollection = null, string $hydraPrefix = ContextBuilder::HYDRA_PREFIX): array
239
    {
UNCOV
240
        $hydraOperations = [];
12✔
UNCOV
241
        foreach ($resourceMetadataCollection as $resourceMetadata) {
12✔
UNCOV
242
            foreach ($resourceMetadata->getOperations() as $operation) {
12✔
UNCOV
243
                if (('POST' === $operation->getMethod() || $operation instanceof CollectionOperationInterface) !== $collection) {
12✔
UNCOV
244
                    continue;
12✔
245
                }
246

UNCOV
247
                $hydraOperations[] = $this->getHydraOperation($operation, $operation->getTypes()[0] ?? "#{$operation->getShortName()}", $hydraPrefix);
12✔
248
            }
249
        }
250

UNCOV
251
        return $hydraOperations;
12✔
252
    }
253

254
    /**
255
     * Gets and populates if applicable a Hydra operation.
256
     */
257
    private function getHydraOperation(HttpOperation $operation, string $prefixedShortName, string $hydraPrefix): array
258
    {
UNCOV
259
        $method = $operation->getMethod() ?: 'GET';
12✔
260

UNCOV
261
        $hydraOperation = $operation->getHydraContext() ?? [];
12✔
UNCOV
262
        if ($operation->getDeprecationReason()) {
12✔
UNCOV
263
            $hydraOperation['owl:deprecated'] = true;
12✔
264
        }
265

UNCOV
266
        $shortName = $operation->getShortName();
12✔
UNCOV
267
        $inputMetadata = $operation->getInput() ?? [];
12✔
UNCOV
268
        $outputMetadata = $operation->getOutput() ?? [];
12✔
269

UNCOV
270
        $inputClass = \array_key_exists('class', $inputMetadata) ? $inputMetadata['class'] : false;
12✔
UNCOV
271
        $outputClass = \array_key_exists('class', $outputMetadata) ? $outputMetadata['class'] : false;
12✔
272

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

UNCOV
314
        $hydraOperation[$hydraPrefix.'method'] ?? $hydraOperation[$hydraPrefix.'method'] = $method;
12✔
315

UNCOV
316
        if (!isset($hydraOperation['rdfs:label']) && isset($hydraOperation[$hydraPrefix.'title'])) {
12✔
UNCOV
317
            $hydraOperation['rdfs:label'] = $hydraOperation[$hydraPrefix.'title'];
12✔
318
        }
319

UNCOV
320
        ksort($hydraOperation);
12✔
321

UNCOV
322
        return $hydraOperation;
12✔
323
    }
324

325
    /**
326
     * Gets the range of the property.
327
     */
328
    private function getRange(ApiProperty $propertyMetadata): array|string|null
329
    {
UNCOV
330
        $jsonldContext = $propertyMetadata->getJsonldContext();
12✔
331

UNCOV
332
        if (isset($jsonldContext['@type'])) {
12✔
UNCOV
333
            return $jsonldContext['@type'];
12✔
334
        }
335

UNCOV
336
        $builtInTypes = $propertyMetadata->getBuiltinTypes() ?? [];
12✔
UNCOV
337
        $types = [];
12✔
338

UNCOV
339
        foreach ($builtInTypes as $type) {
12✔
UNCOV
340
            if ($type->isCollection() && null !== $collectionType = $type->getCollectionValueTypes()[0] ?? null) {
12✔
UNCOV
341
                $type = $collectionType;
12✔
342
            }
343

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

UNCOV
370
                    if (is_a($className, \DateTimeInterface::class, true)) {
12✔
UNCOV
371
                        if (!\in_array('xmls:dateTime', $types, true)) {
12✔
UNCOV
372
                            $types[] = 'xmls:dateTime';
12✔
373
                        }
UNCOV
374
                        break;
12✔
375
                    }
376

UNCOV
377
                    if ($this->resourceClassResolver->isResourceClass($className)) {
12✔
UNCOV
378
                        $resourceMetadata = $this->resourceMetadataFactory->create($className);
12✔
UNCOV
379
                        $operation = $resourceMetadata->getOperation();
12✔
380

UNCOV
381
                        if (!$operation instanceof HttpOperation || !$operation->getTypes()) {
12✔
UNCOV
382
                            if (!\in_array("#{$operation->getShortName()}", $types, true)) {
12✔
UNCOV
383
                                $types[] = "#{$operation->getShortName()}";
12✔
384
                            }
UNCOV
385
                            break;
12✔
386
                        }
387

UNCOV
388
                        $types = array_unique(array_merge($types, $operation->getTypes()));
12✔
UNCOV
389
                        break;
12✔
390
                    }
391
            }
392
        }
393

UNCOV
394
        if ([] === $types) {
12✔
UNCOV
395
            return null;
12✔
396
        }
397

UNCOV
398
        return 1 === \count($types) ? $types[0] : $types;
12✔
399
    }
400

401
    private function isSingleRelation(ApiProperty $propertyMetadata): bool
402
    {
UNCOV
403
        $builtInTypes = $propertyMetadata->getBuiltinTypes() ?? [];
12✔
404

UNCOV
405
        foreach ($builtInTypes as $type) {
12✔
UNCOV
406
            $className = $type->getClassName();
12✔
407
            if (
UNCOV
408
                !$type->isCollection()
12✔
UNCOV
409
                && null !== $className
12✔
UNCOV
410
                && $this->resourceClassResolver->isResourceClass($className)
12✔
411
            ) {
UNCOV
412
                return true;
12✔
413
            }
414
        }
415

UNCOV
416
        return false;
12✔
417
    }
418

419
    /**
420
     * Builds the classes array.
421
     */
422
    private function getClasses(array $entrypointProperties, array $classes, string $hydraPrefix = ContextBuilder::HYDRA_PREFIX): array
423
    {
UNCOV
424
        $classes[] = [
12✔
UNCOV
425
            '@id' => '#Entrypoint',
12✔
UNCOV
426
            '@type' => $hydraPrefix.'Class',
12✔
UNCOV
427
            $hydraPrefix.'title' => 'The API entrypoint',
12✔
UNCOV
428
            $hydraPrefix.'supportedProperty' => $entrypointProperties,
12✔
UNCOV
429
            $hydraPrefix.'supportedOperation' => [
12✔
UNCOV
430
                '@type' => $hydraPrefix.'Operation',
12✔
UNCOV
431
                $hydraPrefix.'method' => 'GET',
12✔
UNCOV
432
                'rdfs:label' => 'The API entrypoint.',
12✔
UNCOV
433
                'returns' => '#EntryPoint',
12✔
UNCOV
434
            ],
12✔
UNCOV
435
        ];
12✔
436

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

474
        // Constraint violation list
UNCOV
475
        $classes[] = [
12✔
UNCOV
476
            '@id' => '#ConstraintViolationList',
12✔
UNCOV
477
            '@type' => $hydraPrefix.'Class',
12✔
UNCOV
478
            'subClassOf' => $hydraPrefix.'Error',
12✔
UNCOV
479
            $hydraPrefix.'title' => 'A constraint violation list',
12✔
UNCOV
480
            $hydraPrefix.'supportedProperty' => [
12✔
UNCOV
481
                [
12✔
UNCOV
482
                    '@type' => $hydraPrefix.'SupportedProperty',
12✔
UNCOV
483
                    $hydraPrefix.'property' => [
12✔
UNCOV
484
                        '@id' => '#ConstraintViolationList/violations',
12✔
UNCOV
485
                        '@type' => 'rdf:Property',
12✔
UNCOV
486
                        'rdfs:label' => 'violations',
12✔
UNCOV
487
                        'domain' => '#ConstraintViolationList',
12✔
UNCOV
488
                        'range' => '#ConstraintViolation',
12✔
UNCOV
489
                    ],
12✔
UNCOV
490
                    $hydraPrefix.'title' => 'violations',
12✔
UNCOV
491
                    $hydraPrefix.'description' => 'The violations',
12✔
UNCOV
492
                    $hydraPrefix.'readable' => true,
12✔
UNCOV
493
                    $hydraPrefix.'writeable' => false,
12✔
UNCOV
494
                ],
12✔
UNCOV
495
            ],
12✔
UNCOV
496
        ];
12✔
497

UNCOV
498
        return $classes;
12✔
499
    }
500

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

UNCOV
510
        if (!isset($iri)) {
12✔
UNCOV
511
            $iri = "#$shortName/$propertyName";
12✔
512
        }
513

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

UNCOV
521
        if (!isset($propertyData['owl:deprecated']) && $propertyMetadata->getDeprecationReason()) {
12✔
UNCOV
522
            $propertyData['owl:deprecated'] = true;
12✔
523
        }
524

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

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

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

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

UNCOV
546
        return $property;
12✔
547
    }
548

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

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

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

UNCOV
564
        $doc[$hydraPrefix.'entrypoint'] = $this->urlGenerator->generate('api_entrypoint');
12✔
UNCOV
565
        $doc[$hydraPrefix.'supportedClass'] = $classes;
12✔
566

UNCOV
567
        return $doc;
12✔
568
    }
569

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

591
    /**
592
     * {@inheritdoc}
593
     */
594
    public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool
595
    {
UNCOV
596
        return self::FORMAT === $format && $data instanceof Documentation;
12✔
597
    }
598

599
    public function getSupportedTypes($format): array
600
    {
UNCOV
601
        return self::FORMAT === $format ? [Documentation::class => true] : [];
2,076✔
602
    }
603
}
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