Completed
Push — master ( da77ac...99f595 )
by Kévin
35:30 queued 29:04
created

DocumentationNormalizer   C

Complexity

Total Complexity 65

Size/Duplication

Total Lines 599
Duplicated Lines 1.17 %

Coupling/Cohesion

Components 1
Dependencies 13

Importance

Changes 0
Metric Value
wmc 65
lcom 1
cbo 13
dl 7
loc 599
rs 5.5294
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 19 1
A normalize() 0 19 2
B addPaths() 0 13 5
A getPath() 0 9 2
B getPathOperation() 0 20 5
B updateGetOperation() 3 43 5
B updatePostOperation() 0 26 1
B updatePutOperation() 0 34 1
A updateDeleteOperation() 0 17 1
A getDefinition() 4 15 3
C getDefinitionSchema() 0 26 7
B getPropertySchema() 0 29 6
C getType() 0 45 11
B computeDoc() 0 37 4
B getFiltersParameters() 0 31 6
A supportsNormalization() 0 4 2
A getSerializerContext() 0 10 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like DocumentationNormalizer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DocumentationNormalizer, and based on these observations, apply Extract Interface, too.

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\FilterLocatorTrait;
18
use ApiPlatform\Core\Api\OperationMethodResolverInterface;
19
use ApiPlatform\Core\Api\OperationType;
20
use ApiPlatform\Core\Api\ResourceClassResolverInterface;
21
use ApiPlatform\Core\Api\UrlGeneratorInterface;
22
use ApiPlatform\Core\Documentation\Documentation;
23
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
24
use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
25
use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
26
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
27
use ApiPlatform\Core\Metadata\Resource\ResourceMetadata;
28
use ApiPlatform\Core\PathResolver\OperationPathResolverInterface;
29
use Psr\Container\ContainerInterface;
30
use Symfony\Component\PropertyInfo\Type;
31
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
32
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
33
34
/**
35
 * Creates a machine readable Swagger API documentation.
36
 *
37
 * @author Amrouche Hamza <[email protected]>
38
 * @author Teoh Han Hui <[email protected]>
39
 * @author Kévin Dunglas <[email protected]>
40
 */
41
final class DocumentationNormalizer implements NormalizerInterface
42
{
43
    use FilterLocatorTrait;
44
45
    const SWAGGER_VERSION = '2.0';
46
    const FORMAT = 'json';
47
48
    private $resourceMetadataFactory;
49
    private $propertyNameCollectionFactory;
50
    private $propertyMetadataFactory;
51
    private $resourceClassResolver;
52
    private $operationMethodResolver;
53
    private $operationPathResolver;
54
    private $urlGenerator;
55
    private $nameConverter;
56
    private $oauthEnabled;
57
    private $oauthType;
58
    private $oauthFlow;
59
    private $oauthTokenUrl;
60
    private $oauthAuthorizationUrl;
61
    private $oauthScopes;
62
63
    /**
64
     * @param ContainerInterface|FilterCollection|null $filterLocator The new filter locator or the deprecated filter collection
65
     */
66
    public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ResourceClassResolverInterface $resourceClassResolver, OperationMethodResolverInterface $operationMethodResolver, OperationPathResolverInterface $operationPathResolver, UrlGeneratorInterface $urlGenerator, $filterLocator = null, NameConverterInterface $nameConverter = null, $oauthEnabled = false, $oauthType = '', $oauthFlow = '', $oauthTokenUrl = '', $oauthAuthorizationUrl = '', $oauthScopes = [])
67
    {
68
        $this->setFilterLocator($filterLocator, true);
69
70
        $this->resourceMetadataFactory = $resourceMetadataFactory;
71
        $this->propertyNameCollectionFactory = $propertyNameCollectionFactory;
72
        $this->propertyMetadataFactory = $propertyMetadataFactory;
73
        $this->resourceClassResolver = $resourceClassResolver;
74
        $this->operationMethodResolver = $operationMethodResolver;
75
        $this->operationPathResolver = $operationPathResolver;
76
        $this->urlGenerator = $urlGenerator;
77
        $this->nameConverter = $nameConverter;
78
        $this->oauthEnabled = $oauthEnabled;
79
        $this->oauthType = $oauthType;
80
        $this->oauthFlow = $oauthFlow;
81
        $this->oauthTokenUrl = $oauthTokenUrl;
82
        $this->oauthAuthorizationUrl = $oauthAuthorizationUrl;
83
        $this->oauthScopes = $oauthScopes;
84
    }
85
86
    /**
87
     * {@inheritdoc}
88
     */
89
    public function normalize($object, $format = null, array $context = [])
90
    {
91
        $mimeTypes = $object->getMimeTypes();
92
        $definitions = new \ArrayObject();
93
        $paths = new \ArrayObject();
94
95
        foreach ($object->getResourceNameCollection() as $resourceClass) {
96
            $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
97
            $resourceShortName = $resourceMetadata->getShortName();
98
99
            $this->addPaths($paths, $definitions, $resourceClass, $resourceShortName, $resourceMetadata, $mimeTypes, OperationType::COLLECTION);
100
            $this->addPaths($paths, $definitions, $resourceClass, $resourceShortName, $resourceMetadata, $mimeTypes, OperationType::ITEM);
101
        }
102
103
        $definitions->ksort();
104
        $paths->ksort();
105
106
        return $this->computeDoc($object, $definitions, $paths);
107
    }
108
109
    /**
110
     * Updates the list of entries in the paths collection.
111
     *
112
     * @param \ArrayObject     $paths
113
     * @param \ArrayObject     $definitions
114
     * @param string           $resourceClass
115
     * @param string           $resourceShortName
116
     * @param ResourceMetadata $resourceMetadata
117
     * @param array            $mimeTypes
118
     * @param string           $operationType
119
     */
120
    private function addPaths(\ArrayObject $paths, \ArrayObject $definitions, string $resourceClass, string $resourceShortName, ResourceMetadata $resourceMetadata, array $mimeTypes, string $operationType)
121
    {
122
        if (null === $operations = $operationType === OperationType::COLLECTION ? $resourceMetadata->getCollectionOperations() : $resourceMetadata->getItemOperations()) {
123
            return;
124
        }
125
126
        foreach ($operations as $operationName => $operation) {
127
            $path = $this->getPath($resourceShortName, $operation, $operationType);
128
            $method = $operationType === OperationType::ITEM ? $this->operationMethodResolver->getItemOperationMethod($resourceClass, $operationName) : $this->operationMethodResolver->getCollectionOperationMethod($resourceClass, $operationName);
129
130
            $paths[$path][strtolower($method)] = $this->getPathOperation($operationName, $operation, $method, $operationType, $resourceClass, $resourceMetadata, $mimeTypes, $definitions);
131
        }
132
    }
133
134
    /**
135
     * Gets the path for an operation.
136
     *
137
     * If the path ends with the optional _format parameter, it is removed
138
     * as optional path parameters are not yet supported.
139
     *
140
     * @see https://github.com/OAI/OpenAPI-Specification/issues/93
141
     *
142
     * @param string $resourceShortName
143
     * @param array  $operation
144
     * @param string $operationType
145
     *
146
     * @return string
147
     */
148
    private function getPath(string $resourceShortName, array $operation, string $operationType): string
149
    {
150
        $path = $this->operationPathResolver->resolveOperationPath($resourceShortName, $operation, $operationType);
151
        if ('.{_format}' === substr($path, -10)) {
152
            $path = substr($path, 0, -10);
153
        }
154
155
        return $path;
156
    }
157
158
    /**
159
     * Gets a path Operation Object.
160
     *
161
     * @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#operation-object
162
     *
163
     * @param string           $operationName
164
     * @param array            $operation
165
     * @param string           $method
166
     * @param string           $operationType
167
     * @param string           $resourceClass
168
     * @param ResourceMetadata $resourceMetadata
169
     * @param string[]         $mimeTypes
170
     * @param \ArrayObject     $definitions
171
     *
172
     * @return \ArrayObject
173
     */
174
    private function getPathOperation(string $operationName, array $operation, string $method, string $operationType, string $resourceClass, ResourceMetadata $resourceMetadata, array $mimeTypes, \ArrayObject $definitions): \ArrayObject
175
    {
176
        $pathOperation = new \ArrayObject($operation['swagger_context'] ?? []);
177
        $resourceShortName = $resourceMetadata->getShortName();
178
        $pathOperation['tags'] ?? $pathOperation['tags'] = [$resourceShortName];
179
        $pathOperation['operationId'] ?? $pathOperation['operationId'] = lcfirst($operationName).ucfirst($resourceShortName).ucfirst($operationType);
180
181
        switch ($method) {
182
            case 'GET':
183
                return $this->updateGetOperation($pathOperation, $mimeTypes, $operationType, $resourceMetadata, $resourceClass, $resourceShortName, $operationName, $definitions);
184
            case 'POST':
185
                return $this->updatePostOperation($pathOperation, $mimeTypes, $operationType, $resourceMetadata, $resourceClass, $resourceShortName, $operationName, $definitions);
186
            case 'PUT':
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...
187
                return $this->updatePutOperation($pathOperation, $mimeTypes, $operationType, $resourceMetadata, $resourceClass, $resourceShortName, $operationName, $definitions);
188
            case 'DELETE':
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...
189
                return $this->updateDeleteOperation($pathOperation, $resourceShortName);
190
        }
191
192
        return $pathOperation;
193
    }
194
195
    /**
196
     * @param \ArrayObject     $pathOperation
197
     * @param array            $mimeTypes
198
     * @param string           $operationType
199
     * @param ResourceMetadata $resourceMetadata
200
     * @param string           $resourceClass
201
     * @param string           $resourceShortName
202
     * @param string           $operationName
203
     * @param \ArrayObject     $definitions
204
     *
205
     * @return \ArrayObject
206
     */
207
    private function updateGetOperation(\ArrayObject $pathOperation, array $mimeTypes, string $operationType, ResourceMetadata $resourceMetadata, string $resourceClass, string $resourceShortName, string $operationName, \ArrayObject $definitions)
208
    {
209
        $serializerContext = $this->getSerializerContext($operationType, false, $resourceMetadata, $operationName);
210
        $responseDefinitionKey = $this->getDefinition($definitions, $resourceMetadata, $resourceClass, $serializerContext);
211
212
        $pathOperation['produces'] ?? $pathOperation['produces'] = $mimeTypes;
213
214
        if ($operationType === OperationType::COLLECTION || $operationType === OperationType::SUBRESOURCE) {
215
            $pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Retrieves the collection of %s resources.', $resourceShortName);
216
            $pathOperation['responses'] ?? $pathOperation['responses'] = [
217
                '200' => [
218
                    'description' => sprintf('%s collection response', $resourceShortName),
219
                    'schema' => [
220
                        'type' => 'array',
221
                        'items' => ['$ref' => sprintf('#/definitions/%s', $responseDefinitionKey)],
222
                    ],
223
                ],
224
            ];
225
226 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...
227
                $pathOperation['parameters'] = $parameters;
228
            }
229
230
            return $pathOperation;
231
        }
232
233
        $pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Retrieves a %s resource.', $resourceShortName);
234
        $pathOperation['parameters'] ?? $pathOperation['parameters'] = [[
235
            'name' => 'id',
236
            'in' => 'path',
237
            'required' => true,
238
            'type' => 'integer',
239
        ]];
240
        $pathOperation['responses'] ?? $pathOperation['responses'] = [
241
            '200' => [
242
                'description' => sprintf('%s resource response', $resourceShortName),
243
                'schema' => ['$ref' => sprintf('#/definitions/%s', $responseDefinitionKey)],
244
            ],
245
            '404' => ['description' => 'Resource not found'],
246
        ];
247
248
        return $pathOperation;
249
    }
250
251
    /**
252
     * @param \ArrayObject     $pathOperation
253
     * @param array            $mimeTypes
254
     * @param string           $operationType
255
     * @param ResourceMetadata $resourceMetadata
256
     * @param string           $resourceClass
257
     * @param string           $resourceShortName
258
     * @param string           $operationName
259
     * @param \ArrayObject     $definitions
260
     *
261
     * @return \ArrayObject
262
     */
263
    private function updatePostOperation(\ArrayObject $pathOperation, array $mimeTypes, string $operationType, ResourceMetadata $resourceMetadata, string $resourceClass, string $resourceShortName, string $operationName, \ArrayObject $definitions)
264
    {
265
        $pathOperation['consumes'] ?? $pathOperation['consumes'] = $mimeTypes;
266
        $pathOperation['produces'] ?? $pathOperation['produces'] = $mimeTypes;
267
        $pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Creates a %s resource.', $resourceShortName);
268
        $pathOperation['parameters'] ?? $pathOperation['parameters'] = [[
269
            'name' => lcfirst($resourceShortName),
270
            'in' => 'body',
271
            'description' => sprintf('The new %s resource', $resourceShortName),
272
            'schema' => ['$ref' => sprintf('#/definitions/%s', $this->getDefinition($definitions, $resourceMetadata, $resourceClass,
273
                $this->getSerializerContext($operationType, true, $resourceMetadata, $operationName)
274
            ))],
275
        ]];
276
        $pathOperation['responses'] ?? $pathOperation['responses'] = [
277
            '201' => [
278
                'description' => sprintf('%s resource created', $resourceShortName),
279
                'schema' => ['$ref' => sprintf('#/definitions/%s', $this->getDefinition($definitions, $resourceMetadata, $resourceClass,
280
                    $this->getSerializerContext($operationType, false, $resourceMetadata, $operationName)
281
                ))],
282
            ],
283
            '400' => ['description' => 'Invalid input'],
284
            '404' => ['description' => 'Resource not found'],
285
        ];
286
287
        return $pathOperation;
288
    }
289
290
    /**
291
     * @param \ArrayObject     $pathOperation
292
     * @param array            $mimeTypes
293
     * @param string           $operationType
294
     * @param ResourceMetadata $resourceMetadata
295
     * @param string           $resourceClass
296
     * @param string           $resourceShortName
297
     * @param string           $operationName
298
     * @param \ArrayObject     $definitions
299
     *
300
     * @return \ArrayObject
301
     */
302
    private function updatePutOperation(\ArrayObject $pathOperation, array $mimeTypes, string $operationType, ResourceMetadata $resourceMetadata, string $resourceClass, string $resourceShortName, string $operationName, \ArrayObject $definitions)
303
    {
304
        $pathOperation['consumes'] ?? $pathOperation['consumes'] = $mimeTypes;
305
        $pathOperation['produces'] ?? $pathOperation['produces'] = $mimeTypes;
306
        $pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Replaces the %s resource.', $resourceShortName);
307
        $pathOperation['parameters'] ?? $pathOperation['parameters'] = [
308
            [
309
                'name' => 'id',
310
                'in' => 'path',
311
                'type' => 'integer',
312
                'required' => true,
313
            ],
314
            [
315
                'name' => lcfirst($resourceShortName),
316
                'in' => 'body',
317
                'description' => sprintf('The updated %s resource', $resourceShortName),
318
                'schema' => ['$ref' => sprintf('#/definitions/%s', $this->getDefinition($definitions, $resourceMetadata, $resourceClass,
319
                    $this->getSerializerContext($operationType, true, $resourceMetadata, $operationName)
320
                ))],
321
            ],
322
        ];
323
        $pathOperation['responses'] ?? $pathOperation['responses'] = [
324
            '200' => [
325
                'description' => sprintf('%s resource updated', $resourceShortName),
326
                'schema' => ['$ref' => sprintf('#/definitions/%s', $this->getDefinition($definitions, $resourceMetadata, $resourceClass,
327
                    $this->getSerializerContext($operationType, false, $resourceMetadata, $operationName)
328
                ))],
329
            ],
330
            '400' => ['description' => 'Invalid input'],
331
            '404' => ['description' => 'Resource not found'],
332
        ];
333
334
        return $pathOperation;
335
    }
336
337
    /**
338
     * @param \ArrayObject $pathOperation
339
     * @param string       $resourceShortName
340
     *
341
     * @return \ArrayObject
342
     */
343
    private function updateDeleteOperation(\ArrayObject $pathOperation, string $resourceShortName): \ArrayObject
344
    {
345
        $pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Removes the %s resource.', $resourceShortName);
346
        $pathOperation['responses'] ?? $pathOperation['responses'] = [
347
            '204' => ['description' => sprintf('%s resource deleted', $resourceShortName)],
348
            '404' => ['description' => 'Resource not found'],
349
        ];
350
351
        $pathOperation['parameters'] ?? $pathOperation['parameters'] = [[
352
            'name' => 'id',
353
            'in' => 'path',
354
            'type' => 'integer',
355
            'required' => true,
356
        ]];
357
358
        return $pathOperation;
359
    }
360
361
    /**
362
     * @param \ArrayObject     $definitions
363
     * @param ResourceMetadata $resourceMetadata
364
     * @param string           $resourceClass
365
     * @param array|null       $serializerContext
366
     *
367
     * @return string
368
     */
369
    private function getDefinition(\ArrayObject $definitions, ResourceMetadata $resourceMetadata, string $resourceClass, array $serializerContext = null): string
370
    {
371
        if (isset($serializerContext['groups'])) {
372
            $definitionKey = sprintf('%s_%s', $resourceMetadata->getShortName(), md5(serialize($serializerContext['groups'])));
373
        } else {
374
            $definitionKey = $resourceMetadata->getShortName();
375
        }
376
377 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...
378
            $definitions[$definitionKey] = [];  // Initialize first to prevent infinite loop
379
            $definitions[$definitionKey] = $this->getDefinitionSchema($resourceClass, $resourceMetadata, $definitions, $serializerContext);
380
        }
381
382
        return $definitionKey;
383
    }
384
385
    /**
386
     * Gets a definition Schema Object.
387
     *
388
     * @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#schemaObject
389
     *
390
     * @param string           $resourceClass
391
     * @param ResourceMetadata $resourceMetadata
392
     * @param \ArrayObject     $definitions
393
     * @param array|null       $serializerContext
394
     *
395
     * @return \ArrayObject
396
     */
397
    private function getDefinitionSchema(string $resourceClass, ResourceMetadata $resourceMetadata, \ArrayObject $definitions, array $serializerContext = null): \ArrayObject
398
    {
399
        $definitionSchema = new \ArrayObject(['type' => 'object']);
400
401
        if (null !== $description = $resourceMetadata->getDescription()) {
402
            $definitionSchema['description'] = $description;
403
        }
404
405
        if (null !== $iri = $resourceMetadata->getIri()) {
406
            $definitionSchema['externalDocs'] = ['url' => $iri];
407
        }
408
409
        $options = isset($serializerContext['groups']) ? ['serializer_groups' => $serializerContext['groups']] : [];
410
        foreach ($this->propertyNameCollectionFactory->create($resourceClass, $options) as $propertyName) {
411
            $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName);
412
            $normalizedPropertyName = $this->nameConverter ? $this->nameConverter->normalize($propertyName) : $propertyName;
413
414
            if ($propertyMetadata->isRequired()) {
415
                $definitionSchema['required'][] = $normalizedPropertyName;
416
            }
417
418
            $definitionSchema['properties'][$normalizedPropertyName] = $this->getPropertySchema($propertyMetadata, $definitions, $serializerContext);
419
        }
420
421
        return $definitionSchema;
422
    }
423
424
    /**
425
     * Gets a property Schema Object.
426
     *
427
     * @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#schemaObject
428
     *
429
     * @param PropertyMetadata $propertyMetadata
430
     * @param \ArrayObject     $definitions
431
     * @param array|null       $serializerContext
432
     *
433
     * @return \ArrayObject
434
     */
435
    private function getPropertySchema(PropertyMetadata $propertyMetadata, \ArrayObject $definitions, array $serializerContext = null): \ArrayObject
436
    {
437
        $propertySchema = new \ArrayObject();
438
439
        if (false === $propertyMetadata->isWritable()) {
440
            $propertySchema['readOnly'] = true;
441
        }
442
443
        if (null !== $description = $propertyMetadata->getDescription()) {
444
            $propertySchema['description'] = $description;
445
        }
446
447
        if (null === $type = $propertyMetadata->getType()) {
448
            return $propertySchema;
449
        }
450
451
        $isCollection = $type->isCollection();
452
        if (null === $valueType = $isCollection ? $type->getCollectionValueType() : $type) {
453
            $builtinType = 'string';
454
            $className = null;
455
        } else {
456
            $builtinType = $valueType->getBuiltinType();
457
            $className = $valueType->getClassName();
458
        }
459
460
        $valueSchema = $this->getType($builtinType, $isCollection, $className, $propertyMetadata->isReadableLink(), $definitions, $serializerContext);
461
462
        return new \ArrayObject((array) $propertySchema + $valueSchema);
463
    }
464
465
    /**
466
     * Gets the Swagger's type corresponding to the given PHP's type.
467
     *
468
     * @param string       $type
469
     * @param bool         $isCollection
470
     * @param string       $className
471
     * @param bool         $readableLink
472
     * @param \ArrayObject $definitions
473
     * @param array|null   $serializerContext
474
     *
475
     * @return array
476
     */
477
    private function getType(string $type, bool $isCollection, string $className = null, bool $readableLink = null, \ArrayObject $definitions, array $serializerContext = null): array
478
    {
479
        if ($isCollection) {
480
            return ['type' => 'array', 'items' => $this->getType($type, false, $className, $readableLink, $definitions, $serializerContext)];
481
        }
482
483
        if (Type::BUILTIN_TYPE_STRING === $type) {
484
            return ['type' => 'string'];
485
        }
486
487
        if (Type::BUILTIN_TYPE_INT === $type) {
488
            return ['type' => 'integer'];
489
        }
490
491
        if (Type::BUILTIN_TYPE_FLOAT === $type) {
492
            return ['type' => 'number'];
493
        }
494
495
        if (Type::BUILTIN_TYPE_BOOL === $type) {
496
            return ['type' => 'boolean'];
497
        }
498
499
        if (Type::BUILTIN_TYPE_OBJECT === $type) {
500
            if (null === $className) {
501
                return ['type' => 'string'];
502
            }
503
504
            if (is_subclass_of($className, \DateTimeInterface::class)) {
505
                return ['type' => 'string', 'format' => 'date-time'];
506
            }
507
508
            if (!$this->resourceClassResolver->isResourceClass($className)) {
509
                return ['type' => 'string'];
510
            }
511
512
            if (true === $readableLink) {
513
                return ['$ref' => sprintf('#/definitions/%s', $this->getDefinition($definitions,
514
                    $this->resourceMetadataFactory->create($className),
515
                    $className, $serializerContext)
516
                )];
517
            }
518
        }
519
520
        return ['type' => 'string'];
521
    }
522
523
    /**
524
     * Computes the Swagger documentation.
525
     *
526
     * @param Documentation $documentation
527
     * @param \ArrayObject  $definitions
528
     * @param \ArrayObject  $paths
529
     *
530
     * @return array
531
     */
532
    private function computeDoc(Documentation $documentation, \ArrayObject $definitions, \ArrayObject $paths): array
533
    {
534
        $doc = [
535
            'swagger' => self::SWAGGER_VERSION,
536
            'basePath' => $this->urlGenerator->generate('api_entrypoint'),
537
            'info' => [
538
                'title' => $documentation->getTitle(),
539
                'version' => $documentation->getVersion(),
540
            ],
541
            'paths' => $paths,
542
        ];
543
544
        if ($this->oauthEnabled) {
545
            $doc['securityDefinitions'] = [
546
                'oauth' => [
547
                    'type' => $this->oauthType,
548
                    'description' => 'OAuth client_credentials Grant',
549
                    'flow' => $this->oauthFlow,
550
                    'tokenUrl' => $this->oauthTokenUrl,
551
                    'authorizationUrl' => $this->oauthAuthorizationUrl,
552
                    'scopes' => $this->oauthScopes,
553
                ],
554
            ];
555
556
            $doc['security'] = [['oauth' => []]];
557
        }
558
559
        if ('' !== $description = $documentation->getDescription()) {
560
            $doc['info']['description'] = $description;
561
        }
562
563
        if (count($definitions) > 0) {
564
            $doc['definitions'] = $definitions;
565
        }
566
567
        return $doc;
568
    }
569
570
    /**
571
     * Gets Swagger parameters corresponding to enabled filters.
572
     *
573
     * @param string           $resourceClass
574
     * @param string           $operationName
575
     * @param ResourceMetadata $resourceMetadata
576
     * @param \ArrayObject     $definitions
577
     * @param array|null       $serializerContext
578
     *
579
     * @return array
580
     */
581
    private function getFiltersParameters(string $resourceClass, string $operationName, ResourceMetadata $resourceMetadata, \ArrayObject $definitions, array $serializerContext = null): array
582
    {
583
        if (null === $this->filterLocator) {
584
            return [];
585
        }
586
587
        $parameters = [];
588
        $resourceFilters = $resourceMetadata->getCollectionOperationAttribute($operationName, 'filters', [], true);
589
        foreach ($resourceFilters as $filterId) {
590
            if (!$filter = $this->getFilter($filterId)) {
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $filter is correct as $this->getFilter($filterId) (which targets ApiPlatform\Core\Api\Fil...catorTrait::getFilter()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
591
                continue;
592
            }
593
594
            foreach ($filter->getDescription($resourceClass) as $name => $data) {
595
                $parameter = [
596
                    'name' => $name,
597
                    'in' => 'query',
598
                    'required' => $data['required'],
599
                ];
600
                $parameter += $this->getType($data['type'], false, null, null, $definitions, $serializerContext);
601
602
                if (isset($data['swagger'])) {
603
                    $parameter = $data['swagger'] + $parameter;
604
                }
605
606
                $parameters[] = $parameter;
607
            }
608
        }
609
610
        return $parameters;
611
    }
612
613
    /**
614
     * {@inheritdoc}
615
     */
616
    public function supportsNormalization($data, $format = null)
617
    {
618
        return self::FORMAT === $format && $data instanceof Documentation;
619
    }
620
621
    /**
622
     * @param string           $operationType
623
     * @param bool             $denormalization
624
     * @param ResourceMetadata $resourceMetadata
625
     * @param string           $operationType
626
     *
627
     * @return array|null
628
     */
629
    private function getSerializerContext(string $operationType, bool $denormalization, ResourceMetadata $resourceMetadata, string $operationName)
630
    {
631
        $contextKey = $denormalization ? 'denormalization_context' : 'normalization_context';
632
633
        if (OperationType::COLLECTION === $operationType) {
634
            return $resourceMetadata->getCollectionOperationAttribute($operationName, $contextKey, null, true);
635
        }
636
637
        return $resourceMetadata->getItemOperationAttribute($operationName, $contextKey, null, true);
638
    }
639
}
640