Skip to content

Commit 7ddbbef

Browse files
feat: add ApiResource::openapi and deprecate openapiContext
1 parent 40b637f commit 7ddbbef

File tree

24 files changed

+588
-82
lines changed

24 files changed

+588
-82
lines changed

src/Metadata/ApiResource.php

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
namespace ApiPlatform\Metadata;
1515

1616
use ApiPlatform\Metadata\GraphQl\Operation as GraphQlOperation;
17+
use ApiPlatform\OpenApi\Model\Operation as OpenApiOperation;
1718

1819
/**
1920
* Resource metadata attribute.
@@ -52,6 +53,7 @@ class ApiResource
5253
* @param array|null $denormalizationContext https://api-platform.com/docs/core/serialization/#using-serialization-groups
5354
* @param string[]|null $hydraContext https://api-platform.com/docs/core/extending-jsonld-context/#hydra
5455
* @param array|null $openapiContext https://api-platform.com/docs/core/openapi/#using-the-openapi-and-swagger-contexts
56+
* @param bool|OpenApiOperation|null $openapi https://api-platform.com/docs/core/openapi/#using-the-openapi-and-swagger-contexts
5557
* @param array|null $validationContext https://api-platform.com/docs/core/validation/#using-validation-groups
5658
* @param string[] $filters https://api-platform.com/docs/core/filters/#doctrine-orm-and-mongodb-odm-filters
5759
* @param bool|null $elasticsearch https://api-platform.com/docs/core/elasticsearch/
@@ -110,7 +112,8 @@ public function __construct(
110112
protected ?array $normalizationContext = null,
111113
protected ?array $denormalizationContext = null,
112114
protected ?array $hydraContext = null,
113-
protected ?array $openapiContext = null,
115+
protected ?array $openapiContext = null, // TODO Remove in 4.0
116+
protected bool|OpenApiOperation|null $openapi = null,
114117
protected ?array $validationContext = null,
115118
protected ?array $filters = null,
116119
protected ?bool $elasticsearch = null,
@@ -548,11 +551,21 @@ public function withHydraContext(array $hydraContext): self
548551
return $self;
549552
}
550553

554+
/**
555+
* TODO Remove in 4.0.
556+
*
557+
* @deprecated
558+
*/
551559
public function getOpenapiContext(): ?array
552560
{
553561
return $this->openapiContext;
554562
}
555563

564+
/**
565+
* TODO Remove in 4.0.
566+
*
567+
* @deprecated
568+
*/
556569
public function withOpenapiContext(array $openapiContext): self
557570
{
558571
$self = clone $this;
@@ -561,6 +574,19 @@ public function withOpenapiContext(array $openapiContext): self
561574
return $self;
562575
}
563576

577+
public function getOpenapi(): bool|OpenApiOperation|null
578+
{
579+
return $this->openapi;
580+
}
581+
582+
public function withOpenapi(bool|OpenApiOperation $openapi): self
583+
{
584+
$self = clone $this;
585+
$self->openapi = $openapi;
586+
587+
return $self;
588+
}
589+
564590
public function getValidationContext(): ?array
565591
{
566592
return $this->validationContext;

src/Metadata/Delete.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
namespace ApiPlatform\Metadata;
1515

16+
use ApiPlatform\OpenApi\Model\Operation as OpenApiOperation;
17+
1618
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
1719
final class Delete extends HttpOperation implements DeleteOperationInterface
1820
{
@@ -43,7 +45,7 @@ public function __construct(
4345

4446
?array $hydraContext = null,
4547
?array $openapiContext = null,
46-
?bool $openapi = null,
48+
bool|OpenApiOperation|null $openapi = null,
4749
?array $exceptionToStatus = null,
4850

4951
?bool $queryParameterValidationEnabled = null,

src/Metadata/Extractor/XmlResourceExtractor.php

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121
use ApiPlatform\Metadata\GraphQl\QueryCollection;
2222
use ApiPlatform\Metadata\GraphQl\Subscription;
2323
use ApiPlatform\Metadata\Post;
24+
use ApiPlatform\OpenApi\Model\ExternalDocumentation;
25+
use ApiPlatform\OpenApi\Model\Operation as OpenApiOperation;
26+
use ApiPlatform\OpenApi\Model\Parameter;
27+
use ApiPlatform\OpenApi\Model\RequestBody;
2428
use Symfony\Component\Config\Util\XmlUtils;
2529

2630
/**
@@ -87,7 +91,8 @@ private function buildExtendedBase(\SimpleXMLElement $resource): array
8791
'schemes' => $this->buildArrayValue($resource, 'scheme'),
8892
'cacheHeaders' => $this->buildCacheHeaders($resource),
8993
'hydraContext' => isset($resource->hydraContext->values) ? $this->buildValues($resource->hydraContext->values) : null,
90-
'openapiContext' => isset($resource->openapiContext->values) ? $this->buildValues($resource->openapiContext->values) : null,
94+
'openapiContext' => isset($resource->openapiContext->values) ? $this->buildValues($resource->openapiContext->values) : null, // TODO Remove in 4.0
95+
'openapi' => $this->buildOpenapi($resource),
9196
'paginationViaCursor' => $this->buildPaginationViaCursor($resource),
9297
'exceptionToStatus' => $this->buildExceptionToStatus($resource),
9398
'queryParameterValidationEnabled' => $this->phpize($resource, 'queryParameterValidationEnabled', 'bool'),
@@ -156,6 +161,91 @@ private function buildFormats(\SimpleXMLElement $resource, string $key): ?array
156161
return $data;
157162
}
158163

164+
private function buildOpenapi(\SimpleXMLElement $resource): bool|OpenApiOperation|null
165+
{
166+
if (!isset($resource->openapi) && !isset($resource['openapi'])) {
167+
return null;
168+
}
169+
170+
if (isset($resource['openapi']) && (\is_bool($resource['openapi']) || \in_array((string) $resource['openapi'], ['1', '0', 'true', 'false'], true))) {
171+
return $this->phpize($resource, 'openapi', 'bool');
172+
}
173+
174+
$openapi = $resource->openapi;
175+
$data = [];
176+
$attributes = $openapi->attributes();
177+
foreach ($attributes as $attribute) {
178+
$data[$attribute->getName()] = $this->phpize($attributes, 'deprecated', 'deprecated' === $attribute->getName() ? 'bool' : 'string');
179+
}
180+
181+
$data['tags'] = $this->buildArrayValue($resource, 'tag');
182+
183+
if (isset($openapi->responses->response)) {
184+
foreach ($openapi->responses->response as $response) {
185+
$data['responses'][(string) $response->attributes()->status] = [
186+
'description' => $this->phpize($response, 'description', 'string'),
187+
'content' => isset($response->content->values) ? $this->buildValues($response->content->values) : null,
188+
'headers' => isset($response->headers->values) ? $this->buildValues($response->headers->values) : null,
189+
'links' => isset($response->links->values) ? $this->buildValues($response->links->values) : null,
190+
];
191+
}
192+
}
193+
194+
$data['externalDocs'] = isset($openapi->externalDocs) ? new ExternalDocumentation(
195+
description: $this->phpize($resource, 'description', 'string'),
196+
url: $this->phpize($resource, 'url', 'string'),
197+
) : null;
198+
199+
if (isset($openapi->parameters->parameter)) {
200+
foreach ($openapi->parameters->parameter as $parameter) {
201+
$data['parameters'][(string) $parameter->attributes()->name] = new Parameter(
202+
name: $this->phpize($parameter, 'name', 'string'),
203+
in: $this->phpize($parameter, 'in', 'string'),
204+
description: $this->phpize($parameter, 'description', 'string'),
205+
required: $this->phpize($parameter, 'required', 'bool'),
206+
deprecated: $this->phpize($parameter, 'deprecated', 'bool'),
207+
allowEmptyValue: $this->phpize($parameter, 'allowEmptyValue', 'bool'),
208+
schema: isset($parameter->schema->values) ? $this->buildValues($parameter->schema->values) : null,
209+
style: $this->phpize($parameter, 'style', 'string'),
210+
explode: $this->phpize($parameter, 'explode', 'bool'),
211+
allowReserved: $this->phpize($parameter, 'allowReserved', 'bool'),
212+
example: $this->phpize($parameter, 'example', 'string'),
213+
examples: isset($parameter->examples->values) ? new \ArrayObject($this->buildValues($parameter->examples->values)) : null,
214+
content: isset($parameter->content->values) ? new \ArrayObject($this->buildValues($parameter->content->values)) : null,
215+
);
216+
}
217+
}
218+
$data['requestBody'] = isset($openapi->requestBody) ? new RequestBody(
219+
description: $this->phpize($openapi->requestBody, 'description', 'string'),
220+
content: isset($openapi->requestBody->content->values) ? new \ArrayObject($this->buildValues($openapi->requestBody->values)) : null,
221+
required: $this->phpize($openapi->requestBody, 'required', 'bool'),
222+
) : null;
223+
224+
$data['callbacks'] = isset($openapi->callbacks->values) ? new \ArrayObject($this->buildValues($openapi->callbacks->values)) : null;
225+
226+
$data['security'] = isset($openapi->security->values) ? $this->buildValues($openapi->security->values) : null;
227+
228+
if (isset($openapi->servers->server)) {
229+
foreach ($openapi->servers->server as $server) {
230+
$data['servers'][] = [
231+
'description' => $this->phpize($server, 'description', 'string'),
232+
'url' => $this->phpize($server, 'url', 'string'),
233+
'variables' => isset($server->variables->values) ? $this->buildValues($server->variables->values) : null,
234+
];
235+
}
236+
}
237+
238+
$data['extensionProperties'] = isset($openapi->extensionProperties->values) ? $this->buildValues($openapi->extensionProperties->values) : null;
239+
240+
foreach ($data as $key => $value) {
241+
if (null === $value) {
242+
unset($data[$key]);
243+
}
244+
}
245+
246+
return new OpenApiOperation(...$data);
247+
}
248+
159249
private function buildUriVariables(\SimpleXMLElement $resource): ?array
160250
{
161251
if (!isset($resource->uriVariables->uriVariable)) {
@@ -300,7 +390,6 @@ private function buildOperations(\SimpleXMLElement $resource, array $root): ?arr
300390
}
301391

302392
$data[] = array_merge($datum, [
303-
'openapi' => $this->phpize($operation, 'openapi', 'bool'),
304393
'collection' => $this->phpize($operation, 'collection', 'bool'),
305394
'class' => (string) $operation['class'],
306395
'method' => $this->phpize($operation, 'method', 'string'),

src/Metadata/Extractor/YamlResourceExtractor.php

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
use ApiPlatform\Metadata\GraphQl\QueryCollection;
2222
use ApiPlatform\Metadata\GraphQl\Subscription;
2323
use ApiPlatform\Metadata\Post;
24+
use ApiPlatform\OpenApi\Model\ExternalDocumentation;
25+
use ApiPlatform\OpenApi\Model\Operation as OpenApiOperation;
26+
use ApiPlatform\OpenApi\Model\RequestBody;
2427
use Symfony\Component\Yaml\Exception\ParseException;
2528
use Symfony\Component\Yaml\Yaml;
2629

@@ -106,7 +109,8 @@ private function buildExtendedBase(array $resource): array
106109
'types' => $this->buildArrayValue($resource, 'types'),
107110
'cacheHeaders' => $this->buildArrayValue($resource, 'cacheHeaders'),
108111
'hydraContext' => $this->buildArrayValue($resource, 'hydraContext'),
109-
'openapiContext' => $this->buildArrayValue($resource, 'openapiContext'),
112+
'openapiContext' => $this->buildArrayValue($resource, 'openapiContext'), // TODO Remove in 4.0
113+
'openapi' => $this->buildOpenapi($resource),
110114
'paginationViaCursor' => $this->buildArrayValue($resource, 'paginationViaCursor'),
111115
'exceptionToStatus' => $this->buildArrayValue($resource, 'exceptionToStatus'),
112116
'defaults' => $this->buildArrayValue($resource, 'defaults'),
@@ -205,6 +209,36 @@ private function buildUriVariables(array $resource): ?array
205209
return $uriVariables;
206210
}
207211

212+
private function buildOpenapi(array $resource): bool|OpenApiOperation|null
213+
{
214+
if (!\array_key_exists('openapi', $resource)) {
215+
return null;
216+
}
217+
218+
if (!\is_array($resource['openapi'])) {
219+
return $this->phpize($resource, 'openapi', 'bool');
220+
}
221+
222+
$allowedProperties = array_map(fn (\ReflectionProperty $reflProperty): string => $reflProperty->getName(), (new \ReflectionClass(OpenApiOperation::class))->getProperties());
223+
foreach ($resource['openapi'] as $key => $value) {
224+
$resource['openapi'][$key] = match ($key) {
225+
'externalDocs' => new ExternalDocumentation(description: $value['description'] ?? '', url: $value['url'] ?? ''),
226+
'requestBody' => new RequestBody(description: $value['description'] ?? '', content: isset($value['content']) ? new \ArrayObject($value['content'] ?? []) : null, required: $value['required'] ?? false),
227+
'callbacks' => new \ArrayObject($value ?? []),
228+
default => $value,
229+
};
230+
231+
if (\in_array($key, $allowedProperties, true)) {
232+
continue;
233+
}
234+
235+
$resource['openapi']['extensionProperties'][$key] = $value;
236+
unset($resource['openapi'][$key]);
237+
}
238+
239+
return new OpenApiOperation(...$resource['openapi']);
240+
}
241+
208242
/**
209243
* @return bool|string|string[]|null
210244
*/
@@ -271,7 +305,6 @@ private function buildOperations(array $resource, array $root): ?array
271305
$data[] = array_merge($datum, [
272306
'read' => $this->phpize($operation, 'read', 'bool'),
273307
'deserialize' => $this->phpize($operation, 'deserialize', 'bool'),
274-
'openapi' => $this->phpize($operation, 'openapi', 'bool'),
275308
'validate' => $this->phpize($operation, 'validate', 'bool'),
276309
'write' => $this->phpize($operation, 'write', 'bool'),
277310
'serialize' => $this->phpize($operation, 'serialize', 'bool'),

0 commit comments

Comments
 (0)