From ea02fb5fdef40a384cbe098f145067c1cd2d9ee0 Mon Sep 17 00:00:00 2001 From: Maxcastel Date: Tue, 30 Dec 2025 13:02:31 +0100 Subject: [PATCH] feat(OpenApi): allow to change the above node PathItem --- src/Metadata/ApiResource.php | 15 ++++++++ src/Metadata/Delete.php | 3 ++ src/Metadata/Error.php | 3 ++ src/Metadata/Get.php | 3 ++ src/Metadata/GetCollection.php | 3 ++ src/Metadata/HttpOperation.php | 15 ++++++++ src/Metadata/NotExposed.php | 3 ++ src/Metadata/Patch.php | 3 ++ src/Metadata/Post.php | 3 ++ src/Metadata/Put.php | 3 ++ src/OpenApi/Factory/OpenApiFactory.php | 48 ++++++++++++++++++++++++++ 11 files changed, 102 insertions(+) diff --git a/src/Metadata/ApiResource.php b/src/Metadata/ApiResource.php index 28580ea7c3..fd880e17bd 100644 --- a/src/Metadata/ApiResource.php +++ b/src/Metadata/ApiResource.php @@ -15,6 +15,7 @@ use ApiPlatform\Metadata\GraphQl\Operation as GraphQlOperation; use ApiPlatform\OpenApi\Model\Operation as OpenApiOperation; +use ApiPlatform\OpenApi\Model\PathItem; use ApiPlatform\State\OptionsInterface; /** @@ -327,6 +328,7 @@ public function __construct( protected ?bool $collectDenormalizationErrors = null, protected ?array $hydraContext = null, protected bool|OpenApiOperation|null $openapi = null, + protected ?PathItem $openApiPathItem = null, /** * The `validationContext` option configures the context of validation for the current ApiResource. * You can, for instance, describe the validation groups that will be used:. @@ -1366,6 +1368,19 @@ public function withOpenapi(bool|OpenApiOperation $openapi): static return $self; } + public function getOpenApiPathItem(): ?PathItem + { + return $this->openApiPathItem; + } + + public function withOpenApiPathItem(PathItem $openApiPathItem): static + { + $self = clone $this; + $self->openApiPathItem = $openApiPathItem; + + return $self; + } + public function getPaginationViaCursor(): ?array { return $this->paginationViaCursor; diff --git a/src/Metadata/Delete.php b/src/Metadata/Delete.php index 5f459c0e35..d2498c9113 100644 --- a/src/Metadata/Delete.php +++ b/src/Metadata/Delete.php @@ -15,6 +15,7 @@ use ApiPlatform\OpenApi\Attributes\Webhook; use ApiPlatform\OpenApi\Model\Operation as OpenApiOperation; +use ApiPlatform\OpenApi\Model\PathItem; use ApiPlatform\State\OptionsInterface; #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] @@ -45,6 +46,7 @@ public function __construct( ?array $paginationViaCursor = null, ?array $hydraContext = null, bool|OpenApiOperation|Webhook|null $openapi = null, + ?PathItem $openApiPathItem = null, ?array $exceptionToStatus = null, ?bool $queryParameterValidationEnabled = null, ?array $links = null, @@ -129,6 +131,7 @@ public function __construct( paginationViaCursor: $paginationViaCursor, hydraContext: $hydraContext, openapi: $openapi, + openApiPathItem: $openApiPathItem, exceptionToStatus: $exceptionToStatus, queryParameterValidationEnabled: $queryParameterValidationEnabled, links: $links, diff --git a/src/Metadata/Error.php b/src/Metadata/Error.php index abeb8ed7a5..bc886d0d53 100644 --- a/src/Metadata/Error.php +++ b/src/Metadata/Error.php @@ -15,6 +15,7 @@ use ApiPlatform\OpenApi\Attributes\Webhook; use ApiPlatform\OpenApi\Model\Operation as OpenApiOperation; +use ApiPlatform\OpenApi\Model\PathItem; use ApiPlatform\State\OptionsInterface; #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] @@ -45,6 +46,7 @@ public function __construct( ?array $paginationViaCursor = null, ?array $hydraContext = null, bool|OpenApiOperation|Webhook|null $openapi = null, + ?PathItem $openApiPathItem = null, ?array $exceptionToStatus = null, ?bool $queryParameterValidationEnabled = null, ?array $links = null, @@ -123,6 +125,7 @@ public function __construct( paginationViaCursor: $paginationViaCursor, hydraContext: $hydraContext, openapi: $openapi, + openApiPathItem: $openApiPathItem, exceptionToStatus: $exceptionToStatus, queryParameterValidationEnabled: $queryParameterValidationEnabled, links: $links, diff --git a/src/Metadata/Get.php b/src/Metadata/Get.php index 0139b82561..1c3595403e 100644 --- a/src/Metadata/Get.php +++ b/src/Metadata/Get.php @@ -15,6 +15,7 @@ use ApiPlatform\OpenApi\Attributes\Webhook; use ApiPlatform\OpenApi\Model\Operation as OpenApiOperation; +use ApiPlatform\OpenApi\Model\PathItem; use ApiPlatform\State\OptionsInterface; #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] @@ -45,6 +46,7 @@ public function __construct( ?array $paginationViaCursor = null, ?array $hydraContext = null, bool|OpenApiOperation|Webhook|null $openapi = null, + ?PathItem $openApiPathItem = null, ?array $exceptionToStatus = null, ?bool $queryParameterValidationEnabled = null, ?array $links = null, @@ -128,6 +130,7 @@ public function __construct( paginationViaCursor: $paginationViaCursor, hydraContext: $hydraContext, openapi: $openapi, + openApiPathItem: $openApiPathItem, exceptionToStatus: $exceptionToStatus, queryParameterValidationEnabled: $queryParameterValidationEnabled, links: $links, diff --git a/src/Metadata/GetCollection.php b/src/Metadata/GetCollection.php index 74886491a2..be5d8414d4 100644 --- a/src/Metadata/GetCollection.php +++ b/src/Metadata/GetCollection.php @@ -15,6 +15,7 @@ use ApiPlatform\OpenApi\Attributes\Webhook; use ApiPlatform\OpenApi\Model\Operation as OpenApiOperation; +use ApiPlatform\OpenApi\Model\PathItem; use ApiPlatform\State\OptionsInterface; #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] @@ -45,6 +46,7 @@ public function __construct( ?array $paginationViaCursor = null, ?array $hydraContext = null, bool|OpenApiOperation|Webhook|null $openapi = null, + ?PathItem $openApiPathItem = null, ?array $exceptionToStatus = null, ?bool $queryParameterValidationEnabled = null, ?array $links = null, @@ -129,6 +131,7 @@ public function __construct( paginationViaCursor: $paginationViaCursor, hydraContext: $hydraContext, openapi: $openapi, + openApiPathItem: $openApiPathItem, exceptionToStatus: $exceptionToStatus, queryParameterValidationEnabled: $queryParameterValidationEnabled, links: $links, diff --git a/src/Metadata/HttpOperation.php b/src/Metadata/HttpOperation.php index 3f5e0daaeb..255c064a5b 100644 --- a/src/Metadata/HttpOperation.php +++ b/src/Metadata/HttpOperation.php @@ -16,6 +16,7 @@ use ApiPlatform\Metadata\Exception\ProblemExceptionInterface; use ApiPlatform\OpenApi\Attributes\Webhook; use ApiPlatform\OpenApi\Model\Operation as OpenApiOperation; +use ApiPlatform\OpenApi\Model\PathItem; use ApiPlatform\State\OptionsInterface; use Symfony\Component\WebLink\Link as WebLink; @@ -165,6 +166,7 @@ public function __construct( protected ?array $paginationViaCursor = null, protected ?array $hydraContext = null, protected bool|OpenApiOperation|Webhook|null $openapi = null, + protected ?PathItem $openApiPathItem = null, protected ?array $exceptionToStatus = null, protected ?array $links = null, protected ?array $errors = null, @@ -640,6 +642,19 @@ public function withOpenapi(bool|OpenApiOperation|Webhook $openapi): static return $self; } + public function getOpenApiPathItem(): ?PathItem + { + return $this->openApiPathItem; + } + + public function withOpenApiPathItem(PathItem $openApiPathItem): static + { + $self = clone $this; + $self->openApiPathItem = $openApiPathItem; + + return $self; + } + public function getExceptionToStatus(): ?array { return $this->exceptionToStatus; diff --git a/src/Metadata/NotExposed.php b/src/Metadata/NotExposed.php index c7afb4941f..985b12b96d 100644 --- a/src/Metadata/NotExposed.php +++ b/src/Metadata/NotExposed.php @@ -15,6 +15,7 @@ use ApiPlatform\OpenApi\Attributes\Webhook; use ApiPlatform\OpenApi\Model\Operation as OpenApiOperation; +use ApiPlatform\OpenApi\Model\PathItem; use ApiPlatform\State\OptionsInterface; /** @@ -57,6 +58,7 @@ public function __construct( ?array $hydraContext = null, bool|OpenApiOperation|Webhook|null $openapi = false, + ?PathItem $openApiPathItem = null, ?array $exceptionToStatus = null, ?bool $queryParameterValidationEnabled = null, @@ -135,6 +137,7 @@ public function __construct( paginationViaCursor: $paginationViaCursor, hydraContext: $hydraContext, openapi: $openapi, + openApiPathItem: $openApiPathItem, exceptionToStatus: $exceptionToStatus, queryParameterValidationEnabled: $queryParameterValidationEnabled, links: $links, diff --git a/src/Metadata/Patch.php b/src/Metadata/Patch.php index b81814350d..0c3352c8a5 100644 --- a/src/Metadata/Patch.php +++ b/src/Metadata/Patch.php @@ -15,6 +15,7 @@ use ApiPlatform\OpenApi\Attributes\Webhook; use ApiPlatform\OpenApi\Model\Operation as OpenApiOperation; +use ApiPlatform\OpenApi\Model\PathItem; use ApiPlatform\State\OptionsInterface; #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] @@ -45,6 +46,7 @@ public function __construct( ?array $paginationViaCursor = null, ?array $hydraContext = null, bool|OpenApiOperation|Webhook|null $openapi = null, + ?PathItem $openApiPathItem = null, ?array $exceptionToStatus = null, ?bool $queryParameterValidationEnabled = null, ?array $links = null, @@ -129,6 +131,7 @@ public function __construct( paginationViaCursor: $paginationViaCursor, hydraContext: $hydraContext, openapi: $openapi, + openApiPathItem: $openApiPathItem, exceptionToStatus: $exceptionToStatus, queryParameterValidationEnabled: $queryParameterValidationEnabled, links: $links, diff --git a/src/Metadata/Post.php b/src/Metadata/Post.php index 208366234e..6f068c0aaa 100644 --- a/src/Metadata/Post.php +++ b/src/Metadata/Post.php @@ -15,6 +15,7 @@ use ApiPlatform\OpenApi\Attributes\Webhook; use ApiPlatform\OpenApi\Model\Operation as OpenApiOperation; +use ApiPlatform\OpenApi\Model\PathItem; use ApiPlatform\State\OptionsInterface; #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] @@ -45,6 +46,7 @@ public function __construct( ?array $paginationViaCursor = null, ?array $hydraContext = null, bool|OpenApiOperation|Webhook|null $openapi = null, + ?PathItem $openApiPathItem = null, ?array $exceptionToStatus = null, ?bool $queryParameterValidationEnabled = null, ?array $links = null, @@ -130,6 +132,7 @@ public function __construct( paginationViaCursor: $paginationViaCursor, hydraContext: $hydraContext, openapi: $openapi, + openApiPathItem: $openApiPathItem, exceptionToStatus: $exceptionToStatus, queryParameterValidationEnabled: $queryParameterValidationEnabled, links: $links, diff --git a/src/Metadata/Put.php b/src/Metadata/Put.php index 5fbfbfd49f..054a9f89cb 100644 --- a/src/Metadata/Put.php +++ b/src/Metadata/Put.php @@ -15,6 +15,7 @@ use ApiPlatform\OpenApi\Attributes\Webhook; use ApiPlatform\OpenApi\Model\Operation as OpenApiOperation; +use ApiPlatform\OpenApi\Model\PathItem; use ApiPlatform\State\OptionsInterface; #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] @@ -45,6 +46,7 @@ public function __construct( ?array $paginationViaCursor = null, ?array $hydraContext = null, bool|OpenApiOperation|Webhook|null $openapi = null, + ?PathItem $openApiPathItem = null, ?array $exceptionToStatus = null, ?bool $queryParameterValidationEnabled = null, ?array $links = null, @@ -130,6 +132,7 @@ public function __construct( paginationViaCursor: $paginationViaCursor, hydraContext: $hydraContext, openapi: $openapi, + openApiPathItem: $openApiPathItem, exceptionToStatus: $exceptionToStatus, queryParameterValidationEnabled: $queryParameterValidationEnabled, links: $links, diff --git a/src/OpenApi/Factory/OpenApiFactory.php b/src/OpenApi/Factory/OpenApiFactory.php index 272feae527..48ae405697 100644 --- a/src/OpenApi/Factory/OpenApiFactory.php +++ b/src/OpenApi/Factory/OpenApiFactory.php @@ -258,6 +258,54 @@ private function collectPaths(ApiResource $resource, ResourceMetadataCollection $pathItem = $paths->getPath($path) ?? new PathItem(); } + if ($openApiPathItem = $operation->getOpenApiPathItem()) { + if ($globalPathSummary = $openApiPathItem->getSummary()) { + $pathItem = $pathItem->withSummary($globalPathSummary); + + foreach (PathItem::$methods as $pathMethod) { + $getMethod = 'get'.ucfirst(strtolower($pathMethod)); + $withMethod = 'with'.ucfirst(strtolower($pathMethod)); + + if (($existingOperation = $pathItem->{$getMethod}()) && $existingOperation instanceof Operation) { + $existingOperationSummary = $existingOperation->getSummary(); + + if ($existingOperationSummary === $this->getPathDescription($resourceShortName, $pathMethod, false) || $existingOperationSummary === $this->getPathDescription($resourceShortName, $pathMethod, true)) { + $pathItem = $pathItem->{$withMethod}($existingOperation->withSummary($globalPathSummary)); + } + } + } + } + + if ($globalPathDescription = $openApiPathItem->getDescription()) { + $pathItem = $pathItem->withDescription($globalPathDescription); + + foreach (PathItem::$methods as $pathMethod) { + $getMethod = 'get'.ucfirst(strtolower($pathMethod)); + $withMethod = 'with'.ucfirst(strtolower($pathMethod)); + + if (($existingOperation = $pathItem->{$getMethod}()) && $existingOperation instanceof Operation) { + $existingOperationDescription = $existingOperation->getDescription(); + + if ($existingOperationDescription === $this->getPathDescription($resourceShortName, $pathMethod, false) || $existingOperationDescription === $this->getPathDescription($resourceShortName, $pathMethod, true)) { + $pathItem = $pathItem->{$withMethod}($existingOperation->withDescription($globalPathDescription)); + } + } + } + } + } + + if ($openapiAttribute instanceof Operation && $openapiSummary = $openapiAttribute->getSummary()) { + $openapiOperation = $openapiOperation->withSummary($openapiSummary); + } elseif ($parentSummary = $pathItem->getSummary()) { + $openapiOperation = $openapiOperation->withSummary($parentSummary); + } + + if ($openapiAttribute instanceof Operation && $openapiDescription = $openapiAttribute->getDescription()) { + $openapiOperation = $openapiOperation->withDescription($openapiDescription); + } elseif ($parentDescription = $pathItem->getDescription()) { + $openapiOperation = $openapiOperation->withDescription($parentDescription); + } + $forceSchemaCollection = $operation instanceof CollectionOperationInterface && 'GET' === $method; $schema = new Schema('openapi'); $schema->setDefinitions($schemas);