vendor/api-platform/core/src/Core/OpenApi/Factory/OpenApiFactory.php line 97

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the API Platform project.
  4.  *
  5.  * (c) Kévin Dunglas <dunglas@gmail.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. declare(strict_types=1);
  11. namespace ApiPlatform\Core\OpenApi\Factory;
  12. use ApiPlatform\Core\Api\FilterLocatorTrait;
  13. use ApiPlatform\Core\Api\IdentifiersExtractorInterface;
  14. use ApiPlatform\Core\Api\OperationType;
  15. use ApiPlatform\Core\JsonSchema\SchemaFactoryInterface;
  16. use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
  17. use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
  18. use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
  19. use ApiPlatform\Core\Metadata\Resource\ResourceMetadata;
  20. use ApiPlatform\Core\Operation\Factory\SubresourceOperationFactoryInterface;
  21. use ApiPlatform\JsonSchema\Schema;
  22. use ApiPlatform\JsonSchema\TypeFactoryInterface;
  23. use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface;
  24. use ApiPlatform\OpenApi\Factory\OpenApiFactoryInterface;
  25. use ApiPlatform\OpenApi\Model;
  26. use ApiPlatform\OpenApi\Model\ExternalDocumentation;
  27. use ApiPlatform\OpenApi\Model\PathItem;
  28. use ApiPlatform\OpenApi\OpenApi;
  29. use ApiPlatform\OpenApi\Options;
  30. use ApiPlatform\PathResolver\OperationPathResolverInterface;
  31. use ApiPlatform\State\Pagination\PaginationOptions;
  32. use Psr\Container\ContainerInterface;
  33. use Symfony\Component\PropertyInfo\Type;
  34. /**
  35.  * Generates an Open API v3 specification.
  36.  */
  37. final class OpenApiFactory implements OpenApiFactoryInterface
  38. {
  39.     use FilterLocatorTrait;
  40.     public const BASE_URL 'base_url';
  41.     public const OPENAPI_DEFINITION_NAME 'openapi_definition_name';
  42.     private $resourceNameCollectionFactory;
  43.     private $resourceMetadataFactory;
  44.     private $propertyNameCollectionFactory;
  45.     private $propertyMetadataFactory;
  46.     private $operationPathResolver;
  47.     private $subresourceOperationFactory;
  48.     private $formats;
  49.     private $jsonSchemaFactory;
  50.     private $jsonSchemaTypeFactory;
  51.     private $openApiOptions;
  52.     private $paginationOptions;
  53.     private $identifiersExtractor;
  54.     public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactoryResourceMetadataFactoryInterface $resourceMetadataFactoryPropertyNameCollectionFactoryInterface $propertyNameCollectionFactoryPropertyMetadataFactoryInterface $propertyMetadataFactorySchemaFactoryInterface $jsonSchemaFactoryTypeFactoryInterface $jsonSchemaTypeFactoryOperationPathResolverInterface $operationPathResolverContainerInterface $filterLocatorSubresourceOperationFactoryInterface $subresourceOperationFactoryIdentifiersExtractorInterface $identifiersExtractor null, array $formats = [], Options $openApiOptions nullPaginationOptions $paginationOptions null)
  55.     {
  56.         $this->resourceNameCollectionFactory $resourceNameCollectionFactory;
  57.         $this->jsonSchemaFactory $jsonSchemaFactory;
  58.         $this->jsonSchemaTypeFactory $jsonSchemaTypeFactory;
  59.         $this->formats $formats;
  60.         $this->setFilterLocator($filterLocatortrue);
  61.         $this->resourceMetadataFactory $resourceMetadataFactory;
  62.         $this->propertyNameCollectionFactory $propertyNameCollectionFactory;
  63.         $this->propertyMetadataFactory $propertyMetadataFactory;
  64.         $this->operationPathResolver $operationPathResolver;
  65.         $this->subresourceOperationFactory $subresourceOperationFactory;
  66.         $this->identifiersExtractor $identifiersExtractor;
  67.         $this->openApiOptions $openApiOptions ?: new Options('API Platform');
  68.         $this->paginationOptions $paginationOptions ?: new PaginationOptions();
  69.     }
  70.     /**
  71.      * {@inheritdoc}
  72.      */
  73.     public function __invoke(array $context = []): OpenApi
  74.     {
  75.         $baseUrl $context[self::BASE_URL] ?? '/';
  76.         $contact null === $this->openApiOptions->getContactUrl() || null === $this->openApiOptions->getContactEmail() ? null : new Model\Contact($this->openApiOptions->getContactName(), $this->openApiOptions->getContactUrl(), $this->openApiOptions->getContactEmail());
  77.         $license null === $this->openApiOptions->getLicenseName() ? null : new Model\License($this->openApiOptions->getLicenseName(), $this->openApiOptions->getLicenseUrl());
  78.         $info = new Model\Info($this->openApiOptions->getTitle(), $this->openApiOptions->getVersion(), trim($this->openApiOptions->getDescription()), $this->openApiOptions->getTermsOfService(), $contact$license);
  79.         $servers '/' === $baseUrl || '' === $baseUrl ? [new Model\Server('/')] : [new Model\Server($baseUrl)];
  80.         $paths = new Model\Paths();
  81.         $links = [];
  82.         $schemas = new \ArrayObject();
  83.         foreach ($this->resourceNameCollectionFactory->create() as $resourceClass) {
  84.             $resourceMetadata $this->resourceMetadataFactory->create($resourceClass);
  85.             // Items needs to be parsed first to be able to reference the lines from the collection operation
  86.             $this->collectPaths($resourceMetadata$resourceClassOperationType::ITEM$context$paths$links$schemas);
  87.             $this->collectPaths($resourceMetadata$resourceClassOperationType::COLLECTION$context$paths$links$schemas);
  88.             $this->collectPaths($resourceMetadata$resourceClassOperationType::SUBRESOURCE$context$paths$links$schemas);
  89.         }
  90.         $securitySchemes $this->getSecuritySchemes();
  91.         $securityRequirements = [];
  92.         foreach (array_keys($securitySchemes) as $key) {
  93.             $securityRequirements[] = [$key => []];
  94.         }
  95.         return new OpenApi(
  96.             $info,
  97.             $servers,
  98.             $paths,
  99.             new Model\Components(
  100.                 $schemas,
  101.                 new \ArrayObject(),
  102.                 new \ArrayObject(),
  103.                 new \ArrayObject(),
  104.                 new \ArrayObject(),
  105.                 new \ArrayObject(),
  106.                 new \ArrayObject($securitySchemes)
  107.             ),
  108.             $securityRequirements
  109.         );
  110.     }
  111.     private function collectPaths(ResourceMetadata $resourceMetadatastring $resourceClassstring $operationType, array $contextModel\Paths $paths, array &$links\ArrayObject $schemas): void
  112.     {
  113.         $resourceShortName $resourceMetadata->getShortName();
  114.         $operations OperationType::COLLECTION === $operationType $resourceMetadata->getCollectionOperations() : (OperationType::ITEM === $operationType $resourceMetadata->getItemOperations() : $this->subresourceOperationFactory->create($resourceClass));
  115.         if (!$operations) {
  116.             return;
  117.         }
  118.         $rootResourceClass $resourceClass;
  119.         foreach ($operations as $operationName => $operation) {
  120.             if (OperationType::COLLECTION === $operationType && !$resourceMetadata->getItemOperations()) {
  121.                 $identifiers = [];
  122.             } else {
  123.                 $identifiers = (array) ($operation['identifiers'] ?? $resourceMetadata->getAttribute('identifiers'null === $this->identifiersExtractor ? ['id'] : $this->identifiersExtractor->getIdentifiersFromResourceClass($resourceClass)));
  124.             }
  125.             if (\count($identifiers) > $resourceMetadata->getAttribute('composite_identifier'true) : false) {
  126.                 $identifiers = ['id'];
  127.             }
  128.             $resourceClass $operation['resource_class'] ?? $rootResourceClass;
  129.             $path $this->getPath($resourceShortName$operationName$operation$operationType);
  130.             $method $resourceMetadata->getTypedOperationAttribute($operationType$operationName'method''GET');
  131.             if (!\in_array($methodPathItem::$methodstrue)) {
  132.                 continue;
  133.             }
  134.             [$requestMimeTypes$responseMimeTypes] = $this->getMimeTypes($resourceClass$operationName$operationType$resourceMetadata);
  135.             $operationId $operation['openapi_context']['operationId'] ?? lcfirst($operationName).ucfirst($resourceShortName).ucfirst($operationType);
  136.             $linkedOperationId 'get'.ucfirst($resourceShortName).ucfirst(OperationType::ITEM);
  137.             $pathItem $paths->getPath($path) ?: new Model\PathItem();
  138.             $forceSchemaCollection OperationType::SUBRESOURCE === $operationType ? ($operation['collection'] ?? false) : false;
  139.             $schema = new Schema('openapi');
  140.             $schema->setDefinitions($schemas);
  141.             $operationOutputSchemas = [];
  142.             foreach ($responseMimeTypes as $operationFormat) {
  143.                 $operationOutputSchema $this->jsonSchemaFactory->buildSchema($resourceClass$operationFormatSchema::TYPE_OUTPUT$operationType$operationName$schemanull$forceSchemaCollection);
  144.                 $operationOutputSchemas[$operationFormat] = $operationOutputSchema;
  145.                 $this->appendSchemaDefinitions($schemas$operationOutputSchema->getDefinitions());
  146.             }
  147.             $parameters = [];
  148.             $responses = [];
  149.             if ($operation['openapi_context']['parameters'] ?? false) {
  150.                 foreach ($operation['openapi_context']['parameters'] as $parameter) {
  151.                     $parameters[] = new Model\Parameter($parameter['name'], $parameter['in'], $parameter['description'] ?? ''$parameter['required'] ?? false$parameter['deprecated'] ?? false$parameter['allowEmptyValue'] ?? false$parameter['schema'] ?? [], $parameter['style'] ?? null$parameter['explode'] ?? false$parameter['allowReserved '] ?? false$parameter['example'] ?? null, isset($parameter['examples']) ? new \ArrayObject($parameter['examples']) : null, isset($parameter['content']) ? new \ArrayObject($parameter['content']) : null);
  152.                 }
  153.             }
  154.             // Set up parameters
  155.             if (OperationType::ITEM === $operationType) {
  156.                 foreach ($identifiers as $parameterName => $identifier) {
  157.                     $parameterName \is_string($parameterName) ? $parameterName $identifier;
  158.                     $parameter = new Model\Parameter($parameterName'path''Resource identifier'truefalsefalse, ['type' => 'string']);
  159.                     if ($this->hasParameter($parameter$parameters)) {
  160.                         continue;
  161.                     }
  162.                     $parameters[] = $parameter;
  163.                 }
  164.                 $links[$operationId] = $this->getLink($resourceClass$operationId$path);
  165.             } elseif (OperationType::COLLECTION === $operationType && 'GET' === $method) {
  166.                 foreach (array_merge($this->getPaginationParameters($resourceMetadata$operationName), $this->getFiltersParameters($resourceMetadata$operationName$resourceClass)) as $parameter) {
  167.                     if ($this->hasParameter($parameter$parameters)) {
  168.                         continue;
  169.                     }
  170.                     $parameters[] = $parameter;
  171.                 }
  172.             } elseif (OperationType::SUBRESOURCE === $operationType) {
  173.                 foreach ($operation['identifiers'] as $parameterName => [$class$property]) {
  174.                     $parameter = new Model\Parameter($parameterName'path'$this->resourceMetadataFactory->create($class)->getShortName().' identifier'truefalsefalse, ['type' => 'string']);
  175.                     if ($this->hasParameter($parameter$parameters)) {
  176.                         continue;
  177.                     }
  178.                     $parameters[] = $parameter;
  179.                 }
  180.                 if ($operation['collection']) {
  181.                     $subresourceMetadata $this->resourceMetadataFactory->create($resourceClass);
  182.                     foreach (array_merge($this->getPaginationParameters($resourceMetadata$operationName), $this->getFiltersParameters($subresourceMetadata$operationName$resourceClass)) as $parameter) {
  183.                         if ($this->hasParameter($parameter$parameters)) {
  184.                             continue;
  185.                         }
  186.                         $parameters[] = $parameter;
  187.                     }
  188.                 }
  189.             }
  190.             // Create responses
  191.             switch ($method) {
  192.                 case 'GET':
  193.                     $successStatus = (string) $resourceMetadata->getTypedOperationAttribute($operationType$operationName'status''200');
  194.                     $responseContent $this->buildContent($responseMimeTypes$operationOutputSchemas);
  195.                     $responses[$successStatus] = new Model\Response(sprintf('%s %s'$resourceShortNameOperationType::COLLECTION === $operationType 'collection' 'resource'), $responseContent);
  196.                     break;
  197.                 case 'POST':
  198.                     $responseLinks = new \ArrayObject(isset($links[$linkedOperationId]) ? [ucfirst($linkedOperationId) => $links[$linkedOperationId]] : []);
  199.                     $responseContent $this->buildContent($responseMimeTypes$operationOutputSchemas);
  200.                     $successStatus = (string) $resourceMetadata->getTypedOperationAttribute($operationType$operationName'status''201');
  201.                     $responses[$successStatus] = new Model\Response(sprintf('%s resource created'$resourceShortName), $responseContentnull$responseLinks);
  202.                     $responses['400'] = new Model\Response('Invalid input');
  203.                     $responses['422'] = new Model\Response('Unprocessable entity');
  204.                     break;
  205.                 case 'PATCH':
  206.                 case 'PUT':
  207.                     $responseLinks = new \ArrayObject(isset($links[$linkedOperationId]) ? [ucfirst($linkedOperationId) => $links[$linkedOperationId]] : []);
  208.                     $successStatus = (string) $resourceMetadata->getTypedOperationAttribute($operationType$operationName'status''200');
  209.                     $responseContent $this->buildContent($responseMimeTypes$operationOutputSchemas);
  210.                     $responses[$successStatus] = new Model\Response(sprintf('%s resource updated'$resourceShortName), $responseContentnull$responseLinks);
  211.                     $responses['400'] = new Model\Response('Invalid input');
  212.                     $responses['422'] = new Model\Response('Unprocessable entity');
  213.                     break;
  214.                 case 'DELETE':
  215.                     $successStatus = (string) $resourceMetadata->getTypedOperationAttribute($operationType$operationName'status''204');
  216.                     $responses[$successStatus] = new Model\Response(sprintf('%s resource deleted'$resourceShortName));
  217.                     break;
  218.             }
  219.             if (OperationType::ITEM === $operationType) {
  220.                 $responses['404'] = new Model\Response('Resource not found');
  221.             }
  222.             if (!$responses) {
  223.                 $responses['default'] = new Model\Response('Unexpected error');
  224.             }
  225.             if ($contextResponses $operation['openapi_context']['responses'] ?? false) {
  226.                 foreach ($contextResponses as $statusCode => $contextResponse) {
  227.                     $responses[$statusCode] = new Model\Response($contextResponse['description'] ?? '', isset($contextResponse['content']) ? new \ArrayObject($contextResponse['content']) : null, isset($contextResponse['headers']) ? new \ArrayObject($contextResponse['headers']) : null, isset($contextResponse['links']) ? new \ArrayObject($contextResponse['links']) : null);
  228.                 }
  229.             }
  230.             $requestBody null;
  231.             if ($contextRequestBody $operation['openapi_context']['requestBody'] ?? false) {
  232.                 $requestBody = new Model\RequestBody($contextRequestBody['description'] ?? '', new \ArrayObject($contextRequestBody['content']), $contextRequestBody['required'] ?? false);
  233.             } elseif ('PUT' === $method || 'POST' === $method || 'PATCH' === $method) {
  234.                 $operationInputSchemas = [];
  235.                 foreach ($requestMimeTypes as $operationFormat) {
  236.                     $operationInputSchema $this->jsonSchemaFactory->buildSchema($resourceClass$operationFormatSchema::TYPE_INPUT$operationType$operationName$schemanull$forceSchemaCollection);
  237.                     $operationInputSchemas[$operationFormat] = $operationInputSchema;
  238.                     $this->appendSchemaDefinitions($schemas$operationInputSchema->getDefinitions());
  239.                 }
  240.                 $requestBody = new Model\RequestBody(sprintf('The %s %s resource''POST' === $method 'new' 'updated'$resourceShortName), $this->buildContent($requestMimeTypes$operationInputSchemas), true);
  241.             }
  242.             $pathItem $pathItem->{'with'.ucfirst($method)}(new Model\Operation(
  243.                 $operationId,
  244.                 $operation['openapi_context']['tags'] ?? (OperationType::SUBRESOURCE === $operationType $operation['shortNames'] : [$resourceShortName]),
  245.                 $responses,
  246.                 $operation['openapi_context']['summary'] ?? $this->getPathDescription($resourceShortName$method$operationType),
  247.                 $operation['openapi_context']['description'] ?? $this->getPathDescription($resourceShortName$method$operationType),
  248.                 isset($operation['openapi_context']['externalDocs']) ? new ExternalDocumentation($operation['openapi_context']['externalDocs']['description'] ?? null$operation['openapi_context']['externalDocs']['url']) : null,
  249.                 $parameters,
  250.                 $requestBody,
  251.                 isset($operation['openapi_context']['callbacks']) ? new \ArrayObject($operation['openapi_context']['callbacks']) : null,
  252.                 $operation['openapi_context']['deprecated'] ?? (bool) $resourceMetadata->getTypedOperationAttribute($operationType$operationName'deprecation_reason'falsetrue),
  253.                 $operation['openapi_context']['security'] ?? null,
  254.                 $operation['openapi_context']['servers'] ?? null,
  255.                 array_filter($operation['openapi_context'] ?? [], static function ($item) {
  256.                     return preg_match('/^x-.*$/i'$item);
  257.                 }, \ARRAY_FILTER_USE_KEY)
  258.             ));
  259.             $paths->addPath($path$pathItem);
  260.         }
  261.     }
  262.     private function buildContent(array $responseMimeTypes, array $operationSchemas): \ArrayObject
  263.     {
  264.         /** @var \ArrayObject<Model\MediaType> */
  265.         $content = new \ArrayObject();
  266.         foreach ($responseMimeTypes as $mimeType => $format) {
  267.             $content[$mimeType] = new Model\MediaType(new \ArrayObject($operationSchemas[$format]->getArrayCopy(false)));
  268.         }
  269.         return $content;
  270.     }
  271.     private function getMimeTypes(string $resourceClassstring $operationNamestring $operationTypeResourceMetadata $resourceMetadata null): array
  272.     {
  273.         $requestFormats $resourceMetadata->getTypedOperationAttribute($operationType$operationName'input_formats'$this->formatstrue);
  274.         $responseFormats $resourceMetadata->getTypedOperationAttribute($operationType$operationName'output_formats'$this->formatstrue);
  275.         $requestMimeTypes $this->flattenMimeTypes($requestFormats);
  276.         $responseMimeTypes $this->flattenMimeTypes($responseFormats);
  277.         return [$requestMimeTypes$responseMimeTypes];
  278.     }
  279.     private function flattenMimeTypes(array $responseFormats): array
  280.     {
  281.         $responseMimeTypes = [];
  282.         foreach ($responseFormats as $responseFormat => $mimeTypes) {
  283.             foreach ($mimeTypes as $mimeType) {
  284.                 $responseMimeTypes[$mimeType] = $responseFormat;
  285.             }
  286.         }
  287.         return $responseMimeTypes;
  288.     }
  289.     /**
  290.      * Gets the path for an operation.
  291.      *
  292.      * If the path ends with the optional _format parameter, it is removed
  293.      * as optional path parameters are not yet supported.
  294.      *
  295.      * @see https://github.com/OAI/OpenAPI-Specification/issues/93
  296.      */
  297.     private function getPath(string $resourceShortNamestring $operationName, array $operationstring $operationType): string
  298.     {
  299.         $path $this->operationPathResolver->resolveOperationPath($resourceShortName$operation$operationType$operationName);
  300.         if ('.{_format}' === substr($path, -10)) {
  301.             $path substr($path0, -10);
  302.         }
  303.         return === strpos($path'/') ? $path '/'.$path;
  304.     }
  305.     private function getPathDescription(string $resourceShortNamestring $methodstring $operationType): string
  306.     {
  307.         switch ($method) {
  308.             case 'GET':
  309.                 $pathSummary OperationType::COLLECTION === $operationType 'Retrieves the collection of %s resources.' 'Retrieves a %s resource.';
  310.                 break;
  311.             case 'POST':
  312.                 $pathSummary 'Creates a %s resource.';
  313.                 break;
  314.             case 'PATCH':
  315.                 $pathSummary 'Updates the %s resource.';
  316.                 break;
  317.             case 'PUT':
  318.                 $pathSummary 'Replaces the %s resource.';
  319.                 break;
  320.             case 'DELETE':
  321.                 $pathSummary 'Removes the %s resource.';
  322.                 break;
  323.             default:
  324.                 return $resourceShortName;
  325.         }
  326.         return sprintf($pathSummary$resourceShortName);
  327.     }
  328.     /**
  329.      * @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#linkObject.
  330.      */
  331.     private function getLink(string $resourceClassstring $operationIdstring $path): Model\Link
  332.     {
  333.         $parameters = [];
  334.         foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $propertyName) {
  335.             $propertyMetadata $this->propertyMetadataFactory->create($resourceClass$propertyName);
  336.             if (!$propertyMetadata->isIdentifier()) {
  337.                 continue;
  338.             }
  339.             $parameters[$propertyName] = sprintf('$response.body#/%s'$propertyName);
  340.         }
  341.         return new Model\Link(
  342.             $operationId,
  343.             new \ArrayObject($parameters),
  344.             null,
  345.             === \count($parameters) ? sprintf('The `%1$s` value returned in the response can be used as the `%1$s` parameter in `GET %2$s`.'key($parameters), $path) : sprintf('The values returned in the response can be used in `GET %s`.'$path)
  346.         );
  347.     }
  348.     /**
  349.      * Gets parameters corresponding to enabled filters.
  350.      */
  351.     private function getFiltersParameters(ResourceMetadata $resourceMetadatastring $operationNamestring $resourceClass): array
  352.     {
  353.         $parameters = [];
  354.         $resourceFilters $resourceMetadata->getCollectionOperationAttribute($operationName'filters', [], true);
  355.         foreach ($resourceFilters as $filterId) {
  356.             if (!$filter $this->getFilter($filterId)) {
  357.                 continue;
  358.             }
  359.             foreach ($filter->getDescription($resourceClass) as $name => $data) {
  360.                 $schema $data['schema'] ?? (\in_array($data['type'], Type::$builtinTypestrue) ? $this->jsonSchemaTypeFactory->getType(new Type($data['type'], falsenull$data['is_collection'] ?? false)) : ['type' => 'string']);
  361.                 $parameters[] = new Model\Parameter(
  362.                     $name,
  363.                     'query',
  364.                     $data['description'] ?? '',
  365.                     $data['required'] ?? false,
  366.                     $data['openapi']['deprecated'] ?? false,
  367.                     $data['openapi']['allowEmptyValue'] ?? true,
  368.                     $schema,
  369.                     'array' === $schema['type'] && \in_array($data['type'],
  370.                         [Type::BUILTIN_TYPE_ARRAYType::BUILTIN_TYPE_OBJECT], true) ? 'deepObject' 'form',
  371.                     $data['openapi']['explode'] ?? ('array' === $schema['type']),
  372.                     $data['openapi']['allowReserved'] ?? false,
  373.                     $data['openapi']['example'] ?? null,
  374.                     isset($data['openapi']['examples']
  375.                     ) ? new \ArrayObject($data['openapi']['examples']) : null);
  376.             }
  377.         }
  378.         return $parameters;
  379.     }
  380.     private function getPaginationParameters(ResourceMetadata $resourceMetadatastring $operationName): array
  381.     {
  382.         if (!$this->paginationOptions->isPaginationEnabled()) {
  383.             return [];
  384.         }
  385.         $parameters = [];
  386.         if ($resourceMetadata->getCollectionOperationAttribute($operationName'pagination_enabled'truetrue)) {
  387.             $parameters[] = new Model\Parameter($this->paginationOptions->getPaginationPageParameterName(), 'query''The collection page number'falsefalsetrue, ['type' => 'integer''default' => 1]);
  388.             if ($resourceMetadata->getCollectionOperationAttribute($operationName'pagination_client_items_per_page'$this->paginationOptions->getClientItemsPerPage(), true)) {
  389.                 $schema = [
  390.                     'type' => 'integer',
  391.                     'default' => $resourceMetadata->getCollectionOperationAttribute($operationName'pagination_items_per_page'30true),
  392.                     'minimum' => 0,
  393.                 ];
  394.                 if (null !== $maxItemsPerPage $resourceMetadata->getCollectionOperationAttribute($operationName'pagination_maximum_items_per_page'nulltrue)) {
  395.                     $schema['maximum'] = $maxItemsPerPage;
  396.                 }
  397.                 $parameters[] = new Model\Parameter($this->paginationOptions->getItemsPerPageParameterName(), 'query''The number of items per page'falsefalsetrue$schema);
  398.             }
  399.         }
  400.         if ($resourceMetadata->getCollectionOperationAttribute($operationName'pagination_client_enabled'$this->paginationOptions->getPaginationClientEnabled(), true)) {
  401.             $parameters[] = new Model\Parameter($this->paginationOptions->getPaginationClientEnabledParameterName(), 'query''Enable or disable pagination'falsefalsetrue, ['type' => 'boolean']);
  402.         }
  403.         return $parameters;
  404.     }
  405.     private function getOauthSecurityScheme(): Model\SecurityScheme
  406.     {
  407.         $oauthFlow = new Model\OAuthFlow($this->openApiOptions->getOAuthAuthorizationUrl(), $this->openApiOptions->getOAuthTokenUrl(), $this->openApiOptions->getOAuthRefreshUrl(), new \ArrayObject($this->openApiOptions->getOAuthScopes()));
  408.         $description sprintf(
  409.             'OAuth 2.0 %s Grant',
  410.             strtolower(preg_replace('/[A-Z]/'' \\0'lcfirst($this->openApiOptions->getOAuthFlow())))
  411.         );
  412.         $implicit $password $clientCredentials $authorizationCode null;
  413.         switch ($this->openApiOptions->getOAuthFlow()) {
  414.             case 'implicit':
  415.                 $implicit $oauthFlow;
  416.                 break;
  417.             case 'password':
  418.                 $password $oauthFlow;
  419.                 break;
  420.             case 'application':
  421.             case 'clientCredentials':
  422.                 $clientCredentials $oauthFlow;
  423.                 break;
  424.             case 'accessCode':
  425.             case 'authorizationCode':
  426.                 $authorizationCode $oauthFlow;
  427.                 break;
  428.             default:
  429.                 throw new \LogicException('OAuth flow must be one of: implicit, password, clientCredentials, authorizationCode');
  430.         }
  431.         return new Model\SecurityScheme($this->openApiOptions->getOAuthType(), $descriptionnullnullnullnull, new Model\OAuthFlows($implicit$password$clientCredentials$authorizationCode), null);
  432.     }
  433.     private function getSecuritySchemes(): array
  434.     {
  435.         $securitySchemes = [];
  436.         if ($this->openApiOptions->getOAuthEnabled()) {
  437.             $securitySchemes['oauth'] = $this->getOauthSecurityScheme();
  438.         }
  439.         foreach ($this->openApiOptions->getApiKeys() as $key => $apiKey) {
  440.             $description sprintf('Value for the %s %s parameter.'$apiKey['name'], $apiKey['type']);
  441.             $securitySchemes[$key] = new Model\SecurityScheme('apiKey'$description$apiKey['name'], $apiKey['type']);
  442.         }
  443.         return $securitySchemes;
  444.     }
  445.     private function appendSchemaDefinitions(\ArrayObject $schemas\ArrayObject $definitions): void
  446.     {
  447.         foreach ($definitions as $key => $value) {
  448.             $schemas[$key] = $value;
  449.         }
  450.     }
  451.     /**
  452.      * @param Model\Parameter[] $parameters
  453.      */
  454.     private function hasParameter(Model\Parameter $parameter, array $parameters): bool
  455.     {
  456.         foreach ($parameters as $existingParameter) {
  457.             if ($existingParameter->getName() === $parameter->getName() && $existingParameter->getIn() === $parameter->getIn()) {
  458.                 return true;
  459.             }
  460.         }
  461.         return false;
  462.     }
  463. }