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

api-platform / core / 3713134090

pending completion
3713134090

Pull #5254

github

GitHub
Merge b2ec54b3c into ac711530f
Pull Request #5254: [OpenApi] Add ApiResource::openapi and deprecate openapiContext

197 of 197 new or added lines in 5 files covered. (100.0%)

10372 of 12438 relevant lines covered (83.39%)

11.97 hits per line

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

89.0
/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;
17
use ApiPlatform\Api\UrlGeneratorInterface;
18
use ApiPlatform\Documentation\Documentation;
19
use ApiPlatform\JsonLd\ContextBuilderInterface;
20
use ApiPlatform\Metadata\ApiProperty;
21
use ApiPlatform\Metadata\ApiResource;
22
use ApiPlatform\Metadata\CollectionOperationInterface;
23
use ApiPlatform\Metadata\HttpOperation;
24
use ApiPlatform\Metadata\Operation;
25
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
26
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
27
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
28
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
29
use Symfony\Component\PropertyInfo\Type;
30
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
31
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
32
use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface;
33
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
34

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

44
    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)
45
    {
46
    }
28✔
47

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

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

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

66
        return $this->computeDoc($object, $this->getClasses($entrypointProperties, $classes));
2✔
67
    }
68

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

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

102
        if ($resourceMetadata->getDeprecationReason()) {
2✔
103
            $entrypointProperty['owl:deprecated'] = true;
×
104
        }
105

106
        $entrypointProperties[] = $entrypointProperty;
2✔
107
    }
108

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

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

126
        if (null !== $description) {
2✔
127
            $class['hydra:description'] = $description;
2✔
128
        }
129

130
        if ($isDeprecated) {
2✔
131
            $class['owl:deprecated'] = true;
×
132
        }
133

134
        return $class;
2✔
135
    }
136

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

150
        if ($normalizationGroups) {
2✔
151
            $propertyNameContext['serializer_groups'] = $normalizationGroups;
×
152
        }
153

154
        if (!$denormalizationGroups) {
2✔
155
            return [$propertyNameContext, $propertyContext];
2✔
156
        }
157

158
        if (!isset($propertyNameContext['serializer_groups'])) {
×
159
            $propertyNameContext['serializer_groups'] = $denormalizationGroups;
×
160

161
            return [$propertyNameContext, $propertyContext];
×
162
        }
163

164
        foreach ($denormalizationGroups as $group) {
×
165
            $propertyNameContext['serializer_groups'][] = $group;
×
166
        }
167

168
        return [$propertyNameContext, $propertyContext];
×
169
    }
170

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

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

185
            $inputMetadata = $operation->getInput();
2✔
186
            if (null !== $inputClass = $inputMetadata['class'] ?? null) {
2✔
187
                $classes[$inputClass] = true;
1✔
188
            }
189

190
            $outputMetadata = $operation->getOutput();
2✔
191
            if (null !== $outputClass = $outputMetadata['class'] ?? null) {
2✔
192
                $classes[$outputClass] = true;
1✔
193
            }
194
        }
195

196
        /** @var string[] $classes */
197
        $classes = array_keys($classes);
2✔
198
        $properties = [];
2✔
199
        [$propertyNameContext, $propertyContext] = $this->getPropertyMetadataFactoryContext($resourceMetadata);
2✔
200

201
        foreach ($classes as $class) {
2✔
202
            foreach ($this->propertyNameCollectionFactory->create($class, $propertyNameContext) as $propertyName) {
2✔
203
                $propertyMetadata = $this->propertyMetadataFactory->create($class, $propertyName, $propertyContext);
2✔
204

205
                if (true === $propertyMetadata->isIdentifier() && false === $propertyMetadata->isWritable()) {
2✔
206
                    continue;
×
207
                }
208

209
                if ($this->nameConverter) {
2✔
210
                    $propertyName = $this->nameConverter->normalize($propertyName, $class, self::FORMAT, $context);
1✔
211
                }
212

213
                $properties[] = $this->getProperty($propertyMetadata, $propertyName, $prefixedShortName, $shortName);
2✔
214
            }
215
        }
216

217
        return $properties;
2✔
218
    }
219

220
    /**
221
     * Gets Hydra operations.
222
     */
223
    private function getHydraOperations(bool $collection, ?ResourceMetadataCollection $resourceMetadataCollection = null): array
224
    {
225
        $hydraOperations = [];
2✔
226
        foreach ($resourceMetadataCollection as $resourceMetadata) {
2✔
227
            foreach ($resourceMetadata->getOperations() as $operation) {
2✔
228
                if ((HttpOperation::METHOD_POST === $operation->getMethod() || $operation instanceof CollectionOperationInterface) !== $collection) {
2✔
229
                    continue;
2✔
230
                }
231

232
                $hydraOperations[] = $this->getHydraOperation($operation, $operation->getTypes()[0] ?? "#{$operation->getShortName()}");
2✔
233
            }
234
        }
235

236
        return $hydraOperations;
2✔
237
    }
238

239
    /**
240
     * Gets and populates if applicable a Hydra operation.
241
     */
242
    private function getHydraOperation(HttpOperation $operation, string $prefixedShortName): array
243
    {
244
        $method = $operation->getMethod() ?: HttpOperation::METHOD_GET;
2✔
245

246
        $hydraOperation = $operation->getHydraContext() ?? [];
2✔
247
        if ($operation->getDeprecationReason()) {
2✔
248
            $hydraOperation['owl:deprecated'] = true;
×
249
        }
250

251
        $shortName = $operation->getShortName();
2✔
252
        $inputMetadata = $operation->getInput() ?? [];
2✔
253
        $outputMetadata = $operation->getOutput() ?? [];
2✔
254

255
        $inputClass = \array_key_exists('class', $inputMetadata) ? $inputMetadata['class'] : false;
2✔
256
        $outputClass = \array_key_exists('class', $outputMetadata) ? $outputMetadata['class'] : false;
2✔
257

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

299
        $hydraOperation['hydra:method'] ?? $hydraOperation['hydra:method'] = $method;
2✔
300

301
        if (!isset($hydraOperation['rdfs:label']) && isset($hydraOperation['hydra:title'])) {
2✔
302
            $hydraOperation['rdfs:label'] = $hydraOperation['hydra:title'];
2✔
303
        }
304

305
        ksort($hydraOperation);
2✔
306

307
        return $hydraOperation;
2✔
308
    }
309

310
    /**
311
     * Gets the range of the property.
312
     */
313
    private function getRange(ApiProperty $propertyMetadata): ?string
314
    {
315
        $jsonldContext = $propertyMetadata->getJsonldContext();
2✔
316

317
        if (isset($jsonldContext['@type'])) {
2✔
318
            return $jsonldContext['@type'];
1✔
319
        }
320

321
        // TODO: 3.0 support multiple types, default value of types will be [] instead of null
322
        $type = $propertyMetadata->getBuiltinTypes()[0] ?? null;
2✔
323
        if (null === $type) {
2✔
324
            return null;
1✔
325
        }
326

327
        if ($type->isCollection() && null !== $collectionType = $type->getCollectionValueTypes()[0] ?? null) {
2✔
328
            $type = $collectionType;
1✔
329
        }
330

331
        switch ($type->getBuiltinType()) {
2✔
332
            case Type::BUILTIN_TYPE_STRING:
333
                return 'xmls:string';
2✔
334
            case Type::BUILTIN_TYPE_INT:
335
                return 'xmls:integer';
×
336
            case Type::BUILTIN_TYPE_FLOAT:
337
                return 'xmls:decimal';
×
338
            case Type::BUILTIN_TYPE_BOOL:
339
                return 'xmls:boolean';
×
340
            case Type::BUILTIN_TYPE_OBJECT:
341
                if (null === $className = $type->getClassName()) {
1✔
342
                    return null;
×
343
                }
344

345
                if (is_a($className, \DateTimeInterface::class, true)) {
1✔
346
                    return 'xmls:dateTime';
×
347
                }
348

349
                if ($this->resourceClassResolver->isResourceClass($className)) {
1✔
350
                    $resourceMetadata = $this->resourceMetadataFactory->create($className);
1✔
351
                    $operation = $resourceMetadata->getOperation();
1✔
352

353
                    if (!$operation instanceof HttpOperation) {
1✔
354
                        return "#{$operation->getShortName()}";
×
355
                    }
356

357
                    return $operation->getTypes()[0] ?? "#{$operation->getShortName()}";
1✔
358
                }
359
        }
360

361
        return null;
×
362
    }
363

364
    /**
365
     * Builds the classes array.
366
     */
367
    private function getClasses(array $entrypointProperties, array $classes): array
368
    {
369
        $classes[] = [
2✔
370
            '@id' => '#Entrypoint',
2✔
371
            '@type' => 'hydra:Class',
2✔
372
            'hydra:title' => 'The API entrypoint',
2✔
373
            'hydra:supportedProperty' => $entrypointProperties,
2✔
374
            'hydra:supportedOperation' => [
2✔
375
                '@type' => 'hydra:Operation',
2✔
376
                'hydra:method' => 'GET',
2✔
377
                'rdfs:label' => 'The API entrypoint.',
2✔
378
                'returns' => '#EntryPoint',
2✔
379
            ],
2✔
380
        ];
2✔
381

382
        // Constraint violation
383
        $classes[] = [
2✔
384
            '@id' => '#ConstraintViolation',
2✔
385
            '@type' => 'hydra:Class',
2✔
386
            'hydra:title' => 'A constraint violation',
2✔
387
            'hydra:supportedProperty' => [
2✔
388
                [
2✔
389
                    '@type' => 'hydra:SupportedProperty',
2✔
390
                    'hydra:property' => [
2✔
391
                        '@id' => '#ConstraintViolation/propertyPath',
2✔
392
                        '@type' => 'rdf:Property',
2✔
393
                        'rdfs:label' => 'propertyPath',
2✔
394
                        'domain' => '#ConstraintViolation',
2✔
395
                        'range' => 'xmls:string',
2✔
396
                    ],
2✔
397
                    'hydra:title' => 'propertyPath',
2✔
398
                    'hydra:description' => 'The property path of the violation',
2✔
399
                    'hydra:readable' => true,
2✔
400
                    'hydra:writeable' => false,
2✔
401
                ],
2✔
402
                [
2✔
403
                    '@type' => 'hydra:SupportedProperty',
2✔
404
                    'hydra:property' => [
2✔
405
                        '@id' => '#ConstraintViolation/message',
2✔
406
                        '@type' => 'rdf:Property',
2✔
407
                        'rdfs:label' => 'message',
2✔
408
                        'domain' => '#ConstraintViolation',
2✔
409
                        'range' => 'xmls:string',
2✔
410
                    ],
2✔
411
                    'hydra:title' => 'message',
2✔
412
                    'hydra:description' => 'The message associated with the violation',
2✔
413
                    'hydra:readable' => true,
2✔
414
                    'hydra:writeable' => false,
2✔
415
                ],
2✔
416
            ],
2✔
417
        ];
2✔
418

419
        // Constraint violation list
420
        $classes[] = [
2✔
421
            '@id' => '#ConstraintViolationList',
2✔
422
            '@type' => 'hydra:Class',
2✔
423
            'subClassOf' => 'hydra:Error',
2✔
424
            'hydra:title' => 'A constraint violation list',
2✔
425
            'hydra:supportedProperty' => [
2✔
426
                [
2✔
427
                    '@type' => 'hydra:SupportedProperty',
2✔
428
                    'hydra:property' => [
2✔
429
                        '@id' => '#ConstraintViolationList/violations',
2✔
430
                        '@type' => 'rdf:Property',
2✔
431
                        'rdfs:label' => 'violations',
2✔
432
                        'domain' => '#ConstraintViolationList',
2✔
433
                        'range' => '#ConstraintViolation',
2✔
434
                    ],
2✔
435
                    'hydra:title' => 'violations',
2✔
436
                    'hydra:description' => 'The violations',
2✔
437
                    'hydra:readable' => true,
2✔
438
                    'hydra:writeable' => false,
2✔
439
                ],
2✔
440
            ],
2✔
441
        ];
2✔
442

443
        return $classes;
2✔
444
    }
445

446
    /**
447
     * Gets a property definition.
448
     */
449
    private function getProperty(ApiProperty $propertyMetadata, string $propertyName, string $prefixedShortName, string $shortName): array
450
    {
451
        if ($iri = $propertyMetadata->getIris()) {
2✔
452
            $iri = 1 === (is_countable($iri) ? \count($iri) : 0) ? $iri[0] : $iri;
1✔
453
        }
454

455
        if (!isset($iri)) {
2✔
456
            $iri = "#$shortName/$propertyName";
2✔
457
        }
458

459
        $propertyData = [
2✔
460
            '@id' => $iri,
2✔
461
            '@type' => false === $propertyMetadata->isReadableLink() ? 'hydra:Link' : 'rdf:Property',
2✔
462
            'rdfs:label' => $propertyName,
2✔
463
            'domain' => $prefixedShortName,
2✔
464
        ];
2✔
465

466
        // TODO: 3.0 support multiple types, default value of types will be [] instead of null
467
        $type = $propertyMetadata->getBuiltinTypes()[0] ?? null;
2✔
468

469
        if (null !== $type && !$type->isCollection() && (null !== $className = $type->getClassName()) && $this->resourceClassResolver->isResourceClass($className)) {
2✔
470
            $propertyData['owl:maxCardinality'] = 1;
×
471
        }
472

473
        $property = [
2✔
474
            '@type' => 'hydra:SupportedProperty',
2✔
475
            'hydra:property' => $propertyData,
2✔
476
            'hydra:title' => $propertyName,
2✔
477
            'hydra:required' => $propertyMetadata->isRequired(),
2✔
478
            'hydra:readable' => $propertyMetadata->isReadable(),
2✔
479
            'hydra:writeable' => $propertyMetadata->isWritable() || $propertyMetadata->isInitializable(),
2✔
480
        ];
2✔
481

482
        if (null !== $range = $this->getRange($propertyMetadata)) {
2✔
483
            $property['hydra:property']['range'] = $range;
2✔
484
        }
485

486
        if (null !== $description = $propertyMetadata->getDescription()) {
2✔
487
            $property['hydra:description'] = $description;
2✔
488
        }
489

490
        if ($deprecationReason = $propertyMetadata->getDeprecationReason()) {
2✔
491
            $property['owl:deprecated'] = true;
×
492
        }
493

494
        return $property;
2✔
495
    }
496

497
    /**
498
     * Computes the documentation.
499
     */
500
    private function computeDoc(Documentation $object, array $classes): array
501
    {
502
        $doc = ['@context' => $this->getContext(), '@id' => $this->urlGenerator->generate('api_doc', ['_format' => self::FORMAT]), '@type' => 'hydra:ApiDocumentation'];
2✔
503

504
        if ('' !== $object->getTitle()) {
2✔
505
            $doc['hydra:title'] = $object->getTitle();
2✔
506
        }
507

508
        if ('' !== $object->getDescription()) {
2✔
509
            $doc['hydra:description'] = $object->getDescription();
2✔
510
        }
511

512
        $doc['hydra:entrypoint'] = $this->urlGenerator->generate('api_entrypoint');
2✔
513
        $doc['hydra:supportedClass'] = $classes;
2✔
514

515
        return $doc;
2✔
516
    }
517

518
    /**
519
     * Builds the JSON-LD context for the API documentation.
520
     */
521
    private function getContext(): array
522
    {
523
        return [
2✔
524
            '@vocab' => $this->urlGenerator->generate('api_doc', ['_format' => self::FORMAT], UrlGeneratorInterface::ABS_URL).'#',
2✔
525
            'hydra' => ContextBuilderInterface::HYDRA_NS,
2✔
526
            'rdf' => ContextBuilderInterface::RDF_NS,
2✔
527
            'rdfs' => ContextBuilderInterface::RDFS_NS,
2✔
528
            'xmls' => ContextBuilderInterface::XML_NS,
2✔
529
            'owl' => ContextBuilderInterface::OWL_NS,
2✔
530
            'schema' => ContextBuilderInterface::SCHEMA_ORG_NS,
2✔
531
            'domain' => ['@id' => 'rdfs:domain', '@type' => '@id'],
2✔
532
            'range' => ['@id' => 'rdfs:range', '@type' => '@id'],
2✔
533
            'subClassOf' => ['@id' => 'rdfs:subClassOf', '@type' => '@id'],
2✔
534
            'expects' => ['@id' => 'hydra:expects', '@type' => '@id'],
2✔
535
            'returns' => ['@id' => 'hydra:returns', '@type' => '@id'],
2✔
536
        ];
2✔
537
    }
538

539
    /**
540
     * {@inheritdoc}
541
     */
542
    public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool
543
    {
544
        return self::FORMAT === $format && $data instanceof Documentation;
21✔
545
    }
546

547
    public function hasCacheableSupportsMethod(): bool
548
    {
549
        return true;
21✔
550
    }
551
}
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

© 2026 Coveralls, Inc