diff --git a/src/PrestaShopBundle/ApiPlatform/Metadata/Resource/Factory/OpenApiMetadataCollectionFactoryDecorator.php b/src/PrestaShopBundle/ApiPlatform/Metadata/Resource/Factory/OpenApiMetadataCollectionFactoryDecorator.php new file mode 100644 index 0000000000000..7c829de2c08bb --- /dev/null +++ b/src/PrestaShopBundle/ApiPlatform/Metadata/Resource/Factory/OpenApiMetadataCollectionFactoryDecorator.php @@ -0,0 +1,67 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +namespace PrestaShopBundle\ApiPlatform\Metadata\Resource\Factory; + +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Operation; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; +use ApiPlatform\OpenApi\Model\Operation as OpenapiOperation; +use PrestaShopBundle\ApiPlatform\Metadata\CQRSCommand; +use PrestaShopBundle\ApiPlatform\OpenApi\RequestBodyFactory; + +class OpenApiMetadataCollectionFactoryDecorator implements ResourceMetadataCollectionFactoryInterface +{ + public function __construct( + private readonly ResourceMetadataCollectionFactoryInterface $innerFactory, + private readonly RequestBodyFactory $requestBodyFactory, + ) { + } + + public function create(string $resourceClass): ResourceMetadataCollection + { + // We call the original method since we only want to alter the result of this method. + $resourceMetadataCollection = $this->innerFactory->create($resourceClass); + + /** @var ApiResource $resourceMetadata */ + foreach ($resourceMetadataCollection as $resourceMetadata) { + $operations = $resourceMetadata->getOperations(); + /** @var Operation $operation */ + foreach ($operations as $key => $operation) { + if (!($operation instanceof CQRSCommand)) { + continue; + } + + $openapiOperation = $operation->getOpenapi() ?: new OpenapiOperation(); + $openapiOperation = $openapiOperation->withRequestBody($this->requestBodyFactory->build($operation)); + $operations->add($key, $operation->withOpenapi($openapiOperation)); + } + } + + return $resourceMetadataCollection; + } +} diff --git a/src/PrestaShopBundle/ApiPlatform/OpenApi/RequestBodyFactory.php b/src/PrestaShopBundle/ApiPlatform/OpenApi/RequestBodyFactory.php new file mode 100644 index 0000000000000..9635f56fbea65 --- /dev/null +++ b/src/PrestaShopBundle/ApiPlatform/OpenApi/RequestBodyFactory.php @@ -0,0 +1,98 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +namespace PrestaShopBundle\ApiPlatform\OpenApi; + +use ApiPlatform\OpenApi\Model\RequestBody; +use ArrayObject; +use PrestaShopBundle\ApiPlatform\Metadata\CQRSCommand; +use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface; + +class RequestBodyFactory +{ + public function __construct( + protected readonly PropertyInfoExtractorInterface|PropertyInitializableExtractorInterface $propertyInfoExtractor + ) { + } + + public function build(CQRSCommand $operation): ?RequestBody + { + if (empty($operation->getCQRSCommand()) || !class_exists($operation->getCQRSCommand())) { + return null; + } + + $requestMimeTypes = $this->flattenMimeTypes($operation->getInputFormats() ?: []); + $inputSchema = []; + foreach ($requestMimeTypes as $requestMimeType) { + $operationProperties = $this->getOperationProperties($operation); + $inputSchema[$requestMimeType] = [ + 'schema' => [ + 'type' => 'object', + 'properties' => $operationProperties, + ], + ]; + } + + return new RequestBody( + description: sprintf('The %s %s resource', 'POST' === $operation->getMethod() ? 'new' : 'updated', $operation->getShortName()), + content: new ArrayObject($inputSchema), + ); + } + + private function getOperationProperties(CQRSCommand $operation): array + { + $operationProperties = []; + $classProperties = $this->propertyInfoExtractor->getProperties($operation->getCQRSCommand()); + foreach ($classProperties as $property) { + if ($this->propertyInfoExtractor->isWritable($operation->getCQRSCommand(), $property) + || $this->propertyInfoExtractor->isInitializable($operation->getCQRSCommand(), $property)) { + $propertyTypes = $this->propertyInfoExtractor->getTypes($operation->getCQRSCommand(), $property); + if (count($propertyTypes) === 1) { + $propertyType = $propertyTypes[0]; + $type = $propertyType->getClassName() ?: $propertyType->getBuiltinType(); + } else { + $type = 'mixed'; + } + $operationProperties[$property] = ['type' => $type]; + } + } + + return $operationProperties; + } + + private function flattenMimeTypes(array $responseFormats): array + { + $responseMimeTypes = []; + foreach ($responseFormats as $responseFormat => $mimeTypes) { + foreach ($mimeTypes as $mimeType) { + $responseMimeTypes[$mimeType] = $responseFormat; + } + } + + return $responseMimeTypes; + } +} diff --git a/src/PrestaShopBundle/Resources/config/services/bundle/api_platform.yml b/src/PrestaShopBundle/Resources/config/services/bundle/api_platform.yml index d73a2ec6c101b..013f34ee6474d 100644 --- a/src/PrestaShopBundle/Resources/config/services/bundle/api_platform.yml +++ b/src/PrestaShopBundle/Resources/config/services/bundle/api_platform.yml @@ -45,6 +45,12 @@ services: $innerFactory: '@.inner' $isDebug: '%kernel.debug%' + PrestaShopBundle\ApiPlatform\Metadata\Resource\Factory\OpenApiMetadataCollectionFactoryDecorator: + decorates: api_platform.metadata.resource.metadata_collection_factory + autowire: true + arguments: + $innerFactory: '@.inner' + # This service depends on ResourceMetadataCollectionFactoryInterface (auto wired) and can extract the scopes defined on operations PrestaShopBundle\ApiPlatform\Scopes\ApiResourceScopesExtractor: autowire: true @@ -59,3 +65,6 @@ services: decorates: PrestaShopBundle\ApiPlatform\Scopes\ApiResourceScopesExtractor PrestaShopBundle\ApiPlatform\Scopes\ApiResourceScopesExtractorInterface: '@PrestaShopBundle\ApiPlatform\Scopes\CachedApiResourceScopesExtractor' + + PrestaShopBundle\ApiPlatform\OpenApi\RequestBodyFactory: + autowire: true