Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions features/main/input_output.feature
Original file line number Diff line number Diff line change
Expand Up @@ -245,3 +245,45 @@ Feature: DTO input and output
"""
Then the response status code should be 201
And the response should be empty

@!mongodb
Scenario: Use messenger with an input where the handler gives a synchronous result
When I add "Content-Type" header equal to "application/ld+json"
And I send a "POST" request to "/messenger_with_inputs" with body:
"""
{
"var": "test"
}
"""
Then the response status code should be 201
And the response should be in JSON
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
And the JSON should be equal to:
"""
{
"@context": "/contexts/MessengerWithInput",
"@id": "/messenger_with_inputs/1",
"@type": "MessengerWithInput",
"id": 1,
"name": "test"
}
"""

@!mongodb
Scenario: Use messenger with an input where the handler gives a synchronous Response result
When I add "Content-Type" header equal to "application/ld+json"
And I send a "POST" request to "/messenger_with_responses" with body:
"""
{
"var": "test"
}
"""
Then the response status code should be 200
And the response should be in JSON
And the header "Content-Type" should be equal to "application/json"
And the JSON should be equal to:
"""
{
"data": 123
}
"""
4 changes: 2 additions & 2 deletions src/Annotation/ApiResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
* @Attribute("itemOperations", type="array"),
* @Attribute("maximumItemsPerPage", type="int"),
* @Attribute("mercure", type="mixed"),
* @Attribute("messenger", type="bool"),
* @Attribute("messenger", type="mixed"),
* @Attribute("normalizationContext", type="array"),
* @Attribute("openapiContext", type="array"),
* @Attribute("order", type="array"),
Expand Down Expand Up @@ -196,7 +196,7 @@ final class ApiResource
/**
* @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112
*
* @var bool
* @var bool|string
*/
private $messenger;

Expand Down
6 changes: 6 additions & 0 deletions src/Bridge/Symfony/Bundle/Resources/config/messenger.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@

<tag name="api_platform.data_persister" priority="-900" />
</service>

<service id="api_platform.messenger.data_transformer" class="ApiPlatform\Core\Bridge\Symfony\Messenger\DataTransformer" public="false">
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />

<tag name="api_platform.data_transformer" priority="-10" />
</service>
</services>

</container>
4 changes: 2 additions & 2 deletions src/Bridge/Symfony/Messenger/DataPersister.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public function supports($data, array $context = []): bool
}

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

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

/**
Expand Down
70 changes: 70 additions & 0 deletions src/Bridge/Symfony/Messenger/DataTransformer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Core\Bridge\Symfony\Messenger;

use ApiPlatform\Core\DataTransformer\DataTransformerInterface;
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;

/**
* Transforms an input that implements the InputMessage interface
* to itself. This gives the ability to send the Input to a
* message handler and process it asynchronously.
*
* @author Antoine Bluchet <soyuka@gmail.com>
*/
final class DataTransformer implements DataTransformerInterface
{
private $resourceMetadataFactory;

public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory)
{
$this->resourceMetadataFactory = $resourceMetadataFactory;
}

/**
* {@inheritdoc}
*/
public function transform($object, string $to, array $context = [])
{
return $object;
}

/**
* {@inheritdoc}
*/
public function supportsTransformation($data, string $to, array $context = []): bool
{
if (
\is_object($data) // data is not normalized yet, it should be an array
||
null === ($context['input']['class'] ?? null)
) {
return false;
}

$metadata = $this->resourceMetadataFactory->create($context['resource_class'] ?? $to);

if (!isset($context['operation_type'])) {
return 'input' === $metadata->getAttribute('messenger');
}

return 'input' === $metadata->getTypedOperationAttribute(
$context['operation_type'],
$context[$context['operation_type'].'_operation_name'] ?? '',
'messenger',
null,
true
);
}
}
8 changes: 7 additions & 1 deletion src/EventListener/RespondListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,13 @@ public function onKernelView(GetResponseForControllerResultEvent $event): void
$controllerResult = $event->getControllerResult();
$request = $event->getRequest();

if ($controllerResult instanceof Response || !(($attributes = RequestAttributesExtractor::extractAttributes($request))['respond'] ?? $request->attributes->getBoolean('_api_respond', false))) {
$attributes = RequestAttributesExtractor::extractAttributes($request);
if ($controllerResult instanceof Response && ($attributes['respond'] ?? false)) {
$event->setResponse($controllerResult);

return;
}
if ($controllerResult instanceof Response || !($attributes['respond'] ?? $request->attributes->getBoolean('_api_respond', false))) {
return;
}

Expand Down
2 changes: 1 addition & 1 deletion src/EventListener/WriteListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public function onKernelView(GetResponseForControllerResultEvent $event): void
if (null !== $this->resourceMetadataFactory) {
$resourceMetadata = $this->resourceMetadataFactory->create($attributes['resource_class']);
$outputMetadata = $resourceMetadata->getOperationAttribute($attributes, 'output', ['class' => $attributes['resource_class']], true);
$hasOutput = \array_key_exists('class', $outputMetadata) && null !== $outputMetadata['class'];
$hasOutput = \array_key_exists('class', $outputMetadata) && null !== $outputMetadata['class'] && $controllerResult instanceof $outputMetadata['class'];
}

if ($hasOutput) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,7 @@ public function testDisabledMessenger()
$containerBuilderProphecy = $this->getBaseContainerBuilderProphecy();
$containerBuilderProphecy->setAlias('api_platform.message_bus', 'message_bus')->shouldNotBeCalled();
$containerBuilderProphecy->setDefinition('api_platform.messenger.data_persister', Argument::type(Definition::class))->shouldNotBeCalled();
$containerBuilderProphecy->setDefinition('api_platform.messenger.data_transformer', Argument::type(Definition::class))->shouldNotBeCalled();
$containerBuilder = $containerBuilderProphecy->reveal();

$config = self::DEFAULT_CONFIG;
Expand Down Expand Up @@ -973,6 +974,7 @@ private function getBaseContainerBuilderProphecy()
'api_platform.jsonld.normalizer.item.non_resource',
'api_platform.mercure.listener.response.add_link_header',
'api_platform.messenger.data_persister',
'api_platform.messenger.data_transformer',
'api_platform.metadata.extractor.yaml',
'api_platform.metadata.property.metadata_factory.annotation',
'api_platform.metadata.property.metadata_factory.yaml',
Expand Down
89 changes: 89 additions & 0 deletions tests/Bridge/Symfony/Messenger/DataTransformerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Core\Tests\Bridge\Symfony\Messenger;

use ApiPlatform\Core\Api\OperationType;
use ApiPlatform\Core\Bridge\Symfony\Messenger\DataTransformer;
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
use ApiPlatform\Core\Metadata\Resource\ResourceMetadata;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy;
use PHPUnit\Framework\TestCase;

/**
* @author Antoine Bluchet <soyuka@gmail.com>
*/
class DataTransformerTest extends TestCase
{
public function testSupport()
{
$metadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
$metadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata(null, null, null, null, null, ['messenger' => 'input']));

$dataTransformer = new DataTransformer($metadataFactoryProphecy->reveal());
$this->assertTrue($dataTransformer->supportsTransformation([], Dummy::class, ['input' => ['class' => 'smth']]));
}

public function testSupportWithinRequest()
{
$metadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
$metadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata(null, null, null, ['foo' => ['messenger' => 'input']], null, []));

$dataTransformer = new DataTransformer($metadataFactoryProphecy->reveal());
$this->assertTrue($dataTransformer->supportsTransformation([], Dummy::class, ['input' => ['class' => 'smth'], 'operation_type' => OperationType::ITEM, 'item_operation_name' => 'foo']));
}

public function testNoSupport()
{
$metadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
$metadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata(null, null, null, null, null, ['messenger' => true]));

$dataTransformer = new DataTransformer($metadataFactoryProphecy->reveal());
$this->assertFalse($dataTransformer->supportsTransformation([], Dummy::class, ['input' => ['class' => 'smth']]));
}

public function testNoSupportWithinRequest()
{
$metadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
$metadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata(null, null, null, ['foo' => ['messenger' => true]], null, []));

$dataTransformer = new DataTransformer($metadataFactoryProphecy->reveal());
$this->assertFalse($dataTransformer->supportsTransformation([], Dummy::class, ['input' => ['class' => 'smth'], 'operation_type' => OperationType::ITEM, 'item_operation_name' => 'foo']));
}

public function testNoSupportWithoutInput()
{
$metadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
$metadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata(null, null, null, null, null, ['messenger' => 'input']));

$dataTransformer = new DataTransformer($metadataFactoryProphecy->reveal());
$this->assertFalse($dataTransformer->supportsTransformation([], Dummy::class, []));
}

public function testNoSupportWithObject()
{
$metadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
$metadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata(null, null, null, null, null, ['messenger' => 'input']));

$dataTransformer = new DataTransformer($metadataFactoryProphecy->reveal());
$this->assertFalse($dataTransformer->supportsTransformation(new Dummy(), Dummy::class, []));
}

public function testTransform()
{
$dummy = new Dummy();
$metadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
$dataTransformer = new DataTransformer($metadataFactoryProphecy->reveal());
$this->assertSame($dummy, $dataTransformer->transform($dummy, Dummy::class));
}
}
13 changes: 13 additions & 0 deletions tests/EventListener/RespondListenerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -180,4 +180,17 @@ public function testSetCustomStatus()

$this->assertSame(Response::HTTP_ACCEPTED, $event->getResponse()->getStatusCode());
}

public function testHandleResponse()
{
$request = new Request([], [], ['_api_resource_class' => Dummy::class, '_api_item_operation_name' => 'get', '_api_respond' => true]);
$response = new Response();
$eventProphecy = $this->prophesize(GetResponseForControllerResultEvent::class);
$eventProphecy->getControllerResult()->willReturn($response);
$eventProphecy->getRequest()->willReturn($request);
$eventProphecy->setResponse($response)->shouldBeCalled();

$listener = new RespondListener();
$listener->onKernelView($eventProphecy->reveal());
}
}
22 changes: 22 additions & 0 deletions tests/Fixtures/TestBundle/Dto/MessengerInput.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Dto;

class MessengerInput
{
/**
* @var string
*/
public $var;
}
22 changes: 22 additions & 0 deletions tests/Fixtures/TestBundle/Dto/MessengerResponseInput.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Dto;

class MessengerResponseInput
{
/**
* @var string
*/
public $var;
}
33 changes: 33 additions & 0 deletions tests/Fixtures/TestBundle/Entity/MessengerWithInput.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity;

use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Dto\MessengerInput;

/**
* @ApiResource(messenger="input", input=MessengerInput::class)
*/
class MessengerWithInput
{
/**
* @ApiProperty(identifier=true)
*/
public $id;
/**
* @var string
*/
public $name;
}
Loading