Skip to content

Commit 3bc6be5

Browse files
authored
Merge pull request #2628 from soyuka/help-message-input
Add a helper to use Inputs as messages
2 parents 743dbb0 + 1985101 commit 3bc6be5

File tree

17 files changed

+418
-6
lines changed

17 files changed

+418
-6
lines changed

features/main/input_output.feature

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,3 +245,45 @@ Feature: DTO input and output
245245
"""
246246
Then the response status code should be 201
247247
And the response should be empty
248+
249+
@!mongodb
250+
Scenario: Use messenger with an input where the handler gives a synchronous result
251+
When I add "Content-Type" header equal to "application/ld+json"
252+
And I send a "POST" request to "/messenger_with_inputs" with body:
253+
"""
254+
{
255+
"var": "test"
256+
}
257+
"""
258+
Then the response status code should be 201
259+
And the response should be in JSON
260+
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
261+
And the JSON should be equal to:
262+
"""
263+
{
264+
"@context": "/contexts/MessengerWithInput",
265+
"@id": "/messenger_with_inputs/1",
266+
"@type": "MessengerWithInput",
267+
"id": 1,
268+
"name": "test"
269+
}
270+
"""
271+
272+
@!mongodb
273+
Scenario: Use messenger with an input where the handler gives a synchronous Response result
274+
When I add "Content-Type" header equal to "application/ld+json"
275+
And I send a "POST" request to "/messenger_with_responses" with body:
276+
"""
277+
{
278+
"var": "test"
279+
}
280+
"""
281+
Then the response status code should be 200
282+
And the response should be in JSON
283+
And the header "Content-Type" should be equal to "application/json"
284+
And the JSON should be equal to:
285+
"""
286+
{
287+
"data": 123
288+
}
289+
"""

src/Annotation/ApiResource.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
* @Attribute("itemOperations", type="array"),
4444
* @Attribute("maximumItemsPerPage", type="int"),
4545
* @Attribute("mercure", type="mixed"),
46-
* @Attribute("messenger", type="bool"),
46+
* @Attribute("messenger", type="mixed"),
4747
* @Attribute("normalizationContext", type="array"),
4848
* @Attribute("openapiContext", type="array"),
4949
* @Attribute("order", type="array"),
@@ -196,7 +196,7 @@ final class ApiResource
196196
/**
197197
* @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112
198198
*
199-
* @var bool
199+
* @var bool|string
200200
*/
201201
private $messenger;
202202

src/Bridge/Symfony/Bundle/Resources/config/messenger.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@
1313

1414
<tag name="api_platform.data_persister" priority="-900" />
1515
</service>
16+
17+
<service id="api_platform.messenger.data_transformer" class="ApiPlatform\Core\Bridge\Symfony\Messenger\DataTransformer" public="false">
18+
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
19+
20+
<tag name="api_platform.data_transformer" priority="-10" />
21+
</service>
1622
</services>
1723

1824
</container>

src/Bridge/Symfony/Messenger/DataPersister.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public function supports($data, array $context = []): bool
5454
}
5555

5656
if (null !== $operationName = $context['collection_operation_name'] ?? $context['item_operation_name'] ?? null) {
57-
return true === $resourceMetadata->getTypedOperationAttribute(
57+
return false !== $resourceMetadata->getTypedOperationAttribute(
5858
$context['collection_operation_name'] ?? false ? OperationType::COLLECTION : OperationType::ITEM,
5959
$operationName,
6060
'messenger',
@@ -63,7 +63,7 @@ public function supports($data, array $context = []): bool
6363
);
6464
}
6565

66-
return true === $resourceMetadata->getAttribute('messenger');
66+
return false !== $resourceMetadata->getAttribute('messenger', false);
6767
}
6868

6969
/**
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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\Core\Bridge\Symfony\Messenger;
15+
16+
use ApiPlatform\Core\DataTransformer\DataTransformerInterface;
17+
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
18+
19+
/**
20+
* Transforms an input that implements the InputMessage interface
21+
* to itself. This gives the ability to send the Input to a
22+
* message handler and process it asynchronously.
23+
*
24+
* @author Antoine Bluchet <[email protected]>
25+
*/
26+
final class DataTransformer implements DataTransformerInterface
27+
{
28+
private $resourceMetadataFactory;
29+
30+
public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory)
31+
{
32+
$this->resourceMetadataFactory = $resourceMetadataFactory;
33+
}
34+
35+
/**
36+
* {@inheritdoc}
37+
*/
38+
public function transform($object, string $to, array $context = [])
39+
{
40+
return $object;
41+
}
42+
43+
/**
44+
* {@inheritdoc}
45+
*/
46+
public function supportsTransformation($data, string $to, array $context = []): bool
47+
{
48+
if (
49+
\is_object($data) // data is not normalized yet, it should be an array
50+
||
51+
null === ($context['input']['class'] ?? null)
52+
) {
53+
return false;
54+
}
55+
56+
$metadata = $this->resourceMetadataFactory->create($context['resource_class'] ?? $to);
57+
58+
if (!isset($context['operation_type'])) {
59+
return 'input' === $metadata->getAttribute('messenger');
60+
}
61+
62+
return 'input' === $metadata->getTypedOperationAttribute(
63+
$context['operation_type'],
64+
$context[$context['operation_type'].'_operation_name'] ?? '',
65+
'messenger',
66+
null,
67+
true
68+
);
69+
}
70+
}

src/EventListener/RespondListener.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,13 @@ public function onKernelView(GetResponseForControllerResultEvent $event): void
4545
$controllerResult = $event->getControllerResult();
4646
$request = $event->getRequest();
4747

48-
if ($controllerResult instanceof Response || !(($attributes = RequestAttributesExtractor::extractAttributes($request))['respond'] ?? $request->attributes->getBoolean('_api_respond', false))) {
48+
$attributes = RequestAttributesExtractor::extractAttributes($request);
49+
if ($controllerResult instanceof Response && ($attributes['respond'] ?? false)) {
50+
$event->setResponse($controllerResult);
51+
52+
return;
53+
}
54+
if ($controllerResult instanceof Response || !($attributes['respond'] ?? $request->attributes->getBoolean('_api_respond', false))) {
4955
return;
5056
}
5157

src/EventListener/WriteListener.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public function onKernelView(GetResponseForControllerResultEvent $event): void
7373
if (null !== $this->resourceMetadataFactory) {
7474
$resourceMetadata = $this->resourceMetadataFactory->create($attributes['resource_class']);
7575
$outputMetadata = $resourceMetadata->getOperationAttribute($attributes, 'output', ['class' => $attributes['resource_class']], true);
76-
$hasOutput = \array_key_exists('class', $outputMetadata) && null !== $outputMetadata['class'];
76+
$hasOutput = \array_key_exists('class', $outputMetadata) && null !== $outputMetadata['class'] && $controllerResult instanceof $outputMetadata['class'];
7777
}
7878

7979
if ($hasOutput) {

tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,7 @@ public function testDisabledMessenger()
460460
$containerBuilderProphecy = $this->getBaseContainerBuilderProphecy();
461461
$containerBuilderProphecy->setAlias('api_platform.message_bus', 'message_bus')->shouldNotBeCalled();
462462
$containerBuilderProphecy->setDefinition('api_platform.messenger.data_persister', Argument::type(Definition::class))->shouldNotBeCalled();
463+
$containerBuilderProphecy->setDefinition('api_platform.messenger.data_transformer', Argument::type(Definition::class))->shouldNotBeCalled();
463464
$containerBuilder = $containerBuilderProphecy->reveal();
464465

465466
$config = self::DEFAULT_CONFIG;
@@ -973,6 +974,7 @@ private function getBaseContainerBuilderProphecy()
973974
'api_platform.jsonld.normalizer.item.non_resource',
974975
'api_platform.mercure.listener.response.add_link_header',
975976
'api_platform.messenger.data_persister',
977+
'api_platform.messenger.data_transformer',
976978
'api_platform.metadata.extractor.yaml',
977979
'api_platform.metadata.property.metadata_factory.annotation',
978980
'api_platform.metadata.property.metadata_factory.yaml',
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
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\Core\Tests\Bridge\Symfony\Messenger;
15+
16+
use ApiPlatform\Core\Api\OperationType;
17+
use ApiPlatform\Core\Bridge\Symfony\Messenger\DataTransformer;
18+
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
19+
use ApiPlatform\Core\Metadata\Resource\ResourceMetadata;
20+
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy;
21+
use PHPUnit\Framework\TestCase;
22+
23+
/**
24+
* @author Antoine Bluchet <[email protected]>
25+
*/
26+
class DataTransformerTest extends TestCase
27+
{
28+
public function testSupport()
29+
{
30+
$metadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
31+
$metadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata(null, null, null, null, null, ['messenger' => 'input']));
32+
33+
$dataTransformer = new DataTransformer($metadataFactoryProphecy->reveal());
34+
$this->assertTrue($dataTransformer->supportsTransformation([], Dummy::class, ['input' => ['class' => 'smth']]));
35+
}
36+
37+
public function testSupportWithinRequest()
38+
{
39+
$metadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
40+
$metadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata(null, null, null, ['foo' => ['messenger' => 'input']], null, []));
41+
42+
$dataTransformer = new DataTransformer($metadataFactoryProphecy->reveal());
43+
$this->assertTrue($dataTransformer->supportsTransformation([], Dummy::class, ['input' => ['class' => 'smth'], 'operation_type' => OperationType::ITEM, 'item_operation_name' => 'foo']));
44+
}
45+
46+
public function testNoSupport()
47+
{
48+
$metadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
49+
$metadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata(null, null, null, null, null, ['messenger' => true]));
50+
51+
$dataTransformer = new DataTransformer($metadataFactoryProphecy->reveal());
52+
$this->assertFalse($dataTransformer->supportsTransformation([], Dummy::class, ['input' => ['class' => 'smth']]));
53+
}
54+
55+
public function testNoSupportWithinRequest()
56+
{
57+
$metadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
58+
$metadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata(null, null, null, ['foo' => ['messenger' => true]], null, []));
59+
60+
$dataTransformer = new DataTransformer($metadataFactoryProphecy->reveal());
61+
$this->assertFalse($dataTransformer->supportsTransformation([], Dummy::class, ['input' => ['class' => 'smth'], 'operation_type' => OperationType::ITEM, 'item_operation_name' => 'foo']));
62+
}
63+
64+
public function testNoSupportWithoutInput()
65+
{
66+
$metadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
67+
$metadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata(null, null, null, null, null, ['messenger' => 'input']));
68+
69+
$dataTransformer = new DataTransformer($metadataFactoryProphecy->reveal());
70+
$this->assertFalse($dataTransformer->supportsTransformation([], Dummy::class, []));
71+
}
72+
73+
public function testNoSupportWithObject()
74+
{
75+
$metadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
76+
$metadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata(null, null, null, null, null, ['messenger' => 'input']));
77+
78+
$dataTransformer = new DataTransformer($metadataFactoryProphecy->reveal());
79+
$this->assertFalse($dataTransformer->supportsTransformation(new Dummy(), Dummy::class, []));
80+
}
81+
82+
public function testTransform()
83+
{
84+
$dummy = new Dummy();
85+
$metadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
86+
$dataTransformer = new DataTransformer($metadataFactoryProphecy->reveal());
87+
$this->assertSame($dummy, $dataTransformer->transform($dummy, Dummy::class));
88+
}
89+
}

tests/EventListener/RespondListenerTest.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,4 +180,17 @@ public function testSetCustomStatus()
180180

181181
$this->assertSame(Response::HTTP_ACCEPTED, $event->getResponse()->getStatusCode());
182182
}
183+
184+
public function testHandleResponse()
185+
{
186+
$request = new Request([], [], ['_api_resource_class' => Dummy::class, '_api_item_operation_name' => 'get', '_api_respond' => true]);
187+
$response = new Response();
188+
$eventProphecy = $this->prophesize(GetResponseForControllerResultEvent::class);
189+
$eventProphecy->getControllerResult()->willReturn($response);
190+
$eventProphecy->getRequest()->willReturn($request);
191+
$eventProphecy->setResponse($response)->shouldBeCalled();
192+
193+
$listener = new RespondListener();
194+
$listener->onKernelView($eventProphecy->reveal());
195+
}
183196
}

0 commit comments

Comments
 (0)