Skip to content

Commit 9edb6c7

Browse files
committed
improve
1 parent fff97c9 commit 9edb6c7

File tree

8 files changed

+101
-34
lines changed

8 files changed

+101
-34
lines changed

src/Action/ExceptionAction.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
* @author Baptiste Meyer <[email protected]>
3131
* @author Kévin Dunglas <[email protected]>
3232
*
33-
* @deprecated
33+
* @deprecated since API Platform 3 and Error resource is used {@see ApiPlatform\Symfony\EventListener\ErrorListener}
3434
*/
3535
final class ExceptionAction
3636
{

src/Doctrine/Common/State/PersistProcessor.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,16 +47,14 @@ public function process(mixed $data, Operation $operation, array $uriVariables =
4747
return $data;
4848
}
4949

50-
$request = $context['request'] ?? null;
51-
5250
// PUT: reset the existing object managed by Doctrine and merge data sent by the user in it
5351
// This custom logic is needed because EntityManager::merge() has been deprecated and UPSERT isn't supported:
5452
// https://github.com/doctrine/orm/issues/8461#issuecomment-1250233555
5553
if ($operation instanceof HttpOperation && 'PUT' === $operation->getMethod() && ($operation->getExtraProperties()['standard_put'] ?? false)) {
5654
\assert(method_exists($manager, 'getReference'));
5755
// TODO: the call to getReference is most likely to fail with complex identifiers
5856
$newData = $data;
59-
if ($previousData = $context['previous_data'] ?? $request?->attributes->get('previous_data')) {
57+
if ($previousData = $context['previous_data']) {
6058
$newData = 1 === \count($uriVariables) ? $manager->getReference($class, current($uriVariables)) : clone $previousData;
6159
}
6260

src/Documentation/Action/DocumentationAction.php

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
use ApiPlatform\Util\ContentNegotiationTrait;
2626
use Negotiation\Negotiator;
2727
use Symfony\Component\HttpFoundation\Request;
28+
use Symfony\Component\HttpFoundation\Response;
2829

2930
/**
3031
* Generates the API documentation.
@@ -49,45 +50,59 @@ public function __construct(
4950
}
5051

5152
/**
52-
* @return DocumentationInterface|OpenApi
53+
* @return DocumentationInterface|OpenApi|Response
5354
*/
5455
public function __invoke(Request $request = null)
5556
{
56-
$context = [];
57-
if (null !== $request) {
58-
$isGateway = $request->query->getBoolean(ApiGatewayNormalizer::API_GATEWAY);
59-
$context['api_gateway'] = $isGateway;
60-
$context['base_url'] = $request->getBaseUrl();
61-
$request->attributes->set('_api_normalization_context', $request->attributes->get('_api_normalization_context', []) + $context);
62-
$format = $this->getRequestFormat($request, ['json' => ['application/json'], 'jsonld' => ['application/ld+json'], 'html' => ['text/html']]);
63-
64-
if ('html' === $format || 'json' === $format && null !== $this->openApiFactory) {
65-
if ($this->provider && $this->processor) {
66-
$context['request'] = $request;
67-
$operation = new Get(class: OpenApi::class, provider: fn () => $this->openApiFactory->__invoke($context), normalizationContext: [ApiGatewayNormalizer::API_GATEWAY => $isGateway]);
68-
if ('html' === $format) {
69-
$operation = $operation->withProcessor('api_platform.swagger_ui.processor')->withWrite(true);
70-
}
71-
72-
$body = $this->provider->provide($operation, [], $context);
73-
74-
return $this->processor->process($body, $operation, [], $context);
75-
}
76-
77-
return $this->openApiFactory->__invoke($context);
57+
if (null === $request) {
58+
return new Documentation($this->resourceNameCollectionFactory->create(), $this->title, $this->description, $this->version);
59+
}
60+
61+
$context = ['api_gateway' => $request->query->getBoolean(ApiGatewayNormalizer::API_GATEWAY), 'base_url' => $request->getBaseUrl()];
62+
$request->attributes->set('_api_normalization_context', $request->attributes->get('_api_normalization_context', []) + $context);
63+
$format = $this->getRequestFormat($request, ['json' => ['application/json'], 'jsonld' => ['application/ld+json'], 'html' => ['text/html']]);
64+
65+
if (null !== $this->openApiFactory && ('html' === $format || 'json' === $format)) {
66+
return $this->getOpenApiDocumentation($context, $format, $request);
67+
}
68+
69+
return $this->getHydraDocumentation($context, $request);
70+
}
71+
72+
/**
73+
* @param array<string,mixed> $context
74+
*/
75+
private function getOpenApiDocumentation(array $context, string $format, Request $request): OpenApi|Response
76+
{
77+
if ($this->provider && $this->processor) {
78+
$context['request'] = $request;
79+
$operation = new Get(class: OpenApi::class, provider: fn () => $this->openApiFactory->__invoke($context), normalizationContext: [ApiGatewayNormalizer::API_GATEWAY => $context['api_gateway'] ?? null]);
80+
if ('html' === $format) {
81+
$operation = $operation->withProcessor('api_platform.swagger_ui.processor')->withWrite(true);
7882
}
83+
84+
return $this->processor->process($this->provider->provide($operation, [], $context), $operation, [], $context);
7985
}
8086

87+
return $this->openApiFactory->__invoke($context);
88+
}
89+
90+
/**
91+
* TODO: the logic behind the Hydra Documentation is done in a ApiPlatform\Hydra\Serializer\DocumentationNormalizer.
92+
* We should transform this to a provider, it'd improve performances also by a bit.
93+
*
94+
* @param array<string,mixed> $context
95+
*/
96+
private function getHydraDocumentation(array $context, Request $request): DocumentationInterface|Response
97+
{
8198
if ($this->provider && $this->processor) {
8299
$context['request'] = $request;
83100
$operation = new Get(
84101
class: Documentation::class,
85-
provider: fn () => new Documentation($this->resourceNameCollectionFactory->create(), $this->title, $this->description, $this->version),
86-
normalizationContext: [ApiGatewayNormalizer::API_GATEWAY => $isGateway ?? false]
102+
provider: fn () => new Documentation($this->resourceNameCollectionFactory->create(), $this->title, $this->description, $this->version)
87103
);
88-
$body = $this->provider->provide($operation, [], $context);
89104

90-
return $this->processor->process($body, $operation, [], $context);
105+
return $this->processor->process($this->provider->provide($operation, [], $context), $operation, [], $context);
91106
}
92107

93108
return new Documentation($this->resourceNameCollectionFactory->create(), $this->title, $this->description, $this->version);

src/Symfony/EventListener/ErrorListener.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
/**
3535
* This error listener extends the Symfony one in order to add
3636
* the `_api_operation` attribute when the request is duplicated.
37-
* It will later be used to retrieve the exceptionToStatus from the operation ({@see ExceptionAction}).
37+
* It will later be used to retrieve the exceptionToStatus from the operation ({@see ApiPlatform\Action\ExceptionAction}).
3838
*/
3939
final class ErrorListener extends SymfonyErrorListener
4040
{

tests/Action/EntrypointActionTest.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
use ApiPlatform\Api\Entrypoint;
1818
use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface;
1919
use ApiPlatform\Metadata\Resource\ResourceNameCollection;
20+
use ApiPlatform\State\ProcessorInterface;
21+
use ApiPlatform\State\ProviderInterface;
2022
use PHPUnit\Framework\TestCase;
2123
use Prophecy\PhpUnit\ProphecyTrait;
2224

@@ -34,4 +36,17 @@ public function testGetEntrypoint(): void
3436
$entrypoint = new EntrypointAction($resourceNameCollectionFactoryProphecy->reveal());
3537
$this->assertEquals(new Entrypoint(new ResourceNameCollection(['dummies'])), $entrypoint());
3638
}
39+
40+
public function testGetEntrypointWithProviderProcessor(): void
41+
{
42+
$expected = new Entrypoint(new ResourceNameCollection(['dummies']));
43+
$resourceNameCollectionFactory = $this->createStub(ResourceNameCollectionFactoryInterface::class);
44+
$resourceNameCollectionFactory->method('create')->willReturn(new ResourceNameCollection(['dummies']));
45+
$provider = $this->createMock(ProviderInterface::class);
46+
$provider->expects($this->once())->method('provide')->willReturn($expected);
47+
$processor = $this->createMock(ProcessorInterface::class);
48+
$processor->expects($this->once())->method('process')->willReturnArgument(0);
49+
$entrypoint = new EntrypointAction($resourceNameCollectionFactory, $provider, $processor);
50+
$this->assertEquals($expected, $entrypoint());
51+
}
3752
}

tests/Doctrine/Common/State/PersistProcessorTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,4 +118,6 @@ public function testTrackingPolicy(string $metadataClass, bool $deferredExplicit
118118
$result = (new PersistProcessor($managerRegistryProphecy->reveal()))->process($dummy, new Get());
119119
$this->assertSame($dummy, $result);
120120
}
121+
122+
public function z
121123
}

tests/Documentation/Action/DocumentationActionTest.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
use ApiPlatform\OpenApi\Model\Info;
2222
use ApiPlatform\OpenApi\Model\Paths;
2323
use ApiPlatform\OpenApi\OpenApi;
24+
use ApiPlatform\State\ProcessorInterface;
25+
use ApiPlatform\State\ProviderInterface;
2426
use PHPUnit\Framework\TestCase;
2527
use Prophecy\Argument;
2628
use Prophecy\PhpUnit\ProphecyTrait;
@@ -81,4 +83,39 @@ public function testDocumentationActionWithoutOpenApiFactory(): void
8183
$documentation = new DocumentationAction($resourceNameCollectionFactoryProphecy->reveal(), 'my api', '', '1.0.0');
8284
$this->assertInstanceOf(Documentation::class, $documentation($requestProphecy->reveal()));
8385
}
86+
87+
public static function getOpenApiContentTypes(): array
88+
{
89+
return [['application/json'], ['application/html']];
90+
}
91+
92+
/**
93+
* @dataProvider getOpenApiContentTypes
94+
*/
95+
public function testGetOpenApi($contentType): void
96+
{
97+
$request = new Request(server: ['CONTENT_TYPE' => $contentType]);
98+
$openApiFactory = $this->createMock(OpenApiFactoryInterface::class);
99+
$openApiFactory->expects($this->once())->method('__invoke')->willReturn(new OpenApi(new Info('a', 'v'), [], new Paths()));
100+
$resourceNameCollectionFactory = $this->createStub(ResourceNameCollectionFactoryInterface::class);
101+
$provider = $this->createMock(ProviderInterface::class);
102+
$provider->expects($this->once())->method('provide')->willReturnCallback(fn ($operation, $uriVariables, $context) => $operation->getProvider()(...\func_get_args()));
103+
$processor = $this->createMock(ProcessorInterface::class);
104+
$processor->expects($this->once())->method('process')->willReturnArgument(0);
105+
$entrypoint = new DocumentationAction($resourceNameCollectionFactory, provider: $provider, processor: $processor, openApiFactory: $openApiFactory);
106+
$entrypoint($request);
107+
}
108+
109+
public function testGetHydraDocumentation(): void
110+
{
111+
$request = new Request();
112+
$resourceNameCollectionFactory = $this->createStub(ResourceNameCollectionFactoryInterface::class);
113+
$resourceNameCollectionFactory->expects($this->once())->method('create')->willReturn(new ResourceNameCollection([]));
114+
$provider = $this->createMock(ProviderInterface::class);
115+
$provider->expects($this->once())->method('provide')->willReturnCallback(fn ($operation, $uriVariables, $context) => $operation->getProvider()(...\func_get_args()));
116+
$processor = $this->createMock(ProcessorInterface::class);
117+
$processor->expects($this->once())->method('process')->willReturnArgument(0);
118+
$entrypoint = new DocumentationAction($resourceNameCollectionFactory, provider: $provider, processor: $processor);
119+
$entrypoint($request);
120+
}
84121
}

tests/Fixtures/TestBundle/State/DummyDtoInputOutputProcessor.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,11 @@ public function __construct(private readonly ManagerRegistry $registry)
3333
/**
3434
* {@inheritDoc}
3535
*
36-
* @param InputDto $data
36+
* @param InputDto|InputDtoDocument|mixed $data
3737
*/
3838
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = [])
3939
{
40-
if (!$data instanceof InputDto && !$data instanceof InputDtoDocument) {
40+
if (!($data instanceof InputDto || $data instanceof InputDtoDocument)) {
4141
throw new \RuntimeException('Data is not an InputDto');
4242
}
4343

0 commit comments

Comments
 (0)