Skip to content

Commit 55f27dc

Browse files
authored
fix(symfony): documentation request _format (#6390)
fixes #6372
1 parent 2f4ecc8 commit 55f27dc

File tree

13 files changed

+131
-19
lines changed

13 files changed

+131
-19
lines changed

features/openapi/docs.feature

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,3 +432,9 @@ Feature: Documentation support
432432
]
433433
"""
434434
And the JSON node "components.schemas.DummyBoolean.properties.isDummyBoolean.owl:maxCardinality" should not exist
435+
436+
Scenario: Retrieve the OpenAPI documentation in JSON
437+
Given I add "Accept" header equal to "text/html,*/*;q=0.8"
438+
And I send a "GET" request to "/docs.jsonopenapi"
439+
Then the response status code should be 200
440+
And the response should be in JSON

src/Metadata/Util/ContentNegotiationTrait.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ private function getRequestFormat(Request $request, array $formats, bool $throw
106106
}
107107
}
108108

109-
// Then use the Symfony request format if available and applicable
109+
// Then, use the Symfony request format if available and applicable
110110
$requestFormat = $request->getRequestFormat('') ?: null;
111111
if (null !== $requestFormat) {
112112
$mimeType = $request->getMimeType($requestFormat);
@@ -135,4 +135,18 @@ private function getNotAcceptableHttpException(string $accept, array $mimeTypes)
135135
implode('", "', array_keys($mimeTypes))
136136
));
137137
}
138+
139+
/**
140+
* Adds the supported formats to the request.
141+
*
142+
* This is necessary for {@see Request::getMimeType} and {@see Request::getMimeTypes} to work.
143+
*
144+
* @param array<string, string|string[]> $formats
145+
*/
146+
private function addRequestFormats(Request $request, array $formats): void
147+
{
148+
foreach ($formats as $format => $mimeTypes) {
149+
$request->setFormat($format, (array) $mimeTypes);
150+
}
151+
}
138152
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
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\OpenApi\Serializer;
15+
16+
use ApiPlatform\State\SerializerContextBuilderInterface;
17+
use Symfony\Component\HttpFoundation\Request;
18+
19+
/**
20+
* @internal
21+
*/
22+
final class SerializerContextBuilder implements SerializerContextBuilderInterface
23+
{
24+
public function __construct(private readonly SerializerContextBuilderInterface $decorated)
25+
{
26+
}
27+
28+
public function createFromRequest(Request $request, bool $normalization, ?array $extractedAttributes = null): array
29+
{
30+
$context = $this->decorated->createFromRequest($request, $normalization, $extractedAttributes);
31+
32+
return $context + [
33+
'api_gateway' => $request->query->getBoolean(ApiGatewayNormalizer::API_GATEWAY),
34+
'base_url' => $request->getBaseUrl(),
35+
'spec_version' => (string) $request->query->get(LegacyOpenApiNormalizer::SPEC_VERSION),
36+
];
37+
}
38+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
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\OpenApi\State;
15+
16+
use ApiPlatform\Metadata\Operation;
17+
use ApiPlatform\OpenApi\Factory\OpenApiFactoryInterface;
18+
use ApiPlatform\OpenApi\OpenApi;
19+
use ApiPlatform\State\ProviderInterface;
20+
21+
/**
22+
* @internal
23+
*/
24+
final class OpenApiProvider implements ProviderInterface
25+
{
26+
public function __construct(private readonly OpenApiFactoryInterface $openApiFactory)
27+
{
28+
}
29+
30+
public function provide(Operation $operation, array $uriVariables = [], array $context = []): OpenApi
31+
{
32+
return $this->openApiFactory->__invoke($context);
33+
}
34+
}

src/State/Provider/DeserializeProvider.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@
1515

1616
use ApiPlatform\Metadata\HttpOperation;
1717
use ApiPlatform\Metadata\Operation;
18-
use ApiPlatform\Serializer\SerializerContextBuilderInterface;
18+
use ApiPlatform\Serializer\SerializerContextBuilderInterface as LegacySerializerContextBuilderInterface;
1919
use ApiPlatform\State\ProviderInterface;
20+
use ApiPlatform\State\SerializerContextBuilderInterface;
2021
use ApiPlatform\Validator\Exception\ValidationException;
2122
use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
2223
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
@@ -32,8 +33,12 @@
3233

3334
final class DeserializeProvider implements ProviderInterface
3435
{
35-
public function __construct(private readonly ?ProviderInterface $decorated, private readonly SerializerInterface $serializer, private readonly SerializerContextBuilderInterface $serializerContextBuilder, private ?TranslatorInterface $translator = null)
36-
{
36+
public function __construct(
37+
private readonly ?ProviderInterface $decorated,
38+
private readonly SerializerInterface $serializer,
39+
private readonly LegacySerializerContextBuilderInterface|SerializerContextBuilderInterface $serializerContextBuilder,
40+
private ?TranslatorInterface $translator = null
41+
) {
3742
if (null === $this->translator) {
3843
$this->translator = new class() implements TranslatorInterface, LocaleAwareInterface {
3944
use TranslatorTrait;

src/State/Provider/ReadProvider.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@
1717
use ApiPlatform\Metadata\Operation;
1818
use ApiPlatform\Metadata\Put;
1919
use ApiPlatform\Metadata\Util\CloneTrait;
20-
use ApiPlatform\Serializer\SerializerContextBuilderInterface;
20+
use ApiPlatform\Serializer\SerializerContextBuilderInterface as LegacySerializerContextBuilderInterface;
2121
use ApiPlatform\State\Exception\ProviderNotFoundException;
2222
use ApiPlatform\State\ProviderInterface;
23+
use ApiPlatform\State\SerializerContextBuilderInterface;
2324
use ApiPlatform\State\UriVariablesResolverTrait;
2425
use ApiPlatform\State\Util\OperationRequestInitiatorTrait;
2526
use ApiPlatform\State\Util\RequestParser;
@@ -38,7 +39,7 @@ final class ReadProvider implements ProviderInterface
3839

3940
public function __construct(
4041
private readonly ProviderInterface $provider,
41-
private readonly ?SerializerContextBuilderInterface $serializerContextBuilder = null,
42+
private readonly LegacySerializerContextBuilderInterface|SerializerContextBuilderInterface|null $serializerContextBuilder = null,
4243
) {
4344
}
4445

src/State/Tests/Provider/ReadProviderTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414
namespace ApiPlatform\State\Tests\Provider;
1515

1616
use ApiPlatform\Metadata\Get;
17-
use ApiPlatform\Serializer\SerializerContextBuilderInterface;
1817
use ApiPlatform\State\Provider\ReadProvider;
1918
use ApiPlatform\State\ProviderInterface;
19+
use ApiPlatform\State\SerializerContextBuilderInterface;
2020
use PHPUnit\Framework\TestCase;
2121
use Symfony\Component\HttpFoundation\Request;
2222

src/Symfony/Action/DocumentationAction.php

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ public function __invoke(?Request $request = null)
6767
'spec_version' => (string) $request->query->get(LegacyOpenApiNormalizer::SPEC_VERSION),
6868
];
6969
$request->attributes->set('_api_normalization_context', $request->attributes->get('_api_normalization_context', []) + $context);
70+
$this->addRequestFormats($request, $this->documentationFormats);
7071
$format = $this->getRequestFormat($request, $this->documentationFormats);
7172

7273
if (null !== $this->openApiFactory && ('html' === $format || OpenApiNormalizer::FORMAT === $format || OpenApiNormalizer::JSON_FORMAT === $format || OpenApiNormalizer::YAML_FORMAT === $format)) {
@@ -87,11 +88,7 @@ private function getOpenApiDocumentation(array $context, string $format, Request
8788
class: OpenApi::class,
8889
read: true,
8990
serialize: true,
90-
provider: fn () => $this->openApiFactory->__invoke($context),
91-
normalizationContext: [
92-
ApiGatewayNormalizer::API_GATEWAY => $context['api_gateway'] ?? null,
93-
LegacyOpenApiNormalizer::SPEC_VERSION => $context['spec_version'] ?? null,
94-
],
91+
provider: 'api_platform.openapi.provider',
9592
outputFormats: $this->documentationFormats
9693
);
9794

src/Symfony/Bundle/Resources/config/openapi.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,17 @@
2929
</service>
3030
<service id="ApiPlatform\OpenApi\Serializer\OpenApiNormalizer" alias="api_platform.openapi.normalizer" />
3131

32+
<service id="api_platform.openapi.provider" class="ApiPlatform\OpenApi\State\OpenApiProvider" public="false">
33+
<argument type="service" id="api_platform.openapi.factory" />
34+
35+
<tag name="api_platform.state_provider" priority="-100" key="ApiPlatform\OpenApi\State\OpenApiProvider" />
36+
<tag name="api_platform.state_provider" priority="-100" key="api_platform.openapi.provider" />
37+
</service>
38+
39+
<service id="api_platform.openapi.serializer_context_builder" class="ApiPlatform\OpenApi\Serializer\SerializerContextBuilder" decorates="api_platform.serializer.context_builder" public="false">
40+
<argument type="service" id="api_platform.openapi.serializer_context_builder.inner" />
41+
</service>
42+
3243
<service id="api_platform.openapi.options" class="ApiPlatform\OpenApi\Options">
3344
<argument>%api_platform.title%</argument>
3445
<argument>%api_platform.description%</argument>

src/Symfony/EventListener/DeserializeListener.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@
1616
use ApiPlatform\Api\FormatMatcher;
1717
use ApiPlatform\Metadata\HttpOperation;
1818
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
19-
use ApiPlatform\Serializer\SerializerContextBuilderInterface;
19+
use ApiPlatform\Serializer\SerializerContextBuilderInterface as LegacySerializerContextBuilderInterface;
2020
use ApiPlatform\State\ProviderInterface;
21+
use ApiPlatform\State\SerializerContextBuilderInterface;
2122
use ApiPlatform\State\Util\OperationRequestInitiatorTrait;
2223
use ApiPlatform\Symfony\Util\RequestAttributesExtractor;
2324
use ApiPlatform\Symfony\Validator\Exception\ValidationException;
@@ -48,8 +49,12 @@ final class DeserializeListener
4849
private SerializerInterface $serializer;
4950
private ?ProviderInterface $provider = null;
5051

51-
public function __construct(ProviderInterface|SerializerInterface $serializer, private readonly SerializerContextBuilderInterface|ResourceMetadataCollectionFactoryInterface|null $serializerContextBuilder = null, ?ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory = null, private ?TranslatorInterface $translator = null)
52-
{
52+
public function __construct(
53+
ProviderInterface|SerializerInterface $serializer,
54+
private readonly LegacySerializerContextBuilderInterface|SerializerContextBuilderInterface|ResourceMetadataCollectionFactoryInterface|null $serializerContextBuilder = null,
55+
?ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory = null,
56+
private ?TranslatorInterface $translator = null
57+
) {
5358
if ($serializer instanceof ProviderInterface) {
5459
$this->provider = $serializer;
5560
} else {

0 commit comments

Comments
 (0)