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

api-platform / core / 6693023961

30 Oct 2023 12:51PM UTC coverage: 67.319%. Remained the same
6693023961

push

github

web-flow
ci: php cs fixer (#5905)

--


fix cs


fix last cs

259 of 259 new or added lines in 83 files covered. (100.0%)

15610 of 23188 relevant lines covered (67.32%)

10.87 hits per line

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

88.3
/src/Core/Swagger/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\Core\Swagger\Serializer;
15

16
use ApiPlatform\Core\Api\FilterCollection;
17
use ApiPlatform\Core\Api\FilterLocatorTrait;
18
use ApiPlatform\Core\Api\FormatsProviderInterface;
19
use ApiPlatform\Core\Api\IdentifiersExtractorInterface;
20
use ApiPlatform\Core\Api\OperationAwareFormatsProviderInterface;
21
use ApiPlatform\Core\Api\OperationMethodResolverInterface;
22
use ApiPlatform\Core\Api\OperationType;
23
use ApiPlatform\Core\Api\ResourceClassResolverInterface;
24
use ApiPlatform\Core\Api\UrlGeneratorInterface;
25
use ApiPlatform\Core\JsonSchema\SchemaFactory as LegacySchemaFactory;
26
use ApiPlatform\Core\JsonSchema\SchemaFactoryInterface as LegacySchemaFactoryInterface;
27
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
28
use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
29
use ApiPlatform\Core\Metadata\Resource\ApiResourceToLegacyResourceMetadataTrait;
30
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
31
use ApiPlatform\Core\Metadata\Resource\ResourceMetadata;
32
use ApiPlatform\Core\Operation\Factory\SubresourceOperationFactoryInterface;
33
use ApiPlatform\Documentation\Documentation;
34
use ApiPlatform\Exception\ResourceClassNotFoundException;
35
use ApiPlatform\Exception\RuntimeException;
36
use ApiPlatform\JsonSchema\Schema;
37
use ApiPlatform\JsonSchema\SchemaFactory;
38
use ApiPlatform\JsonSchema\SchemaFactoryInterface;
39
use ApiPlatform\JsonSchema\TypeFactory;
40
use ApiPlatform\JsonSchema\TypeFactoryInterface;
41
use ApiPlatform\Metadata\HttpOperation;
42
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
43
use ApiPlatform\OpenApi\OpenApi;
44
use ApiPlatform\OpenApi\Serializer\ApiGatewayNormalizer;
45
use ApiPlatform\PathResolver\OperationPathResolverInterface;
46
use Psr\Container\ContainerInterface;
47
use Symfony\Component\PropertyInfo\Type;
48
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
49
use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface;
50
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
51

52
/**
53
 * Generates an OpenAPI specification (formerly known as Swagger). OpenAPI v2 and v3 are supported.
54
 *
55
 * @author Amrouche Hamza <hamza.simperfit@gmail.com>
56
 * @author Teoh Han Hui <teohhanhui@gmail.com>
57
 * @author Kévin Dunglas <dunglas@gmail.com>
58
 * @author Anthony GRASSIOT <antograssiot@free.fr>
59
 */
60
final class DocumentationNormalizer implements NormalizerInterface, CacheableSupportsMethodInterface
61
{
62
    use ApiResourceToLegacyResourceMetadataTrait;
63
    use FilterLocatorTrait;
64

65
    public const FORMAT = 'json';
66
    public const BASE_URL = 'base_url';
67
    public const SPEC_VERSION = 'spec_version';
68
    public const OPENAPI_VERSION = '3.0.2';
69
    public const SWAGGER_DEFINITION_NAME = 'swagger_definition_name';
70
    public const SWAGGER_VERSION = '2.0';
71

72
    /**
73
     * @deprecated
74
     */
75
    public const ATTRIBUTE_NAME = 'swagger_context';
76

77
    private $resourceMetadataFactory;
78
    private $propertyNameCollectionFactory;
79
    private $propertyMetadataFactory;
80
    private $operationMethodResolver;
81
    private $operationPathResolver;
82
    private $oauthEnabled;
83
    private $oauthType;
84
    private $oauthFlow;
85
    private $oauthTokenUrl;
86
    private $oauthAuthorizationUrl;
87
    private $oauthScopes;
88
    private $apiKeys;
89
    private $subresourceOperationFactory;
90
    private $paginationEnabled;
91
    private $paginationPageParameterName;
92
    private $clientItemsPerPage;
93
    private $itemsPerPageParameterName;
94
    private $paginationClientEnabled;
95
    private $paginationClientEnabledParameterName;
96
    private $formats;
97
    private $formatsProvider;
98

99
    /**
100
     * @var SchemaFactoryInterface|LegacySchemaFactoryInterface
101
     */
102
    private $jsonSchemaFactory;
103
    /**
104
     * @var TypeFactoryInterface
105
     */
106
    private $jsonSchemaTypeFactory;
107
    private $defaultContext = [
108
        self::BASE_URL => '/',
109
        ApiGatewayNormalizer::API_GATEWAY => false,
110
    ];
111

112
    private $identifiersExtractor;
113

114
    private $openApiNormalizer;
115
    private $legacyMode;
116

117
    /**
118
     * @param LegacySchemaFactoryInterface|SchemaFactoryInterface|ResourceClassResolverInterface|null $jsonSchemaFactory
119
     * @param ContainerInterface|FilterCollection|null                                                $filterLocator
120
     * @param array|OperationAwareFormatsProviderInterface                                            $formats
121
     * @param mixed|null                                                                              $jsonSchemaTypeFactory
122
     * @param int[]                                                                                   $swaggerVersions
123
     */
124
    public function __construct($resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, $jsonSchemaFactory = null, $jsonSchemaTypeFactory = null, OperationPathResolverInterface $operationPathResolver = null, UrlGeneratorInterface $urlGenerator = null, $filterLocator = null, NameConverterInterface $nameConverter = null, bool $oauthEnabled = false, string $oauthType = '', string $oauthFlow = '', string $oauthTokenUrl = '', string $oauthAuthorizationUrl = '', array $oauthScopes = [], array $apiKeys = [], SubresourceOperationFactoryInterface $subresourceOperationFactory = null, bool $paginationEnabled = true, string $paginationPageParameterName = 'page', bool $clientItemsPerPage = false, string $itemsPerPageParameterName = 'itemsPerPage', $formats = [], bool $paginationClientEnabled = false, string $paginationClientEnabledParameterName = 'pagination', array $defaultContext = [], array $swaggerVersions = [2, 3], IdentifiersExtractorInterface $identifiersExtractor = null, NormalizerInterface $openApiNormalizer = null, bool $legacyMode = false)
125
    {
126
        if ($jsonSchemaTypeFactory instanceof OperationMethodResolverInterface) {
80✔
127
            @trigger_error(sprintf('Passing an instance of %s to %s() is deprecated since version 2.5 and will be removed in 3.0.', OperationMethodResolverInterface::class, __METHOD__), \E_USER_DEPRECATED);
3✔
128

129
            $this->operationMethodResolver = $jsonSchemaTypeFactory;
3✔
130
            $this->jsonSchemaTypeFactory = new TypeFactory();
3✔
131
        } else {
132
            $this->jsonSchemaTypeFactory = $jsonSchemaTypeFactory ?? new TypeFactory();
77✔
133
        }
134

135
        if ($jsonSchemaFactory instanceof ResourceClassResolverInterface) {
80✔
136
            @trigger_error(sprintf('Passing an instance of %s to %s() is deprecated since version 2.5 and will be removed in 3.0.', ResourceClassResolverInterface::class, __METHOD__), \E_USER_DEPRECATED);
1✔
137
        }
138

139
        if (null === $jsonSchemaFactory || $jsonSchemaFactory instanceof ResourceClassResolverInterface) {
80✔
140
            if ($resourceMetadataFactory instanceof ResourceMetadataFactoryInterface) {
52✔
141
                $jsonSchemaFactory = new LegacySchemaFactory($this->jsonSchemaTypeFactory, $resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory, $nameConverter);
52✔
142
            } else {
143
                $jsonSchemaFactory = new SchemaFactory($this->jsonSchemaTypeFactory, $resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory, $nameConverter);
×
144
            }
145

146
            $this->jsonSchemaTypeFactory->setSchemaFactory($jsonSchemaFactory);
52✔
147
        }
148
        $this->jsonSchemaFactory = $jsonSchemaFactory;
80✔
149

150
        if ($nameConverter) {
80✔
151
            @trigger_error(sprintf('Passing an instance of %s to %s() is deprecated since version 2.5 and will be removed in 3.0.', NameConverterInterface::class, __METHOD__), \E_USER_DEPRECATED);
2✔
152
        }
153

154
        if ($urlGenerator) {
80✔
155
            @trigger_error(sprintf('Passing an instance of %s to %s() is deprecated since version 2.1 and will be removed in 3.0.', UrlGeneratorInterface::class, __METHOD__), \E_USER_DEPRECATED);
1✔
156
        }
157

158
        if ($formats instanceof FormatsProviderInterface) {
80✔
159
            @trigger_error(sprintf('Passing an instance of %s to %s() is deprecated since version 2.5 and will be removed in 3.0, pass an array instead.', FormatsProviderInterface::class, __METHOD__), \E_USER_DEPRECATED);
4✔
160

161
            $this->formatsProvider = $formats;
4✔
162
        } else {
163
            $this->formats = $formats;
76✔
164
        }
165

166
        $this->setFilterLocator($filterLocator, true);
80✔
167

168
        if ($resourceMetadataFactory instanceof ResourceMetadataFactoryInterface) {
78✔
169
            trigger_deprecation('api-platform/core', '2.7', sprintf('Use "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class));
52✔
170
        }
171

172
        $this->resourceMetadataFactory = $resourceMetadataFactory;
78✔
173
        $this->propertyNameCollectionFactory = $propertyNameCollectionFactory;
78✔
174
        $this->propertyMetadataFactory = $propertyMetadataFactory;
78✔
175
        $this->operationPathResolver = $operationPathResolver;
78✔
176
        $this->oauthEnabled = $oauthEnabled;
78✔
177
        $this->oauthType = $oauthType;
78✔
178
        $this->oauthFlow = $oauthFlow;
78✔
179
        $this->oauthTokenUrl = $oauthTokenUrl;
78✔
180
        $this->oauthAuthorizationUrl = $oauthAuthorizationUrl;
78✔
181
        $this->oauthScopes = $oauthScopes;
78✔
182
        $this->subresourceOperationFactory = $subresourceOperationFactory;
78✔
183
        $this->paginationEnabled = $paginationEnabled;
78✔
184
        $this->paginationPageParameterName = $paginationPageParameterName;
78✔
185
        $this->apiKeys = $apiKeys;
78✔
186
        $this->clientItemsPerPage = $clientItemsPerPage;
78✔
187
        $this->itemsPerPageParameterName = $itemsPerPageParameterName;
78✔
188
        $this->paginationClientEnabled = $paginationClientEnabled;
78✔
189
        $this->paginationClientEnabledParameterName = $paginationClientEnabledParameterName;
78✔
190
        $this->defaultContext[self::SPEC_VERSION] = $swaggerVersions[0] ?? 2;
78✔
191
        $this->defaultContext = array_merge($this->defaultContext, $defaultContext);
78✔
192
        $this->identifiersExtractor = $identifiersExtractor;
78✔
193
        $this->openApiNormalizer = $openApiNormalizer;
78✔
194
        $this->legacyMode = $legacyMode;
78✔
195
    }
196

197
    /**
198
     * @param mixed|null $format
199
     *
200
     * @return array|string|int|float|bool|\ArrayObject|null
201
     */
202
    public function normalize($object, $format = null, array $context = [])
203
    {
204
        if ($object instanceof OpenApi) {
49✔
205
            @trigger_error('Using the swagger DocumentationNormalizer is deprecated in favor of decorating the OpenApiFactory, use the "openapi.backward_compatibility_layer" configuration to change this behavior.', \E_USER_DEPRECATED);
1✔
206

207
            return $this->openApiNormalizer->normalize($object, $format, $context);
1✔
208
        }
209

210
        $v3 = 3 === ($context['spec_version'] ?? $this->defaultContext['spec_version']) && !($context['api_gateway'] ?? $this->defaultContext['api_gateway']);
48✔
211

212
        $definitions = new \ArrayObject();
48✔
213
        $paths = new \ArrayObject();
48✔
214
        $links = new \ArrayObject();
48✔
215

216
        if ($this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) {
48✔
217
            foreach ($object->getResourceNameCollection() as $resourceClass) {
×
218
                $resourceMetadataCollection = $this->resourceMetadataFactory->create($resourceClass);
×
219
                foreach ($resourceMetadataCollection as $i => $resourceMetadata) {
×
220
                    $resourceMetadata = $this->transformResourceToResourceMetadata($resourceMetadata);
×
221
                    // Items needs to be parsed first to be able to reference the lines from the collection operation
222
                    $this->addPaths($v3, $paths, $definitions, $resourceClass, $resourceMetadata->getShortName(), $resourceMetadata, OperationType::ITEM, $links);
×
223
                    $this->addPaths($v3, $paths, $definitions, $resourceClass, $resourceMetadata->getShortName(), $resourceMetadata, OperationType::COLLECTION, $links);
×
224
                }
225
            }
226

227
            $definitions->ksort();
×
228
            $paths->ksort();
×
229

230
            return $this->computeDoc($v3, $object, $definitions, $paths, $context);
×
231
        }
232

233
        foreach ($object->getResourceNameCollection() as $resourceClass) {
48✔
234
            $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
48✔
235

236
            if ($this->identifiersExtractor) {
48✔
237
                $identifiers = [];
48✔
238
                if ($resourceMetadata->getItemOperations()) {
48✔
239
                    $identifiers = $this->identifiersExtractor->getIdentifiersFromResourceClass($resourceClass);
36✔
240
                }
241

242
                $resourceMetadata = $resourceMetadata->withAttributes(($resourceMetadata->getAttributes() ?: []) + ['identifiers' => $identifiers]);
48✔
243
            }
244
            $resourceShortName = $resourceMetadata->getShortName();
48✔
245

246
            // Items needs to be parsed first to be able to reference the lines from the collection operation
247
            $this->addPaths($v3, $paths, $definitions, $resourceClass, $resourceShortName, $resourceMetadata, OperationType::ITEM, $links);
48✔
248
            $this->addPaths($v3, $paths, $definitions, $resourceClass, $resourceShortName, $resourceMetadata, OperationType::COLLECTION, $links);
48✔
249

250
            if (null === $this->subresourceOperationFactory) {
48✔
251
                continue;
44✔
252
            }
253

254
            foreach ($this->subresourceOperationFactory->create($resourceClass) as $operationId => $subresourceOperation) {
4✔
255
                $method = $resourceMetadata->getTypedOperationAttribute(OperationType::SUBRESOURCE, $subresourceOperation['operation_name'], 'method', 'GET');
4✔
256
                $paths[$this->getPath($subresourceOperation['shortNames'][0], $subresourceOperation['route_name'], $subresourceOperation, OperationType::SUBRESOURCE)][strtolower($method)] = $this->addSubresourceOperation($v3, $subresourceOperation, $definitions, $operationId, $resourceMetadata);
4✔
257
            }
258
        }
259

260
        $definitions->ksort();
48✔
261
        $paths->ksort();
48✔
262

263
        return $this->computeDoc($v3, $object, $definitions, $paths, $context);
48✔
264
    }
265

266
    /**
267
     * Updates the list of entries in the paths collection.
268
     */
269
    private function addPaths(bool $v3, \ArrayObject $paths, \ArrayObject $definitions, string $resourceClass, string $resourceShortName, ResourceMetadata $resourceMetadata, string $operationType, \ArrayObject $links)
270
    {
271
        if (null === $operations = OperationType::COLLECTION === $operationType ? $resourceMetadata->getCollectionOperations() : $resourceMetadata->getItemOperations()) {
48✔
272
            return;
18✔
273
        }
274

275
        foreach ($operations as $operationName => $operation) {
47✔
276
            if (false === ($operation['openapi'] ?? null)) {
46✔
277
                continue;
×
278
            }
279

280
            // Skolem IRI
281
            if ('api_genid' === ($operation['route_name'] ?? null)) {
46✔
282
                continue;
×
283
            }
284

285
            if (isset($operation['uri_template'])) {
46✔
286
                $path = str_replace('.{_format}', '', $operation['uri_template']);
×
287
                if (!str_starts_with($path, '/')) {
×
288
                    $path = '/'.$path;
×
289
                }
290
            } else {
291
                $path = $this->getPath($resourceShortName, $operationName, $operation, $operationType);
46✔
292
            }
293

294
            if ($this->operationMethodResolver) {
46✔
295
                $method = OperationType::ITEM === $operationType ? $this->operationMethodResolver->getItemOperationMethod($resourceClass, $operationName) : $this->operationMethodResolver->getCollectionOperationMethod($resourceClass, $operationName);
2✔
296
            } else {
297
                $method = $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'method', 'GET');
44✔
298
            }
299

300
            $paths[$path][strtolower($method)] = $this->getPathOperation($v3, $operationName, $operation, $method, $operationType, $resourceClass, $resourceMetadata, $definitions, $links);
46✔
301
        }
302
    }
303

304
    /**
305
     * Gets the path for an operation.
306
     *
307
     * If the path ends with the optional _format parameter, it is removed
308
     * as optional path parameters are not yet supported.
309
     *
310
     * @see https://github.com/OAI/OpenAPI-Specification/issues/93
311
     */
312
    private function getPath(string $resourceShortName, string $operationName, array $operation, string $operationType): string
313
    {
314
        $path = $this->operationPathResolver->resolveOperationPath($resourceShortName, $operation, $operationType, $operationName);
46✔
315

316
        if ('.{_format}' === substr($path, -10)) {
46✔
317
            $path = substr($path, 0, -10);
46✔
318
        }
319

320
        return $path;
46✔
321
    }
322

323
    /**
324
     * Gets a path Operation Object.
325
     *
326
     * @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#operation-object
327
     */
328
    private function getPathOperation(bool $v3, string $operationName, array $operation, string $method, string $operationType, string $resourceClass, ResourceMetadata $resourceMetadata, \ArrayObject $definitions, \ArrayObject $links): \ArrayObject
329
    {
330
        $pathOperation = new \ArrayObject($operation[$v3 ? 'openapi_context' : 'swagger_context'] ?? []);
46✔
331
        $resourceShortName = $resourceMetadata->getShortName();
46✔
332
        $pathOperation['tags'] ?? $pathOperation['tags'] = [$resourceShortName];
46✔
333
        $pathOperation['operationId'] ?? $pathOperation['operationId'] = lcfirst($operationName).ucfirst($resourceShortName).ucfirst($operationType);
46✔
334
        if ($v3 && 'GET' === $method && OperationType::ITEM === $operationType && $link = $this->getLinkObject($resourceClass, $pathOperation['operationId'], $this->getPath($resourceShortName, $operationName, $operation, $operationType))) {
46✔
335
            $links[$pathOperation['operationId']] = $link;
4✔
336
        }
337
        if ($resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'deprecation_reason', null, true)) {
46✔
338
            $pathOperation['deprecated'] = true;
×
339
        }
340

341
        if (null === $this->formatsProvider) {
46✔
342
            $requestFormats = $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'input_formats', [], true);
42✔
343
            $responseFormats = $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'output_formats', [], true);
42✔
344
        } else {
345
            $requestFormats = $responseFormats = $this->formatsProvider->getFormatsFromOperation($resourceClass, $operationName, $operationType);
4✔
346
        }
347

348
        $requestMimeTypes = $this->flattenMimeTypes($requestFormats);
46✔
349
        $responseMimeTypes = $this->flattenMimeTypes($responseFormats);
46✔
350
        switch ($method) {
351
            case 'GET':
46✔
352
                return $this->updateGetOperation($v3, $pathOperation, $responseMimeTypes, $operationType, $resourceMetadata, $resourceClass, $resourceShortName, $operationName, $definitions);
44✔
353
            case 'POST':
21✔
354
                return $this->updatePostOperation($v3, $pathOperation, $requestMimeTypes, $responseMimeTypes, $operationType, $resourceMetadata, $resourceClass, $resourceShortName, $operationName, $definitions, $links);
19✔
355
            case 'PATCH':
21✔
356
                $pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Updates the %s resource.', $resourceShortName);
×
357
                // no break
358
            case 'PUT':
21✔
359
                return $this->updatePutOperation($v3, $pathOperation, $requestMimeTypes, $responseMimeTypes, $operationType, $resourceMetadata, $resourceClass, $resourceShortName, $operationName, $definitions);
19✔
360
            case 'DELETE':
2✔
361
                return $this->updateDeleteOperation($v3, $pathOperation, $resourceShortName, $operationType, $operationName, $resourceMetadata, $resourceClass);
×
362
        }
363

364
        return $pathOperation;
2✔
365
    }
366

367
    /**
368
     * @return array the update message as first value, and if the schema is defined as second
369
     */
370
    private function addSchemas(bool $v3, array $message, \ArrayObject $definitions, string $resourceClass, string $operationType, string $operationName, array $mimeTypes, string $type = Schema::TYPE_OUTPUT, bool $forceCollection = false): array
371
    {
372
        if (!$v3) {
44✔
373
            $jsonSchema = $this->getJsonSchema($v3, $definitions, $resourceClass, $type, $operationType, $operationName, 'json', null, $forceCollection);
24✔
374
            if (!$jsonSchema->isDefined()) {
24✔
375
                return [$message, false];
×
376
            }
377

378
            $message['schema'] = $jsonSchema->getArrayCopy(false);
24✔
379

380
            return [$message, true];
24✔
381
        }
382

383
        foreach ($mimeTypes as $mimeType => $format) {
20✔
384
            $jsonSchema = $this->getJsonSchema($v3, $definitions, $resourceClass, $type, $operationType, $operationName, $format, null, $forceCollection);
20✔
385
            if (!$jsonSchema->isDefined()) {
20✔
386
                return [$message, false];
×
387
            }
388

389
            $message['content'][$mimeType] = ['schema' => $jsonSchema->getArrayCopy(false)];
20✔
390
        }
391

392
        return [$message, true];
20✔
393
    }
394

395
    private function updateGetOperation(bool $v3, \ArrayObject $pathOperation, array $mimeTypes, string $operationType, ResourceMetadata $resourceMetadata, string $resourceClass, string $resourceShortName, string $operationName, \ArrayObject $definitions): \ArrayObject
396
    {
397
        $successStatus = (string) $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'status', '200');
44✔
398

399
        if (!$v3) {
44✔
400
            $pathOperation['produces'] ?? $pathOperation['produces'] = array_keys($mimeTypes);
24✔
401
        }
402

403
        if (OperationType::COLLECTION === $operationType) {
44✔
404
            $outputResourseShortName = $resourceMetadata->getCollectionOperations()[$operationName]['output']['name'] ?? $resourceShortName;
27✔
405
            $pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Retrieves the collection of %s resources.', $outputResourseShortName);
27✔
406

407
            $successResponse = ['description' => sprintf('%s collection response', $outputResourseShortName)];
27✔
408
            [$successResponse] = $this->addSchemas($v3, $successResponse, $definitions, $resourceClass, $operationType, $operationName, $mimeTypes);
27✔
409

410
            $pathOperation['responses'] ?? $pathOperation['responses'] = [$successStatus => $successResponse];
27✔
411

412
            if (
413
                ($resourceMetadata->getAttributes()['extra_properties']['is_legacy_subresource'] ?? false)
27✔
414
                || ($resourceMetadata->getAttributes()['extra_properties']['is_alternate_resource_metadata'] ?? false)) {
27✔
415
                // Avoid duplicates parameters when there is a filter on a subresource identifier
416
                $parametersMemory = [];
×
417
                $pathOperation['parameters'] = [];
×
418

419
                foreach ($resourceMetadata->getCollectionOperations()[$operationName]['identifiers'] as $parameterName => [$class, $identifier]) {
×
420
                    $parameter = ['name' => $parameterName, 'in' => 'path', 'required' => true];
×
421
                    $v3 ? $parameter['schema'] = ['type' => 'string'] : $parameter['type'] = 'string';
×
422
                    $pathOperation['parameters'][] = $parameter;
×
423
                    $parametersMemory[] = $parameterName;
×
424
                }
425

426
                if ($parameters = $this->getFiltersParameters($v3, $resourceClass, $operationName, $resourceMetadata)) {
×
427
                    foreach ($parameters as $parameter) {
×
428
                        if (!\in_array($parameter['name'], $parametersMemory, true)) {
×
429
                            $pathOperation['parameters'][] = $parameter;
×
430
                        }
431
                    }
432
                }
433
            } else {
434
                $pathOperation['parameters'] ?? $pathOperation['parameters'] = $this->getFiltersParameters($v3, $resourceClass, $operationName, $resourceMetadata);
27✔
435
            }
436

437
            $this->addPaginationParameters($v3, $resourceMetadata, OperationType::COLLECTION, $operationName, $pathOperation);
27✔
438

439
            return $pathOperation;
27✔
440
        }
441

442
        $outputResourseShortName = $resourceMetadata->getItemOperations()[$operationName]['output']['name'] ?? $resourceShortName;
36✔
443
        $pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Retrieves a %s resource.', $outputResourseShortName);
36✔
444

445
        $pathOperation = $this->addItemOperationParameters($v3, $pathOperation, $operationType, $operationName, $resourceMetadata, $resourceClass);
36✔
446

447
        $successResponse = ['description' => sprintf('%s resource response', $outputResourseShortName)];
36✔
448
        [$successResponse] = $this->addSchemas($v3, $successResponse, $definitions, $resourceClass, $operationType, $operationName, $mimeTypes);
36✔
449

450
        $pathOperation['responses'] ?? $pathOperation['responses'] = [
36✔
451
            $successStatus => $successResponse,
36✔
452
            '404' => ['description' => 'Resource not found'],
36✔
453
        ];
454

455
        return $pathOperation;
36✔
456
    }
457

458
    private function addPaginationParameters(bool $v3, ResourceMetadata $resourceMetadata, string $operationType, string $operationName, \ArrayObject $pathOperation)
459
    {
460
        if ($this->paginationEnabled && $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'pagination_enabled', true, true)) {
27✔
461
            $paginationParameter = [
23✔
462
                'name' => $this->paginationPageParameterName,
23✔
463
                'in' => 'query',
23✔
464
                'required' => false,
23✔
465
                'description' => 'The collection page number',
23✔
466
            ];
23✔
467
            $v3 ? $paginationParameter['schema'] = [
23✔
468
                'type' => 'integer',
23✔
469
                'default' => 1,
23✔
470
            ] : $paginationParameter['type'] = 'integer';
12✔
471
            $pathOperation['parameters'][] = $paginationParameter;
23✔
472

473
            if ($resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'pagination_client_items_per_page', $this->clientItemsPerPage, true)) {
23✔
474
                $itemPerPageParameter = [
7✔
475
                    'name' => $this->itemsPerPageParameterName,
7✔
476
                    'in' => 'query',
7✔
477
                    'required' => false,
7✔
478
                    'description' => 'The number of items per page',
7✔
479
                ];
7✔
480
                if ($v3) {
7✔
481
                    $itemPerPageParameter['schema'] = [
4✔
482
                        'type' => 'integer',
4✔
483
                        'default' => $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'pagination_items_per_page', 30, true),
4✔
484
                        'minimum' => 0,
4✔
485
                    ];
4✔
486

487
                    $maxItemsPerPage = $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'maximum_items_per_page', null, true);
4✔
488
                    if (null !== $maxItemsPerPage) {
4✔
489
                        @trigger_error('The "maximum_items_per_page" option has been deprecated since API Platform 2.5 in favor of "pagination_maximum_items_per_page" and will be removed in API Platform 3.', \E_USER_DEPRECATED);
1✔
490
                    }
491
                    $maxItemsPerPage = $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'pagination_maximum_items_per_page', $maxItemsPerPage, true);
4✔
492

493
                    if (null !== $maxItemsPerPage) {
4✔
494
                        $itemPerPageParameter['schema']['maximum'] = $maxItemsPerPage;
4✔
495
                    }
496
                } else {
497
                    $itemPerPageParameter['type'] = 'integer';
3✔
498
                }
499

500
                $pathOperation['parameters'][] = $itemPerPageParameter;
7✔
501
            }
502
        }
503

504
        if ($this->paginationEnabled && $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'pagination_client_enabled', $this->paginationClientEnabled, true)) {
27✔
505
            $paginationEnabledParameter = [
2✔
506
                'name' => $this->paginationClientEnabledParameterName,
2✔
507
                'in' => 'query',
2✔
508
                'required' => false,
2✔
509
                'description' => 'Enable or disable pagination',
2✔
510
            ];
2✔
511
            $v3 ? $paginationEnabledParameter['schema'] = ['type' => 'boolean'] : $paginationEnabledParameter['type'] = 'boolean';
2✔
512
            $pathOperation['parameters'][] = $paginationEnabledParameter;
2✔
513
        }
514
    }
515

516
    /**
517
     * @throws ResourceClassNotFoundException
518
     */
519
    private function addSubresourceOperation(bool $v3, array $subresourceOperation, \ArrayObject $definitions, string $operationId, ResourceMetadata $resourceMetadata): \ArrayObject
520
    {
521
        $operationName = 'get'; // TODO: we might want to extract that at some point to also support other subresource operations
4✔
522
        $collection = $subresourceOperation['collection'] ?? false;
4✔
523

524
        $subResourceMetadata = $this->resourceMetadataFactory->create($subresourceOperation['resource_class']);
4✔
525

526
        $pathOperation = new \ArrayObject([]);
4✔
527
        $pathOperation['tags'] = $subresourceOperation['shortNames'];
4✔
528
        $pathOperation['operationId'] = $operationId;
4✔
529
        $pathOperation['summary'] = sprintf('Retrieves %s%s resource%s.', $subresourceOperation['collection'] ? 'the collection of ' : 'a ', $subresourceOperation['shortNames'][0], $subresourceOperation['collection'] ? 's' : '');
4✔
530

531
        if (null === $this->formatsProvider) {
4✔
532
            // TODO: Subresource operation metadata aren't available by default, for now we have to fallback on default formats.
533
            // TODO: A better approach would be to always populate the subresource operation array.
534
            $subResourceMetadata = $this
2✔
535
                ->resourceMetadataFactory
2✔
536
                ->create($subresourceOperation['resource_class']);
2✔
537

538
            if ($this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) {
2✔
539
                $subResourceMetadata = $this->transformResourceToResourceMetadata($subResourceMetadata[0]);
×
540
            }
541

542
            $responseFormats = $subResourceMetadata->getTypedOperationAttribute(OperationType::SUBRESOURCE, $operationName, 'output_formats', $this->formats, true);
2✔
543
        } else {
544
            $responseFormats = $this->formatsProvider->getFormatsFromOperation($subresourceOperation['resource_class'], $operationName, OperationType::SUBRESOURCE);
2✔
545
        }
546

547
        $mimeTypes = $this->flattenMimeTypes($responseFormats);
4✔
548

549
        if (!$v3) {
4✔
550
            $pathOperation['produces'] = array_keys($mimeTypes);
2✔
551
        }
552

553
        $successResponse = [
4✔
554
            'description' => sprintf('%s %s response', $subresourceOperation['shortNames'][0], $collection ? 'collection' : 'resource'),
4✔
555
        ];
4✔
556
        [$successResponse] = $this->addSchemas($v3, $successResponse, $definitions, $subresourceOperation['resource_class'], OperationType::SUBRESOURCE, $operationName, $mimeTypes, Schema::TYPE_OUTPUT, $collection);
4✔
557

558
        $pathOperation['responses'] = ['200' => $successResponse, '404' => ['description' => 'Resource not found']];
4✔
559

560
        // Avoid duplicates parameters when there is a filter on a subresource identifier
561
        $parametersMemory = [];
4✔
562
        $pathOperation['parameters'] = [];
4✔
563
        foreach ($subresourceOperation['identifiers'] as $parameterName => [$class, $identifier, $hasIdentifier]) {
4✔
564
            if (!str_contains($subresourceOperation['path'], sprintf('{%s}', $parameterName))) {
4✔
565
                continue;
×
566
            }
567

568
            $parameter = ['name' => $parameterName, 'in' => 'path', 'required' => true];
4✔
569
            $v3 ? $parameter['schema'] = ['type' => 'string'] : $parameter['type'] = 'string';
4✔
570
            $pathOperation['parameters'][] = $parameter;
4✔
571
            $parametersMemory[] = $parameterName;
4✔
572
        }
573

574
        if ($parameters = $this->getFiltersParameters($v3, $subresourceOperation['resource_class'], $operationName, $subResourceMetadata)) {
4✔
575
            foreach ($parameters as $parameter) {
×
576
                if (!\in_array($parameter['name'], $parametersMemory, true)) {
×
577
                    $pathOperation['parameters'][] = $parameter;
×
578
                }
579
            }
580
        }
581

582
        if ($subresourceOperation['collection']) {
4✔
583
            $this->addPaginationParameters($v3, $subResourceMetadata, OperationType::SUBRESOURCE, $subresourceOperation['operation_name'], $pathOperation);
×
584
        }
585

586
        return $pathOperation;
4✔
587
    }
588

589
    private function updatePostOperation(bool $v3, \ArrayObject $pathOperation, array $requestMimeTypes, array $responseMimeTypes, string $operationType, ResourceMetadata $resourceMetadata, string $resourceClass, string $resourceShortName, string $operationName, \ArrayObject $definitions, \ArrayObject $links): \ArrayObject
590
    {
591
        if (!$v3) {
19✔
592
            $pathOperation['consumes'] ?? $pathOperation['consumes'] = array_keys($requestMimeTypes);
11✔
593
            $pathOperation['produces'] ?? $pathOperation['produces'] = array_keys($responseMimeTypes);
11✔
594
        }
595

596
        $pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Creates a %s resource.', $resourceShortName);
19✔
597

598
        $identifiers = (array) $resourceMetadata
19✔
599
                ->getTypedOperationAttribute($operationType, $operationName, 'identifiers', [], false);
19✔
600

601
        $pathOperation = $this->addItemOperationParameters($v3, $pathOperation, $operationType, $operationName, $resourceMetadata, $resourceClass, OperationType::ITEM === $operationType ? false : true);
19✔
602

603
        $successResponse = ['description' => sprintf('%s resource created', $resourceShortName)];
19✔
604
        [$successResponse, $defined] = $this->addSchemas($v3, $successResponse, $definitions, $resourceClass, $operationType, $operationName, $responseMimeTypes);
19✔
605

606
        if ($defined && $v3 && ($links[$key = 'get'.ucfirst($resourceShortName).ucfirst(OperationType::ITEM)] ?? null)) {
19✔
607
            $successResponse['links'] = [ucfirst($key) => $links[$key]];
4✔
608
        }
609

610
        $pathOperation['responses'] ?? $pathOperation['responses'] = [
19✔
611
            (string) $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'status', '201') => $successResponse,
19✔
612
            '400' => ['description' => 'Invalid input'],
19✔
613
            '404' => ['description' => 'Resource not found'],
19✔
614
            '422' => ['description' => 'Unprocessable entity'],
19✔
615
        ];
616

617
        return $this->addRequestBody($v3, $pathOperation, $definitions, $resourceClass, $resourceShortName, $operationType, $operationName, $requestMimeTypes);
19✔
618
    }
619

620
    private function updatePutOperation(bool $v3, \ArrayObject $pathOperation, array $requestMimeTypes, array $responseMimeTypes, string $operationType, ResourceMetadata $resourceMetadata, string $resourceClass, string $resourceShortName, string $operationName, \ArrayObject $definitions): \ArrayObject
621
    {
622
        if (!$v3) {
19✔
623
            $pathOperation['consumes'] ?? $pathOperation['consumes'] = array_keys($requestMimeTypes);
11✔
624
            $pathOperation['produces'] ?? $pathOperation['produces'] = array_keys($responseMimeTypes);
11✔
625
        }
626

627
        $pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Replaces the %s resource.', $resourceShortName);
19✔
628

629
        $pathOperation = $this->addItemOperationParameters($v3, $pathOperation, $operationType, $operationName, $resourceMetadata, $resourceClass);
19✔
630

631
        $successResponse = ['description' => sprintf('%s resource updated', $resourceShortName)];
19✔
632
        [$successResponse] = $this->addSchemas($v3, $successResponse, $definitions, $resourceClass, $operationType, $operationName, $responseMimeTypes);
19✔
633

634
        $pathOperation['responses'] ?? $pathOperation['responses'] = [
19✔
635
            (string) $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'status', '200') => $successResponse,
19✔
636
            '400' => ['description' => 'Invalid input'],
19✔
637
            '404' => ['description' => 'Resource not found'],
19✔
638
            '422' => ['description' => 'Unprocessable entity'],
19✔
639
        ];
640

641
        return $this->addRequestBody($v3, $pathOperation, $definitions, $resourceClass, $resourceShortName, $operationType, $operationName, $requestMimeTypes, true);
19✔
642
    }
643

644
    private function addRequestBody(bool $v3, \ArrayObject $pathOperation, \ArrayObject $definitions, string $resourceClass, string $resourceShortName, string $operationType, string $operationName, array $requestMimeTypes, bool $put = false)
645
    {
646
        if (isset($pathOperation['requestBody'])) {
19✔
647
            return $pathOperation;
×
648
        }
649

650
        [$message, $defined] = $this->addSchemas($v3, [], $definitions, $resourceClass, $operationType, $operationName, $requestMimeTypes, Schema::TYPE_INPUT);
19✔
651
        if (!$defined) {
19✔
652
            return $pathOperation;
×
653
        }
654

655
        $description = sprintf('The %s %s resource', $put ? 'updated' : 'new', $resourceShortName);
19✔
656
        if ($v3) {
19✔
657
            $pathOperation['requestBody'] = $message + ['description' => $description];
8✔
658

659
            return $pathOperation;
8✔
660
        }
661

662
        if (!$this->hasBodyParameter($pathOperation['parameters'] ?? [])) {
11✔
663
            $pathOperation['parameters'][] = [
11✔
664
                'name' => lcfirst($resourceShortName),
11✔
665
                'in' => 'body',
11✔
666
                'description' => $description,
11✔
667
            ] + $message;
11✔
668
        }
669

670
        return $pathOperation;
11✔
671
    }
672

673
    private function hasBodyParameter(array $parameters): bool
674
    {
675
        foreach ($parameters as $parameter) {
11✔
676
            if (\array_key_exists('in', $parameter) && 'body' === $parameter['in']) {
11✔
677
                return true;
1✔
678
            }
679
        }
680

681
        return false;
11✔
682
    }
683

684
    private function updateDeleteOperation(bool $v3, \ArrayObject $pathOperation, string $resourceShortName, string $operationType, string $operationName, ResourceMetadata $resourceMetadata, string $resourceClass): \ArrayObject
685
    {
686
        $pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Removes the %s resource.', $resourceShortName);
×
687
        $pathOperation['responses'] ?? $pathOperation['responses'] = [
×
688
            (string) $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'status', '204') => ['description' => sprintf('%s resource deleted', $resourceShortName)],
×
689
            '404' => ['description' => 'Resource not found'],
×
690
        ];
691

692
        return $this->addItemOperationParameters($v3, $pathOperation, $operationType, $operationName, $resourceMetadata, $resourceClass);
×
693
    }
694

695
    private function addItemOperationParameters(bool $v3, \ArrayObject $pathOperation, string $operationType, string $operationName, ResourceMetadata $resourceMetadata, string $resourceClass, bool $isPost = false): \ArrayObject
696
    {
697
        $identifiers = (array) $resourceMetadata
36✔
698
                ->getTypedOperationAttribute($operationType, $operationName, 'identifiers', [], false);
36✔
699

700
        // Auto-generated routes in API Platform < 2.7 are considered as collection, hotfix this as the OpenApi Factory supports new operations anyways.
701
        // this also fixes a bug where we could not create POST item operations in API P 2.6
702
        if (OperationType::ITEM === $operationType && $isPost) {
36✔
703
            $operationType = OperationType::COLLECTION;
×
704
        }
705

706
        if (!$identifiers && OperationType::COLLECTION !== $operationType) {
36✔
707
            try {
708
                $identifiers = $this->identifiersExtractor->getIdentifiersFromResourceClass($resourceClass);
36✔
709
            } catch (RuntimeException $e) {
×
710
                // Ignore exception here
711
            } catch (ResourceClassNotFoundException $e) {
×
712
                if (false === $this->legacyMode) {
×
713
                    // Skipping these, swagger is not compatible with post 2.7 resource metadata
714
                    return $pathOperation;
×
715
                }
716
                throw $e;
×
717
            }
718
        }
719

720
        if (\count($identifiers) > 1 ? $resourceMetadata->getItemOperationAttribute($operationName, 'composite_identifier', true, true) : false) {
36✔
721
            $identifiers = ['id'];
×
722
        }
723

724
        if (!$identifiers && OperationType::COLLECTION === $operationType) {
36✔
725
            return $pathOperation;
19✔
726
        }
727

728
        if (!isset($pathOperation['parameters'])) {
36✔
729
            $pathOperation['parameters'] = [];
36✔
730
        }
731

732
        foreach ($identifiers as $parameterName => $identifier) {
36✔
733
            $parameter = [
36✔
734
                'name' => \is_string($parameterName) ? $parameterName : $identifier,
36✔
735
                'in' => 'path',
36✔
736
                'required' => true,
36✔
737
            ];
36✔
738
            $v3 ? $parameter['schema'] = ['type' => 'string'] : $parameter['type'] = 'string';
36✔
739
            $pathOperation['parameters'][] = $parameter;
36✔
740
        }
741

742
        return $pathOperation;
36✔
743
    }
744

745
    private function getJsonSchema(bool $v3, \ArrayObject $definitions, string $resourceClass, string $type, ?string $operationType, ?string $operationName, string $format = 'json', array $serializerContext = null, bool $forceCollection = false): Schema
746
    {
747
        $schema = new Schema($v3 ? Schema::VERSION_OPENAPI : Schema::VERSION_SWAGGER);
44✔
748
        $schema->setDefinitions($definitions);
44✔
749

750
        if ($this->jsonSchemaFactory instanceof SchemaFactoryInterface) {
44✔
751
            $operation = $operationName ? (new class() extends HttpOperation {})->withName($operationName) : null;
×
752

753
            return $this->jsonSchemaFactory->buildSchema($resourceClass, $format, $type, $operation, $schema, $serializerContext, $forceCollection);
×
754
        }
755

756
        return $this->jsonSchemaFactory->buildSchema($resourceClass, $format, $type, $operationType, $operationName, $schema, $serializerContext, $forceCollection);
44✔
757
    }
758

759
    private function computeDoc(bool $v3, Documentation $documentation, \ArrayObject $definitions, \ArrayObject $paths, array $context): array
760
    {
761
        $baseUrl = $context[self::BASE_URL] ?? $this->defaultContext[self::BASE_URL];
48✔
762

763
        if ($v3) {
48✔
764
            $docs = ['openapi' => self::OPENAPI_VERSION];
22✔
765
            if ('/' !== $baseUrl && '' !== $baseUrl) {
22✔
766
                $docs['servers'] = [['url' => $baseUrl]];
22✔
767
            }
768
        } else {
769
            $docs = [
26✔
770
                'swagger' => self::SWAGGER_VERSION,
26✔
771
                'basePath' => $baseUrl,
26✔
772
            ];
26✔
773
        }
774

775
        $docs += [
48✔
776
            'info' => [
48✔
777
                'title' => $documentation->getTitle(),
48✔
778
                'version' => $documentation->getVersion(),
48✔
779
            ],
48✔
780
            'paths' => $paths,
48✔
781
        ];
48✔
782

783
        if ('' !== $description = $documentation->getDescription()) {
48✔
784
            $docs['info']['description'] = $description;
37✔
785
        }
786

787
        $securityDefinitions = [];
48✔
788
        $security = [];
48✔
789

790
        if ($this->oauthEnabled) {
48✔
791
            $oauthAttributes = [
4✔
792
                'authorizationUrl' => $this->oauthAuthorizationUrl,
4✔
793
                'scopes' => new \ArrayObject($this->oauthScopes),
4✔
794
            ];
4✔
795

796
            if ($this->oauthTokenUrl) {
4✔
797
                $oauthAttributes['tokenUrl'] = $this->oauthTokenUrl;
4✔
798
            }
799

800
            $securityDefinitions['oauth'] = [
4✔
801
                'type' => $this->oauthType,
4✔
802
                'description' => sprintf(
4✔
803
                    'OAuth 2.0 %s Grant',
4✔
804
                    strtolower(preg_replace('/[A-Z]/', ' \\0', lcfirst($this->oauthFlow)))
4✔
805
                ),
4✔
806
            ];
4✔
807

808
            if ($v3) {
4✔
809
                $securityDefinitions['oauth']['flows'] = [
2✔
810
                    $this->oauthFlow => $oauthAttributes,
2✔
811
                ];
2✔
812
            } else {
813
                $securityDefinitions['oauth']['flow'] = $this->oauthFlow;
2✔
814
                $securityDefinitions['oauth'] = array_merge($securityDefinitions['oauth'], $oauthAttributes);
2✔
815
            }
816

817
            $security[] = ['oauth' => []];
4✔
818
        }
819

820
        foreach ($this->apiKeys as $key => $apiKey) {
48✔
821
            $name = $apiKey['name'];
2✔
822
            $type = $apiKey['type'];
2✔
823

824
            $securityDefinitions[$key] = [
2✔
825
                'type' => 'apiKey',
2✔
826
                'in' => $type,
2✔
827
                'description' => sprintf('Value for the %s %s', $name, 'query' === $type ? sprintf('%s parameter', $type) : $type),
2✔
828
                'name' => $name,
2✔
829
            ];
2✔
830

831
            $security[] = [$key => []];
2✔
832
        }
833

834
        if ($securityDefinitions && $security) { // @phpstan-ignore-line false positive
48✔
835
            $docs['security'] = $security;
6✔
836
            if (!$v3) {
6✔
837
                $docs['securityDefinitions'] = $securityDefinitions;
3✔
838
            }
839
        }
840

841
        if ($v3) {
48✔
842
            if (\count($definitions) + \count($securityDefinitions)) {
22✔
843
                $docs['components'] = [];
20✔
844
                if (\count($definitions)) {
20✔
845
                    $docs['components']['schemas'] = $definitions;
20✔
846
                }
847
                if (\count($securityDefinitions)) {
20✔
848
                    $docs['components']['securitySchemes'] = $securityDefinitions;
22✔
849
                }
850
            }
851
        } elseif (\count($definitions) > 0) {
26✔
852
            $docs['definitions'] = $definitions;
24✔
853
        }
854

855
        return $docs;
48✔
856
    }
857

858
    /**
859
     * Gets parameters corresponding to enabled filters.
860
     */
861
    private function getFiltersParameters(bool $v3, string $resourceClass, string $operationName, ResourceMetadata $resourceMetadata): array
862
    {
863
        if (null === $this->filterLocator) {
31✔
864
            return [];
27✔
865
        }
866

867
        $parameters = [];
4✔
868
        $resourceFilters = $resourceMetadata->getCollectionOperationAttribute($operationName, 'filters', [], true);
4✔
869
        foreach ($resourceFilters as $filterId) {
4✔
870
            if (!$filter = $this->getFilter($filterId)) {
4✔
871
                continue;
4✔
872
            }
873

874
            foreach ($filter->getDescription($resourceClass) as $name => $data) {
4✔
875
                $parameter = [
4✔
876
                    'name' => $name,
4✔
877
                    'in' => 'query',
4✔
878
                    'required' => $data['required'],
4✔
879
                ];
4✔
880

881
                $type = \in_array($data['type'], Type::$builtinTypes, true) ? $this->jsonSchemaTypeFactory->getType(new Type($data['type'], false, null, $data['is_collection'] ?? false)) : ['type' => 'string'];
4✔
882
                $v3 ? $parameter['schema'] = $type : $parameter += $type;
4✔
883

884
                if ($v3 && isset($data['schema'])) {
4✔
885
                    $parameter['schema'] = $data['schema'];
2✔
886
                }
887

888
                if ('array' === ($type['type'] ?? '')) {
4✔
889
                    $deepObject = \in_array($data['type'], [Type::BUILTIN_TYPE_ARRAY, Type::BUILTIN_TYPE_OBJECT], true);
4✔
890

891
                    if ($v3) {
4✔
892
                        $parameter['style'] = $deepObject ? 'deepObject' : 'form';
2✔
893
                        $parameter['explode'] = true;
2✔
894
                    } else {
895
                        $parameter['collectionFormat'] = $deepObject ? 'csv' : 'multi';
2✔
896
                    }
897
                }
898

899
                $key = $v3 ? 'openapi' : 'swagger';
4✔
900
                if (isset($data[$key])) {
4✔
901
                    $parameter = $data[$key] + $parameter;
4✔
902
                }
903

904
                $parameters[] = $parameter;
4✔
905
            }
906
        }
907

908
        return $parameters;
4✔
909
    }
910

911
    public function supportsNormalization($data, $format = null, array $context = []): bool
912
    {
913
        return self::FORMAT === $format && ($data instanceof Documentation || $this->openApiNormalizer && $data instanceof OpenApi);
23✔
914
    }
915

916
    public function hasCacheableSupportsMethod(): bool
917
    {
918
        return true;
2✔
919
    }
920

921
    private function flattenMimeTypes(array $responseFormats): array
922
    {
923
        $responseMimeTypes = [];
46✔
924
        foreach ($responseFormats as $responseFormat => $mimeTypes) {
46✔
925
            foreach ($mimeTypes as $mimeType) {
42✔
926
                $responseMimeTypes[$mimeType] = $responseFormat;
42✔
927
            }
928
        }
929

930
        return $responseMimeTypes;
46✔
931
    }
932

933
    /**
934
     * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#linkObject.
935
     */
936
    private function getLinkObject(string $resourceClass, string $operationId, string $path): array
937
    {
938
        $linkObject = $identifiers = [];
15✔
939
        foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $propertyName) {
15✔
940
            $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName);
15✔
941
            if (!$propertyMetadata->isIdentifier()) {
15✔
942
                continue;
15✔
943
            }
944

945
            $linkObject['parameters'][$propertyName] = sprintf('$response.body#/%s', $propertyName);
4✔
946
            $identifiers[] = $propertyName;
4✔
947
        }
948

949
        if (!$linkObject) {
15✔
950
            return [];
11✔
951
        }
952
        $linkObject['operationId'] = $operationId;
4✔
953
        $linkObject['description'] = 1 === \count($identifiers) ? sprintf('The `%1$s` value returned in the response can be used as the `%1$s` parameter in `GET %2$s`.', $identifiers[0], $path) : sprintf('The values returned in the response can be used in `GET %s`.', $path);
4✔
954

955
        return $linkObject;
4✔
956
    }
957
}
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