Completed
Pull Request — master (#1036)
by Hector
02:56
created

DocumentationNormalizer::getPathOperation()   C

Complexity

Conditions 7
Paths 6

Size

Total Lines 22
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 22
rs 6.9811
c 0
b 0
f 0
cc 7
eloc 18
nc 6
nop 8

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <[email protected]>
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\Core\Swagger\Serializer;
15
16
use ApiPlatform\Core\Api\FilterCollection;
17
use ApiPlatform\Core\Api\OperationMethodResolverInterface;
18
use ApiPlatform\Core\Api\ResourceClassResolverInterface;
19
use ApiPlatform\Core\Api\UrlGeneratorInterface;
20
use ApiPlatform\Core\Documentation\Documentation;
21
use ApiPlatform\Core\Exception\RuntimeException;
22
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
23
use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
24
use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
25
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
26
use ApiPlatform\Core\Metadata\Resource\ResourceMetadata;
27
use ApiPlatform\Core\PathResolver\OperationPathResolverInterface;
28
use Symfony\Component\PropertyInfo\Type;
29
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
30
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
31
32
/**
33
 * Creates a machine readable Swagger API documentation.
34
 *
35
 * @author Amrouche Hamza <[email protected]>
36
 * @author Teoh Han Hui <[email protected]>
37
 * @author Kévin Dunglas <[email protected]>
38
 */
39
final class DocumentationNormalizer implements NormalizerInterface
40
{
41
    const SWAGGER_VERSION = '2.0';
42
    const FORMAT = 'json';
43
44
    private $resourceMetadataFactory;
45
    private $propertyNameCollectionFactory;
46
    private $propertyMetadataFactory;
47
    private $resourceClassResolver;
48
    private $operationMethodResolver;
49
    private $operationPathResolver;
50
    private $urlGenerator;
51
    private $filterCollection;
52
    private $nameConverter;
53
    private $oauthEnabled;
54
    private $oauthType;
55
    private $oauthFlow;
56
    private $oauthTokenUrl;
57
    private $oauthAuthorizationUrl;
58
    private $oauthScopes;
59
60
    public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ResourceClassResolverInterface $resourceClassResolver, OperationMethodResolverInterface $operationMethodResolver, OperationPathResolverInterface $operationPathResolver, UrlGeneratorInterface $urlGenerator, FilterCollection $filterCollection = null, NameConverterInterface $nameConverter = null, $oauthEnabled = false, $oauthType = '', $oauthFlow = '', $oauthTokenUrl = '', $oauthAuthorizationUrl = '', $oauthScopes = [])
61
    {
62
        $this->resourceMetadataFactory = $resourceMetadataFactory;
63
        $this->propertyNameCollectionFactory = $propertyNameCollectionFactory;
64
        $this->propertyMetadataFactory = $propertyMetadataFactory;
65
        $this->resourceClassResolver = $resourceClassResolver;
66
        $this->operationMethodResolver = $operationMethodResolver;
67
        $this->operationPathResolver = $operationPathResolver;
68
        $this->urlGenerator = $urlGenerator;
69
        $this->filterCollection = $filterCollection;
70
        $this->nameConverter = $nameConverter;
71
        $this->oauthEnabled = $oauthEnabled;
72
        $this->oauthType = $oauthType;
73
        $this->oauthFlow = $oauthFlow;
74
        $this->oauthTokenUrl = $oauthTokenUrl;
75
        $this->oauthAuthorizationUrl = $oauthAuthorizationUrl;
76
        $this->oauthScopes = $oauthScopes;
77
    }
78
79
    /**
80
     * {@inheritdoc}
81
     */
82
    public function normalize($object, $format = null, array $context = [])
83
    {
84
        $mimeTypes = $object->getMimeTypes();
85
        $definitions = new \ArrayObject();
86
        $paths = new \ArrayObject();
87
88
        foreach ($object->getResourceNameCollection() as $resourceClass) {
89
            $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
90
            $resourceShortName = $resourceMetadata->getShortName();
91
92
            $this->addPaths($paths, $definitions, $resourceClass, $resourceShortName, $resourceMetadata, $mimeTypes, true);
93
            $this->addPaths($paths, $definitions, $resourceClass, $resourceShortName, $resourceMetadata, $mimeTypes, false);
94
        }
95
96
        $definitions->ksort();
97
        $paths->ksort();
98
99
        return $this->computeDoc($object, $definitions, $paths);
100
    }
101
102
    /**
103
     * Updates the list of entries in the paths collection.
104
     *
105
     * @param \ArrayObject     $paths
106
     * @param \ArrayObject     $definitions
107
     * @param string           $resourceClass
108
     * @param string           $resourceShortName
109
     * @param ResourceMetadata $resourceMetadata
110
     * @param array            $mimeTypes
111
     * @param bool             $collection
112
     */
113
    private function addPaths(\ArrayObject $paths, \ArrayObject $definitions, string $resourceClass, string $resourceShortName, ResourceMetadata $resourceMetadata, array $mimeTypes, bool $collection)
114
    {
115
        $operations = $collection ? $resourceMetadata->getCollectionOperations() : $resourceMetadata->getItemOperations();
116
117
        if (!$operations) {
118
            return;
119
        }
120
121
        foreach ($operations as $operationName => $operation) {
122
            $path = $this->getPath($resourceShortName, $operation, $collection);
123
            $method = $collection ? $this->operationMethodResolver->getCollectionOperationMethod($resourceClass, $operationName) : $this->operationMethodResolver->getItemOperationMethod($resourceClass, $operationName);
124
125
            $paths[$path][strtolower($method)] = $this->getPathOperation($operationName, $operation, $method, $collection, $resourceClass, $resourceMetadata, $mimeTypes, $definitions);
126
        }
127
    }
128
129
    /**
130
     * Gets the path for an operation.
131
     *
132
     * If the path ends with the optional _format parameter, it is removed
133
     * as optional path parameters are not yet supported.
134
     *
135
     * @see https://github.com/OAI/OpenAPI-Specification/issues/93
136
     *
137
     * @param string $resourceShortName
138
     * @param array  $operation
139
     * @param bool   $collection
140
     *
141
     * @return string
142
     */
143
    private function getPath(string $resourceShortName, array $operation, bool $collection): string
144
    {
145
        $path = $this->operationPathResolver->resolveOperationPath($resourceShortName, $operation, $collection);
146
        if ('.{_format}' === substr($path, -10)) {
147
            $path = substr($path, 0, -10);
148
        }
149
150
        return $path;
151
    }
152
153
    /**
154
     * Gets a path Operation Object.
155
     *
156
     * @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#operation-object
157
     *
158
     * @param string           $operationName
159
     * @param array            $operation
160
     * @param string           $method
161
     * @param bool             $collection
162
     * @param string           $resourceClass
163
     * @param ResourceMetadata $resourceMetadata
164
     * @param string[]         $mimeTypes
165
     * @param \ArrayObject     $definitions
166
     *
167
     * @return \ArrayObject
168
     */
169
    private function getPathOperation(string $operationName, array $operation, string $method, bool $collection, string $resourceClass, ResourceMetadata $resourceMetadata, array $mimeTypes, \ArrayObject $definitions): \ArrayObject
170
    {
171
        $pathOperation = new \ArrayObject($operation['swagger_context'] ?? []);
172
        $resourceShortName = $resourceMetadata->getShortName();
173
        $pathOperation['tags'] ?? $pathOperation['tags'] = [$resourceShortName];
174
        $pathOperation['operationId'] ?? $pathOperation['operationId'] = lcfirst($operationName).ucfirst($resourceShortName).ucfirst($collection ? 'collection' : 'item');
175
176
        switch ($method) {
177
            case 'GET':
178
                return $this->updateGetOperation($pathOperation, $mimeTypes, $collection, $resourceMetadata, $resourceClass, $resourceShortName, $operationName, $definitions);
179
            case 'POST':
180
                return $this->updatePostOperation($pathOperation, $mimeTypes, $collection, $resourceMetadata, $resourceClass, $resourceShortName, $operationName, $definitions);
181
            case 'PUT':
182
                return $this->updatePutOperation($pathOperation, $mimeTypes, $collection, $resourceMetadata, $resourceClass, $resourceShortName, $operationName, $definitions);
183
            case 'PATCH':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
184
                $pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Updates the %s resource.', $resourceShortName);
185
            case 'DELETE':
186
                return $this->updateDeleteOperation($pathOperation, $resourceShortName);
187
            default:
0 ignored issues
show
Coding Style introduced by
DEFAULT statements must be defined using a colon

As per the PSR-2 coding standard, default statements should not be wrapped in curly braces.

switch ($expr) {
    default: { //wrong
        doSomething();
        break;
    }
}

switch ($expr) {
    default: //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
188
                throw new RuntimeException(sprintf('Method "%s" is not supported', $method));
189
        }
190
    }
191
192
    /**
193
     * @param \ArrayObject     $pathOperation
194
     * @param array            $mimeTypes
195
     * @param bool             $collection
196
     * @param ResourceMetadata $resourceMetadata
197
     * @param string           $resourceClass
198
     * @param string           $resourceShortName
199
     * @param string           $operationName
200
     * @param \ArrayObject     $definitions
201
     *
202
     * @return \ArrayObject
203
     */
204
    private function updateGetOperation(\ArrayObject $pathOperation, array $mimeTypes, bool $collection, ResourceMetadata $resourceMetadata, string $resourceClass, string $resourceShortName, string $operationName, \ArrayObject $definitions)
205
    {
206
        $serializerContext = $this->getSerializerContext($collection, false, $resourceMetadata, $operationName);
207
        $responseDefinitionKey = $this->getDefinition($definitions, $resourceMetadata, $resourceClass, $serializerContext);
208
209
        $pathOperation['produces'] ?? $pathOperation['produces'] = $mimeTypes;
210
211
        if ($collection) {
212
            $pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Retrieves the collection of %s resources.', $resourceShortName);
213
            $pathOperation['responses'] ?? $pathOperation['responses'] = [
214
                '200' => [
215
                    'description' => sprintf('%s collection response', $resourceShortName),
216
                    'schema' => [
217
                        'type' => 'array',
218
                        'items' => ['$ref' => sprintf('#/definitions/%s', $responseDefinitionKey)],
219
                    ],
220
                ],
221
            ];
222
223 View Code Duplication
            if (!isset($pathOperation['parameters']) && $parameters = $this->getFiltersParameters($resourceClass, $operationName, $resourceMetadata, $definitions, $serializerContext)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
224
                $pathOperation['parameters'] = $parameters;
225
            }
226
227
            return $pathOperation;
228
        }
229
230
        $pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Retrieves a %s resource.', $resourceShortName);
231
        $pathOperation['parameters'] ?? $pathOperation['parameters'] = [[
232
            'name' => 'id',
233
            'in' => 'path',
234
            'required' => true,
235
            'type' => 'integer',
236
        ]];
237
        $pathOperation['responses'] ?? $pathOperation['responses'] = [
238
            '200' => [
239
                'description' => sprintf('%s resource response', $resourceShortName),
240
                'schema' => ['$ref' => sprintf('#/definitions/%s', $responseDefinitionKey)],
241
            ],
242
            '404' => ['description' => 'Resource not found'],
243
        ];
244
245
        return $pathOperation;
246
    }
247
248
    /**
249
     * @param \ArrayObject     $pathOperation
250
     * @param array            $mimeTypes
251
     * @param bool             $collection
252
     * @param ResourceMetadata $resourceMetadata
253
     * @param string           $resourceClass
254
     * @param string           $resourceShortName
255
     * @param string           $operationName
256
     * @param \ArrayObject     $definitions
257
     *
258
     * @return \ArrayObject
259
     */
260
    private function updatePostOperation(\ArrayObject $pathOperation, array $mimeTypes, bool $collection, ResourceMetadata $resourceMetadata, string $resourceClass, string $resourceShortName, string $operationName, \ArrayObject $definitions)
261
    {
262
        $pathOperation['consumes'] ?? $pathOperation['consumes'] = $mimeTypes;
263
        $pathOperation['produces'] ?? $pathOperation['produces'] = $mimeTypes;
264
        $pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Creates a %s resource.', $resourceShortName);
265
        $pathOperation['parameters'] ?? $pathOperation['parameters'] = [[
266
            'name' => lcfirst($resourceShortName),
267
            'in' => 'body',
268
            'description' => sprintf('The new %s resource', $resourceShortName),
269
            'schema' => ['$ref' => sprintf('#/definitions/%s', $this->getDefinition($definitions, $resourceMetadata, $resourceClass,
270
                $this->getSerializerContext($collection, true, $resourceMetadata, $operationName)
271
            ))],
272
        ]];
273
        $pathOperation['responses'] ?? $pathOperation['responses'] = [
274
            '201' => [
275
                'description' => sprintf('%s resource created', $resourceShortName),
276
                'schema' => ['$ref' => sprintf('#/definitions/%s', $this->getDefinition($definitions, $resourceMetadata, $resourceClass,
277
                    $this->getSerializerContext($collection, false, $resourceMetadata, $operationName)
278
                ))],
279
            ],
280
            '400' => ['description' => 'Invalid input'],
281
            '404' => ['description' => 'Resource not found'],
282
        ];
283
284
        return $pathOperation;
285
    }
286
287
    /**
288
     * @param \ArrayObject     $pathOperation
289
     * @param array            $mimeTypes
290
     * @param bool             $collection
291
     * @param ResourceMetadata $resourceMetadata
292
     * @param string           $resourceClass
293
     * @param string           $resourceShortName
294
     * @param string           $operationName
295
     * @param \ArrayObject     $definitions
296
     *
297
     * @return \ArrayObject
298
     */
299
    private function updatePutOperation(\ArrayObject $pathOperation, array $mimeTypes, bool $collection, ResourceMetadata $resourceMetadata, string $resourceClass, string $resourceShortName, string $operationName, \ArrayObject $definitions)
300
    {
301
        $pathOperation['consumes'] ?? $pathOperation['consumes'] = $mimeTypes;
302
        $pathOperation['produces'] ?? $pathOperation['produces'] = $mimeTypes;
303
        $pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Replaces the %s resource.', $resourceShortName);
304
        $pathOperation['parameters'] ?? $pathOperation['parameters'] = [
305
            [
306
                'name' => 'id',
307
                'in' => 'path',
308
                'type' => 'integer',
309
                'required' => true,
310
            ],
311
            [
312
                'name' => lcfirst($resourceShortName),
313
                'in' => 'body',
314
                'description' => sprintf('The updated %s resource', $resourceShortName),
315
                'schema' => ['$ref' => sprintf('#/definitions/%s', $this->getDefinition($definitions, $resourceMetadata, $resourceClass,
316
                    $this->getSerializerContext($collection, true, $resourceMetadata, $operationName)
317
                ))],
318
            ],
319
        ];
320
        $pathOperation['responses'] ?? $pathOperation['responses'] = [
321
            '200' => [
322
                'description' => sprintf('%s resource updated', $resourceShortName),
323
                'schema' => ['$ref' => sprintf('#/definitions/%s', $this->getDefinition($definitions, $resourceMetadata, $resourceClass,
324
                    $this->getSerializerContext($collection, false, $resourceMetadata, $operationName)
325
                ))],
326
            ],
327
            '400' => ['description' => 'Invalid input'],
328
            '404' => ['description' => 'Resource not found'],
329
        ];
330
331
        return $pathOperation;
332
    }
333
334
    /**
335
     * @param \ArrayObject $pathOperation
336
     * @param string       $resourceShortName
337
     *
338
     * @return \ArrayObject
339
     */
340
    private function updateDeleteOperation(\ArrayObject $pathOperation, string $resourceShortName): \ArrayObject
341
    {
342
        $pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Removes the %s resource.', $resourceShortName);
343
        $pathOperation['responses'] ?? $pathOperation['responses'] = [
344
            '204' => ['description' => sprintf('%s resource deleted', $resourceShortName)],
345
            '404' => ['description' => 'Resource not found'],
346
        ];
347
348
        $pathOperation['parameters'] ?? $pathOperation['parameters'] = [[
349
            'name' => 'id',
350
            'in' => 'path',
351
            'type' => 'integer',
352
            'required' => true,
353
        ]];
354
355
        return $pathOperation;
356
    }
357
358
    /**
359
     * @param \ArrayObject     $definitions
360
     * @param ResourceMetadata $resourceMetadata
361
     * @param string           $resourceClass
362
     * @param array|null       $serializerContext
363
     *
364
     * @return string
365
     */
366
    private function getDefinition(\ArrayObject $definitions, ResourceMetadata $resourceMetadata, string $resourceClass, array $serializerContext = null): string
367
    {
368
        if (isset($serializerContext['groups'])) {
369
            $definitionKey = sprintf('%s_%s', $resourceMetadata->getShortName(), md5(serialize($serializerContext['groups'])));
370
        } else {
371
            $definitionKey = $resourceMetadata->getShortName();
372
        }
373
374 View Code Duplication
        if (!isset($definitions[$definitionKey])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
375
            $definitions[$definitionKey] = [];  // Initialize first to prevent infinite loop
376
            $definitions[$definitionKey] = $this->getDefinitionSchema($resourceClass, $resourceMetadata, $definitions, $serializerContext);
377
        }
378
379
        return $definitionKey;
380
    }
381
382
    /**
383
     * Gets a definition Schema Object.
384
     *
385
     * @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#schemaObject
386
     *
387
     * @param string           $resourceClass
388
     * @param ResourceMetadata $resourceMetadata
389
     * @param \ArrayObject     $definitions
390
     * @param array|null       $serializerContext
391
     *
392
     * @return \ArrayObject
393
     */
394
    private function getDefinitionSchema(string $resourceClass, ResourceMetadata $resourceMetadata, \ArrayObject $definitions, array $serializerContext = null): \ArrayObject
395
    {
396
        $definitionSchema = new \ArrayObject(['type' => 'object']);
397
398
        if (null !== $description = $resourceMetadata->getDescription()) {
399
            $definitionSchema['description'] = $description;
400
        }
401
402
        if (null !== $iri = $resourceMetadata->getIri()) {
403
            $definitionSchema['externalDocs'] = ['url' => $iri];
404
        }
405
406
        $options = isset($serializerContext['groups']) ? ['serializer_groups' => $serializerContext['groups']] : [];
407
        foreach ($this->propertyNameCollectionFactory->create($resourceClass, $options) as $propertyName) {
408
            $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName);
409
            $normalizedPropertyName = $this->nameConverter ? $this->nameConverter->normalize($propertyName) : $propertyName;
410
411
            if ($propertyMetadata->isRequired()) {
412
                $definitionSchema['required'][] = $normalizedPropertyName;
413
            }
414
415
            $definitionSchema['properties'][$normalizedPropertyName] = $this->getPropertySchema($propertyMetadata, $definitions, $serializerContext);
416
        }
417
418
        return $definitionSchema;
419
    }
420
421
    /**
422
     * Gets a property Schema Object.
423
     *
424
     * @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#schemaObject
425
     *
426
     * @param PropertyMetadata $propertyMetadata
427
     * @param \ArrayObject     $definitions
428
     * @param array|null       $serializerContext
429
     *
430
     * @return \ArrayObject
431
     */
432
    private function getPropertySchema(PropertyMetadata $propertyMetadata, \ArrayObject $definitions, array $serializerContext = null): \ArrayObject
433
    {
434
        $propertySchema = new \ArrayObject();
435
436
        if (false === $propertyMetadata->isWritable()) {
437
            $propertySchema['readOnly'] = true;
438
        }
439
440
        if (null !== $description = $propertyMetadata->getDescription()) {
441
            $propertySchema['description'] = $description;
442
        }
443
444
        if (null === $type = $propertyMetadata->getType()) {
445
            return $propertySchema;
446
        }
447
448
        $isCollection = $type->isCollection();
449
        if (null === $valueType = $isCollection ? $type->getCollectionValueType() : $type) {
450
            $builtinType = 'string';
451
            $className = null;
452
        } else {
453
            $builtinType = $valueType->getBuiltinType();
454
            $className = $valueType->getClassName();
455
        }
456
457
        $valueSchema = $this->getType($builtinType, $isCollection, $className, $propertyMetadata->isReadableLink(), $definitions, $serializerContext);
458
459
        return new \ArrayObject((array) $propertySchema + $valueSchema);
460
    }
461
462
    /**
463
     * Gets the Swagger's type corresponding to the given PHP's type.
464
     *
465
     * @param string       $type
466
     * @param bool         $isCollection
467
     * @param string       $className
468
     * @param bool         $readableLink
469
     * @param \ArrayObject $definitions
470
     * @param array|null   $serializerContext
471
     *
472
     * @return array
473
     */
474
    private function getType(string $type, bool $isCollection, string $className = null, bool $readableLink = null, \ArrayObject $definitions, array $serializerContext = null): array
475
    {
476
        if ($isCollection) {
477
            return ['type' => 'array', 'items' => $this->getType($type, false, $className, $readableLink, $definitions, $serializerContext)];
478
        }
479
480
        if (Type::BUILTIN_TYPE_STRING === $type) {
481
            return ['type' => 'string'];
482
        }
483
484
        if (Type::BUILTIN_TYPE_INT === $type) {
485
            return ['type' => 'integer'];
486
        }
487
488
        if (Type::BUILTIN_TYPE_FLOAT === $type) {
489
            return ['type' => 'number'];
490
        }
491
492
        if (Type::BUILTIN_TYPE_BOOL === $type) {
493
            return ['type' => 'boolean'];
494
        }
495
496
        if (Type::BUILTIN_TYPE_OBJECT === $type) {
497
            if (null === $className) {
498
                return ['type' => 'string'];
499
            }
500
501
            if (is_subclass_of($className, \DateTimeInterface::class)) {
502
                return ['type' => 'string', 'format' => 'date-time'];
503
            }
504
505
            if (!$this->resourceClassResolver->isResourceClass($className)) {
506
                return ['type' => 'string'];
507
            }
508
509
            if (true === $readableLink) {
510
                return ['$ref' => sprintf('#/definitions/%s', $this->getDefinition($definitions,
511
                    $this->resourceMetadataFactory->create($className),
512
                    $className, $serializerContext)
513
                )];
514
            }
515
        }
516
517
        return ['type' => 'string'];
518
    }
519
520
    /**
521
     * Computes the Swagger documentation.
522
     *
523
     * @param Documentation $documentation
524
     * @param \ArrayObject  $definitions
525
     * @param \ArrayObject  $paths
526
     *
527
     * @return array
528
     */
529
    private function computeDoc(Documentation $documentation, \ArrayObject $definitions, \ArrayObject $paths): array
530
    {
531
        $doc = [
532
            'swagger' => self::SWAGGER_VERSION,
533
            'basePath' => $this->urlGenerator->generate('api_entrypoint'),
534
            'info' => [
535
                'title' => $documentation->getTitle(),
536
                'version' => $documentation->getVersion(),
537
            ],
538
            'paths' => $paths,
539
        ];
540
541
        if ($this->oauthEnabled) {
542
            $doc['securityDefinitions'] = [
543
                'oauth' => [
544
                    'type' => $this->oauthType,
545
                    'description' => 'OAuth client_credentials Grant',
546
                    'flow' => $this->oauthFlow,
547
                    'tokenUrl' => $this->oauthTokenUrl,
548
                    'authorizationUrl' => $this->oauthAuthorizationUrl,
549
                    'scopes' => $this->oauthScopes,
550
                ],
551
            ];
552
553
            $doc['security'] = [['oauth' => []]];
554
        }
555
556
        if ('' !== $description = $documentation->getDescription()) {
557
            $doc['info']['description'] = $description;
558
        }
559
560
        if (count($definitions) > 0) {
561
            $doc['definitions'] = $definitions;
562
        }
563
564
        return $doc;
565
    }
566
567
    /**
568
     * Gets Swagger parameters corresponding to enabled filters.
569
     *
570
     * @param string           $resourceClass
571
     * @param string           $operationName
572
     * @param ResourceMetadata $resourceMetadata
573
     * @param \ArrayObject     $definitions
574
     * @param array|null       $serializerContext
575
     *
576
     * @return array
577
     */
578
    private function getFiltersParameters(string $resourceClass, string $operationName, ResourceMetadata $resourceMetadata, \ArrayObject $definitions, array $serializerContext = null): array
579
    {
580
        if (null === $this->filterCollection) {
581
            return [];
582
        }
583
584
        $parameters = [];
585
        $resourceFilters = $resourceMetadata->getCollectionOperationAttribute($operationName, 'filters', [], true);
586
        foreach ($this->filterCollection as $filterName => $filter) {
587
            if (!in_array($filterName, $resourceFilters, true)) {
588
                continue;
589
            }
590
591
            foreach ($filter->getDescription($resourceClass) as $name => $data) {
592
                $parameter = [
593
                    'name' => $name,
594
                    'in' => 'query',
595
                    'required' => $data['required'],
596
                ];
597
                $parameter += $this->getType($data['type'], false, null, null, $definitions, $serializerContext);
598
599
                if (isset($data['swagger'])) {
600
                    $parameter = $data['swagger'] + $parameter;
601
                }
602
603
                $parameters[] = $parameter;
604
            }
605
        }
606
607
        return $parameters;
608
    }
609
610
    /**
611
     * {@inheritdoc}
612
     */
613
    public function supportsNormalization($data, $format = null)
614
    {
615
        return self::FORMAT === $format && $data instanceof Documentation;
616
    }
617
618
    /**
619
     * @param bool             $collection
620
     * @param bool             $denormalization
621
     * @param ResourceMetadata $resourceMetadata
622
     * @param string           $operationName
623
     *
624
     * @return array|null
625
     */
626
    private function getSerializerContext(bool $collection, bool $denormalization, ResourceMetadata $resourceMetadata, string $operationName)
627
    {
628
        $contextKey = $denormalization ? 'denormalization_context' : 'normalization_context';
629
630
        if ($collection) {
631
            return $resourceMetadata->getCollectionOperationAttribute($operationName, $contextKey, null, true);
632
        }
633
634
        return $resourceMetadata->getItemOperationAttribute($operationName, $contextKey, null, true);
635
    }
636
}
637