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

api-platform / core / 10729306835

05 Sep 2024 10:46PM UTC coverage: 7.655% (-0.01%) from 7.665%
10729306835

push

github

web-flow
Merge pull request #6586 from soyuka/merge-342

Merge 3.4

0 of 54 new or added lines in 12 files covered. (0.0%)

8760 existing lines in 277 files now uncovered.

12505 of 163357 relevant lines covered (7.66%)

22.84 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\ContextBuilderInterface;
18
use ApiPlatform\Metadata\ApiProperty;
19
use ApiPlatform\Metadata\ApiResource;
20
use ApiPlatform\Metadata\CollectionOperationInterface;
21
use ApiPlatform\Metadata\ErrorResource;
22
use ApiPlatform\Metadata\HttpOperation;
23
use ApiPlatform\Metadata\Operation;
24
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
25
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
26
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
27
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
28
use ApiPlatform\Metadata\ResourceClassResolverInterface;
29
use ApiPlatform\Metadata\UrlGeneratorInterface;
30
use ApiPlatform\Validator\Exception\ValidationException;
31
use Symfony\Component\PropertyInfo\Type;
32
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
33
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
34
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
35

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

45
    public function __construct(private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory, private readonly PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, private readonly PropertyMetadataFactoryInterface $propertyMetadataFactory, private readonly ResourceClassResolverInterface $resourceClassResolver, private readonly UrlGeneratorInterface $urlGenerator, private readonly ?NameConverterInterface $nameConverter = null)
46
    {
UNCOV
47
    }
2,255✔
48

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

UNCOV
57
        foreach ($object->getResourceNameCollection() as $resourceClass) {
12✔
UNCOV
58
            $resourceMetadataCollection = $this->resourceMetadataFactory->create($resourceClass);
12✔
59

UNCOV
60
            $resourceMetadata = $resourceMetadataCollection[0];
12✔
UNCOV
61
            if ($resourceMetadata instanceof ErrorResource && ValidationException::class === $resourceMetadata->getClass()) {
12✔
UNCOV
62
                continue;
12✔
63
            }
64

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

UNCOV
71
        return $this->computeDoc($object, $this->getClasses($entrypointProperties, $classes));
12✔
72
    }
73

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

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

UNCOV
107
        if ($resourceMetadata->getDeprecationReason()) {
12✔
UNCOV
108
            $entrypointProperty['owl:deprecated'] = true;
12✔
109
        }
110

UNCOV
111
        $entrypointProperties[] = $entrypointProperty;
12✔
112
    }
113

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

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

UNCOV
131
        if (null !== $description) {
12✔
UNCOV
132
            $class['hydra:description'] = $description;
12✔
133
        }
134

UNCOV
135
        if ($isDeprecated) {
12✔
UNCOV
136
            $class['owl:deprecated'] = true;
12✔
137
        }
138

UNCOV
139
        return $class;
12✔
140
    }
141

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

UNCOV
155
        if ($normalizationGroups) {
12✔
UNCOV
156
            $propertyNameContext['serializer_groups'] = $normalizationGroups;
12✔
157
        }
158

UNCOV
159
        if (!$denormalizationGroups) {
12✔
UNCOV
160
            return [$propertyNameContext, $propertyContext];
12✔
161
        }
162

UNCOV
163
        if (!isset($propertyNameContext['serializer_groups'])) {
12✔
164
            $propertyNameContext['serializer_groups'] = $denormalizationGroups;
×
165

166
            return [$propertyNameContext, $propertyContext];
×
167
        }
168

UNCOV
169
        foreach ($denormalizationGroups as $group) {
12✔
UNCOV
170
            $propertyNameContext['serializer_groups'][] = $group;
12✔
171
        }
172

UNCOV
173
        return [$propertyNameContext, $propertyContext];
12✔
174
    }
175

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

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

UNCOV
190
            $inputMetadata = $operation->getInput();
12✔
UNCOV
191
            if (null !== $inputClass = $inputMetadata['class'] ?? null) {
12✔
UNCOV
192
                $classes[$inputClass] = true;
12✔
193
            }
194

UNCOV
195
            $outputMetadata = $operation->getOutput();
12✔
UNCOV
196
            if (null !== $outputClass = $outputMetadata['class'] ?? null) {
12✔
UNCOV
197
                $classes[$outputClass] = true;
12✔
198
            }
199
        }
200

201
        /** @var string[] $classes */
UNCOV
202
        $classes = array_keys($classes);
12✔
UNCOV
203
        $properties = [];
12✔
UNCOV
204
        [$propertyNameContext, $propertyContext] = $this->getPropertyMetadataFactoryContext($resourceMetadata);
12✔
205

UNCOV
206
        foreach ($classes as $class) {
12✔
UNCOV
207
            foreach ($this->propertyNameCollectionFactory->create($class, $propertyNameContext) as $propertyName) {
12✔
UNCOV
208
                $propertyMetadata = $this->propertyMetadataFactory->create($class, $propertyName, $propertyContext);
12✔
209

UNCOV
210
                if (true === $propertyMetadata->isIdentifier() && false === $propertyMetadata->isWritable()) {
12✔
UNCOV
211
                    continue;
12✔
212
                }
213

UNCOV
214
                if ($this->nameConverter) {
12✔
UNCOV
215
                    $propertyName = $this->nameConverter->normalize($propertyName, $class, self::FORMAT, $context);
12✔
216
                }
217

UNCOV
218
                $properties[] = $this->getProperty($propertyMetadata, $propertyName, $prefixedShortName, $shortName);
12✔
219
            }
220
        }
221

UNCOV
222
        return $properties;
12✔
223
    }
224

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

UNCOV
237
                $hydraOperations[] = $this->getHydraOperation($operation, $operation->getTypes()[0] ?? "#{$operation->getShortName()}");
12✔
238
            }
239
        }
240

UNCOV
241
        return $hydraOperations;
12✔
242
    }
243

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

UNCOV
251
        $hydraOperation = $operation->getHydraContext() ?? [];
12✔
UNCOV
252
        if ($operation->getDeprecationReason()) {
12✔
UNCOV
253
            $hydraOperation['owl:deprecated'] = true;
12✔
254
        }
255

UNCOV
256
        $shortName = $operation->getShortName();
12✔
UNCOV
257
        $inputMetadata = $operation->getInput() ?? [];
12✔
UNCOV
258
        $outputMetadata = $operation->getOutput() ?? [];
12✔
259

UNCOV
260
        $inputClass = \array_key_exists('class', $inputMetadata) ? $inputMetadata['class'] : false;
12✔
UNCOV
261
        $outputClass = \array_key_exists('class', $outputMetadata) ? $outputMetadata['class'] : false;
12✔
262

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

UNCOV
304
        $hydraOperation['hydra:method'] ?? $hydraOperation['hydra:method'] = $method;
12✔
305

UNCOV
306
        if (!isset($hydraOperation['rdfs:label']) && isset($hydraOperation['hydra:title'])) {
12✔
UNCOV
307
            $hydraOperation['rdfs:label'] = $hydraOperation['hydra:title'];
12✔
308
        }
309

UNCOV
310
        ksort($hydraOperation);
12✔
311

UNCOV
312
        return $hydraOperation;
12✔
313
    }
314

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

UNCOV
322
        if (isset($jsonldContext['@type'])) {
12✔
UNCOV
323
            return $jsonldContext['@type'];
12✔
324
        }
325

UNCOV
326
        $builtInTypes = $propertyMetadata->getBuiltinTypes() ?? [];
12✔
UNCOV
327
        $types = [];
12✔
328

UNCOV
329
        foreach ($builtInTypes as $type) {
12✔
UNCOV
330
            if ($type->isCollection() && null !== $collectionType = $type->getCollectionValueTypes()[0] ?? null) {
12✔
UNCOV
331
                $type = $collectionType;
12✔
332
            }
333

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

UNCOV
360
                    if (is_a($className, \DateTimeInterface::class, true)) {
12✔
UNCOV
361
                        if (!\in_array('xmls:dateTime', $types, true)) {
12✔
UNCOV
362
                            $types[] = 'xmls:dateTime';
12✔
363
                        }
UNCOV
364
                        break;
12✔
365
                    }
366

UNCOV
367
                    if ($this->resourceClassResolver->isResourceClass($className)) {
12✔
UNCOV
368
                        $resourceMetadata = $this->resourceMetadataFactory->create($className);
12✔
UNCOV
369
                        $operation = $resourceMetadata->getOperation();
12✔
370

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

UNCOV
378
                        $types = array_unique(array_merge($types, $operation->getTypes()));
12✔
UNCOV
379
                        break;
12✔
380
                    }
381
            }
382
        }
383

UNCOV
384
        if ([] === $types) {
12✔
UNCOV
385
            return null;
12✔
386
        }
387

UNCOV
388
        return 1 === \count($types) ? $types[0] : $types;
12✔
389
    }
390

391
    private function isSingleRelation(ApiProperty $propertyMetadata): bool
392
    {
UNCOV
393
        $builtInTypes = $propertyMetadata->getBuiltinTypes() ?? [];
12✔
394

UNCOV
395
        foreach ($builtInTypes as $type) {
12✔
UNCOV
396
            $className = $type->getClassName();
12✔
UNCOV
397
            if (!$type->isCollection()
12✔
UNCOV
398
                && null !== $className
12✔
UNCOV
399
                && $this->resourceClassResolver->isResourceClass($className)
12✔
400
            ) {
UNCOV
401
                return true;
12✔
402
            }
403
        }
404

UNCOV
405
        return false;
12✔
406
    }
407

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

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

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

UNCOV
487
        return $classes;
12✔
488
    }
489

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

UNCOV
499
        if (!isset($iri)) {
12✔
UNCOV
500
            $iri = "#$shortName/$propertyName";
12✔
501
        }
502

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

UNCOV
510
        if (!isset($propertyData['owl:deprecated']) && $propertyMetadata->getDeprecationReason()) {
12✔
UNCOV
511
            $propertyData['owl:deprecated'] = true;
12✔
512
        }
513

UNCOV
514
        if (!isset($propertyData['owl:maxCardinality']) && $this->isSingleRelation($propertyMetadata)) {
12✔
UNCOV
515
            $propertyData['owl:maxCardinality'] = 1;
12✔
516
        }
517

UNCOV
518
        if (!isset($propertyData['range']) && null !== $range = $this->getRange($propertyMetadata)) {
12✔
UNCOV
519
            $propertyData['range'] = $range;
12✔
520
        }
521

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

UNCOV
531
        if (null !== $description = $propertyMetadata->getDescription()) {
12✔
UNCOV
532
            $property['hydra:description'] = $description;
12✔
533
        }
534

UNCOV
535
        return $property;
12✔
536
    }
537

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

UNCOV
545
        if ('' !== $object->getTitle()) {
12✔
UNCOV
546
            $doc['hydra:title'] = $object->getTitle();
12✔
547
        }
548

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

UNCOV
553
        $doc['hydra:entrypoint'] = $this->urlGenerator->generate('api_entrypoint');
12✔
UNCOV
554
        $doc['hydra:supportedClass'] = $classes;
12✔
555

UNCOV
556
        return $doc;
12✔
557
    }
558

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

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

588
    public function getSupportedTypes($format): array
589
    {
UNCOV
590
        return self::FORMAT === $format ? [Documentation::class => true] : [];
2,072✔
591
    }
592
}
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