Skip to content

Commit

Permalink
Merge 930eecc into 2dc666c
Browse files Browse the repository at this point in the history
  • Loading branch information
vincentchalamon committed Jul 24, 2020
2 parents 2dc666c + 930eecc commit 99bde59
Show file tree
Hide file tree
Showing 16 changed files with 438 additions and 38 deletions.
51 changes: 51 additions & 0 deletions features/doctrine/messenger.feature
@@ -0,0 +1,51 @@
Feature: Combine messenger with doctrine
In order to persist and send a resource
As a client software developer
I need to configure the messenger ApiResource attribute properly

Background:
Given I add "Accept" header equal to "application/ld+json"
And I add "Content-Type" header equal to "application/ld+json"

@createSchema
Scenario: Using messenger="persist" should combine doctrine and messenger
When I send a "POST" request to "/messenger_with_persists" with body:
"""
{
"name": "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/MessengerWithPersist",
"@id": "/messenger_with_persists/1",
"@type": "MessengerWithPersist",
"id": 1,
"name": "test"
}
"""

Scenario: Using messenger={"persist", "input"} should combine doctrine, messenger and input DTO
When I send a "POST" request to "/messenger_with_arrays" 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/MessengerWithArray",
"@id": "/messenger_with_arrays/1",
"@type": "MessengerWithArray",
"id": 1,
"name": "test"
}
"""
Expand Up @@ -16,6 +16,7 @@
use ApiPlatform\Core\DataPersister\ChainDataPersister;
use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface;
use ApiPlatform\Core\DataPersister\DataPersisterInterface;
use ApiPlatform\Core\DataPersister\HandOverDataPersisterInterface;

/**
* @author Anthony GRASSIOT <antograssiot@free.fr>
Expand Down Expand Up @@ -52,8 +53,12 @@ public function supports($data, array $context = []): bool
*/
public function persist($data, array $context = [])
{
if ($match = $this->tracePersisters($data, $context)) {
return $match->persist($data, $context) ?? $data;
foreach ($this->tracePersisters($data, $context) as $match) {
$result = $match->persist($data, $context) ?? $data;

if (!$match instanceof HandOverDataPersisterInterface || !$match->handOver($data, $context)) {
return $result;
}
}
}

Expand All @@ -62,8 +67,12 @@ public function persist($data, array $context = [])
*/
public function remove($data, array $context = [])
{
if ($match = $this->tracePersisters($data, $context)) {
return $match->remove($data, $context);
foreach ($this->tracePersisters($data, $context) as $match) {
$match->remove($data, $context);

if (!$match instanceof HandOverDataPersisterInterface || !$match->handOver($data, $context)) {
return;
}
}
}

Expand All @@ -78,6 +87,10 @@ private function tracePersisters($data, array $context = [])
}
}

return $match;
foreach ($this->persisters as $persister) {
if ($persister->supports($data, $context)) {
yield $persister;
}
}
}
}
56 changes: 40 additions & 16 deletions src/Bridge/Symfony/Messenger/DataPersister.php
Expand Up @@ -15,8 +15,10 @@

use ApiPlatform\Core\Api\OperationType;
use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface;
use ApiPlatform\Core\DataPersister\HandOverDataPersisterInterface;
use ApiPlatform\Core\Exception\ResourceClassNotFoundException;
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
use ApiPlatform\Core\Metadata\Resource\ResourceMetadata;
use ApiPlatform\Core\Util\ClassInfoTrait;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\MessageBusInterface;
Expand All @@ -29,7 +31,7 @@
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
final class DataPersister implements ContextAwareDataPersisterInterface
final class DataPersister implements ContextAwareDataPersisterInterface, HandOverDataPersisterInterface
{
use ClassInfoTrait;
use DispatchTrait;
Expand All @@ -53,21 +55,7 @@ public function supports($data, array $context = []): bool
return false;
}

if (null !== $operationName = $context['collection_operation_name'] ?? $context['item_operation_name'] ?? null) {
return false !== $resourceMetadata->getTypedOperationAttribute(
$context['collection_operation_name'] ?? false ? OperationType::COLLECTION : OperationType::ITEM,
$operationName,
'messenger',
false,
true
);
}

if (isset($context['graphql_operation_name'])) {
return false !== $resourceMetadata->getGraphqlAttribute($context['graphql_operation_name'], 'messenger', false, true);
}

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

/**
Expand Down Expand Up @@ -98,4 +86,40 @@ public function remove($data, array $context = [])
->with(new RemoveStamp())
);
}

/**
* {@inheritdoc}
*/
public function handOver($data, array $context = []): bool
{
try {
$value = $this->getMessengerAttributeValue($this->resourceMetadataFactory->create($context['resource_class'] ?? $this->getObjectClass($data)), $context);
} catch (ResourceClassNotFoundException $exception) {
return false;
}

return 'persist' === $value || (\is_array($value) && (\in_array('persist', $value, true) || (true === $value['persist'] ?? false)));
}

/**
* @return bool|string|array|null
*/
private function getMessengerAttributeValue(ResourceMetadata $resourceMetadata, array $context = [])
{
if (null !== $operationName = $context['collection_operation_name'] ?? $context['item_operation_name'] ?? null) {
return $resourceMetadata->getTypedOperationAttribute(
$context['collection_operation_name'] ?? false ? OperationType::COLLECTION : OperationType::ITEM,
$operationName,
'messenger',
false,
true
);
}

if (isset($context['graphql_operation_name'])) {
return $resourceMetadata->getGraphqlAttribute($context['graphql_operation_name'], 'messenger', false, true);
}

return $resourceMetadata->getAttribute('messenger', false);
}
}
24 changes: 12 additions & 12 deletions src/Bridge/Symfony/Messenger/DataTransformer.php
Expand Up @@ -55,19 +55,19 @@ public function supportsTransformation($data, string $to, array $context = []):
$metadata = $this->resourceMetadataFactory->create($context['resource_class'] ?? $to);

if (isset($context['graphql_operation_name'])) {
return 'input' === $metadata->getGraphqlAttribute($context['graphql_operation_name'], 'messenger', null, true);
$attribute = $metadata->getGraphqlAttribute($context['graphql_operation_name'], 'messenger', null, true);
} elseif (!isset($context['operation_type'])) {
$attribute = $metadata->getAttribute('messenger');
} else {
$attribute = $metadata->getTypedOperationAttribute(
$context['operation_type'],
$context[$context['operation_type'].'_operation_name'] ?? '',
'messenger',
null,
true
);
}

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
);
return 'input' === $attribute || (\is_array($attribute) && \in_array('input', $attribute, true));
}
}
10 changes: 8 additions & 2 deletions src/DataPersister/ChainDataPersister.php
Expand Up @@ -56,7 +56,11 @@ public function persist($data, array $context = [])
{
foreach ($this->persisters as $persister) {
if ($persister->supports($data, $context)) {
return $persister->persist($data, $context) ?? $data;
$result = $persister->persist($data, $context) ?? $data;

if (!$persister instanceof HandOverDataPersisterInterface || !$persister->handOver($data, $context)) {
return $result;
}
}
}
}
Expand All @@ -70,7 +74,9 @@ public function remove($data, array $context = [])
if ($persister->supports($data, $context)) {
$persister->remove($data, $context);

return;
if (!$persister instanceof HandOverDataPersisterInterface || !$persister->handOver($data, $context)) {
return;
}
}
}
}
Expand Down
25 changes: 25 additions & 0 deletions src/DataPersister/HandOverDataPersisterInterface.php
@@ -0,0 +1,25 @@
<?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\DataPersister;

/**
* @author Vincent Chalamon <vincentchalamon@gmail.com>
*/
interface HandOverDataPersisterInterface
{
/**
* Should the chain data persister continue to the next one?
*/
public function handOver($data, array $context = []): bool;
}
Expand Up @@ -16,6 +16,7 @@
use ApiPlatform\Core\Bridge\Symfony\Bundle\DataPersister\TraceableChainDataPersister;
use ApiPlatform\Core\DataPersister\ChainDataPersister;
use ApiPlatform\Core\DataPersister\DataPersisterInterface;
use ApiPlatform\Core\DataPersister\HandOverDataPersisterInterface;
use PHPUnit\Framework\TestCase;

/**
Expand Down Expand Up @@ -74,7 +75,7 @@ public function remove($data)
{
}
},
new class() implements DataPersisterInterface {
new class() implements DataPersisterInterface, HandOverDataPersisterInterface {
public function supports($data): bool
{
return true;
Expand All @@ -87,6 +88,11 @@ public function persist($data)
public function remove($data)
{
}

public function handOver($data, array $context = []): bool
{
return true;
}
},
new class() implements DataPersisterInterface {
public function supports($data): bool
Expand Down
12 changes: 12 additions & 0 deletions tests/Bridge/Symfony/Messenger/DataPersisterTest.php
Expand Up @@ -100,4 +100,16 @@ public function testSupportWithGraphqlContext()
$dataPersister = new DataPersister($metadataFactoryProphecy->reveal(), $this->prophesize(MessageBusInterface::class)->reveal());
$this->assertTrue($dataPersister->supports(new DummyCar(), ['resource_class' => Dummy::class, 'graphql_operation_name' => 'create']));
}

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

$dataPersister = new DataPersister($metadataFactoryProphecy->reveal(), $this->prophesize(MessageBusInterface::class)->reveal());
$this->assertTrue($dataPersister->handOver(new DummyCar(), ['resource_class' => Dummy::class]));
$this->assertFalse($dataPersister->handOver(new DummyCar()));
}
}
26 changes: 26 additions & 0 deletions tests/Bridge/Symfony/Messenger/DataTransformerTest.php
Expand Up @@ -25,6 +25,9 @@
*/
class DataTransformerTest extends TestCase
{
/**
* @dataProvider getMessengerAttribute
*/
public function testSupport()
{
$metadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
Expand All @@ -34,6 +37,9 @@ public function testSupport()
$this->assertTrue($dataTransformer->supportsTransformation([], Dummy::class, ['input' => ['class' => 'smth']]));
}

/**
* @dataProvider getMessengerAttribute
*/
public function testSupportWithinRequest()
{
$metadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
Expand Down Expand Up @@ -61,6 +67,9 @@ public function testNoSupportWithinRequest()
$this->assertFalse($dataTransformer->supportsTransformation([], Dummy::class, ['input' => ['class' => 'smth'], 'operation_type' => OperationType::ITEM, 'item_operation_name' => 'foo']));
}

/**
* @dataProvider getMessengerAttribute
*/
public function testNoSupportWithoutInput()
{
$metadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
Expand All @@ -70,6 +79,9 @@ public function testNoSupportWithoutInput()
$this->assertFalse($dataTransformer->supportsTransformation([], Dummy::class, []));
}

/**
* @dataProvider getMessengerAttribute
*/
public function testNoSupportWithObject()
{
$metadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
Expand All @@ -87,11 +99,25 @@ public function testTransform()
$this->assertSame($dummy, $dataTransformer->transform($dummy, Dummy::class));
}

/**
* @dataProvider getMessengerAttribute
*/
public function testSupportWithGraphqlContext()
{
$metadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
$metadataFactoryProphecy->create(Dummy::class)->willReturn((new ResourceMetadata(null, null, null, null, null, []))->withGraphQl(['create' => ['messenger' => 'input']]));
$dataTransformer = new DataTransformer($metadataFactoryProphecy->reveal());
$this->assertTrue($dataTransformer->supportsTransformation([], Dummy::class, ['input' => ['class' => 'smth'], 'graphql_operation_name' => 'create']));
}

public function getMessengerAttribute()
{
yield [
'input',
];

yield [
['persist', 'input'],
];
}
}

0 comments on commit 99bde59

Please sign in to comment.