Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revert messenger=persist and introduce a ResumableDataPersisterInterface #3912

Merged
merged 3 commits into from Dec 19, 2020
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
51 changes: 0 additions & 51 deletions features/doctrine/messenger.feature

This file was deleted.

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\ResumableDataPersisterInterface;

/**
* @author Anthony GRASSIOT <antograssiot@free.fr>
Expand Down Expand Up @@ -52,32 +53,32 @@ 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;
}
$this->tracePersisters($data, $context);

return $this->decorated->persist($data, $context);
}

/**
* {@inheritdoc}
*/
public function remove($data, array $context = [])
{
if ($match = $this->tracePersisters($data, $context)) {
return $match->remove($data, $context);
}
$this->tracePersisters($data, $context);

return $this->decorated->remove($data, $context);
}

private function tracePersisters($data, array $context = [])
{
$match = null;
$found = false;
foreach ($this->persisters as $persister) {
$this->persistersResponse[\get_class($persister)] = $match ? null : false;
if (!$match && $persister->supports($data, $context)) {
$match = $persister;
$this->persistersResponse[\get_class($persister)] = true;
if (
($this->persistersResponse[\get_class($persister)] = $found ? false : $persister->supports($data))
&&
!($persister instanceof ResumableDataPersisterInterface && $persister->resumable()) && !$found
) {
$found = true;
}
}

return $match;
}
}
1 change: 0 additions & 1 deletion src/Bridge/Symfony/Bundle/Resources/config/messenger.xml
Expand Up @@ -10,7 +10,6 @@
<service id="api_platform.messenger.data_persister" class="ApiPlatform\Core\Bridge\Symfony\Messenger\DataPersister" public="false">
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
<argument type="service" id="api_platform.message_bus" />
<argument type="service" id="api_platform.data_persister" />

<tag name="api_platform.data_persister" priority="-900" />
</service>
Expand Down
69 changes: 16 additions & 53 deletions src/Bridge/Symfony/Messenger/DataPersister.php
Expand Up @@ -17,7 +17,6 @@
use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface;
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 @@ -36,42 +35,46 @@ final class DataPersister implements ContextAwareDataPersisterInterface
use DispatchTrait;

private $resourceMetadataFactory;
private $dataPersister;

public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, MessageBusInterface $messageBus, ContextAwareDataPersisterInterface $dataPersister)
public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, MessageBusInterface $messageBus)
{
$this->resourceMetadataFactory = $resourceMetadataFactory;
$this->messageBus = $messageBus;
$this->dataPersister = $dataPersister;
}

/**
* {@inheritdoc}
*/
public function supports($data, array $context = []): bool
{
if (true === ($context['messenger_dispatched'] ?? false)) {
return false;
}

try {
$resourceMetadata = $this->resourceMetadataFactory->create($context['resource_class'] ?? $this->getObjectClass($data));
} catch (ResourceClassNotFoundException $e) {
return false;
}

return false !== $this->getMessengerAttributeValue($resourceMetadata, $context);
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);
}

/**
* {@inheritdoc}
*/
public function persist($data, array $context = [])
{
if ($this->handOver($data, $context)) {
$data = $this->dataPersister->persist($data, $context + ['messenger_dispatched' => true]);
}

$envelope = $this->dispatch(
(new Envelope($data))
->with(new ContextStamp($context))
Expand All @@ -90,49 +93,9 @@ public function persist($data, array $context = [])
*/
public function remove($data, array $context = [])
{
if ($this->handOver($data, $context)) {
$this->dataPersister->remove($data, $context + ['messenger_dispatched' => true]);
}

$this->dispatch(
(new Envelope($data))
->with(new RemoveStamp())
);
}

/**
* Should this DataPersister hand over in "persist" mode?
*/
private 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'])) {
$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
);
return 'input' === $metadata->getGraphqlAttribute($context['graphql_operation_name'], 'messenger', null, true);
}

return 'input' === $attribute || (\is_array($attribute) && \in_array('input', $attribute, 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
);
}
}
12 changes: 11 additions & 1 deletion src/DataPersister/ChainDataPersister.php
Expand Up @@ -56,9 +56,16 @@ public function persist($data, array $context = [])
{
foreach ($this->persisters as $persister) {
if ($persister->supports($data, $context)) {
return $persister->persist($data, $context) ?? $data;
$data = $persister->persist($data, $context) ?? $data;
if ($persister instanceof ResumableDataPersisterInterface && $persister->resumable($context)) {
continue;
}

return $data;
}
}

return $data;
}

/**
Expand All @@ -69,6 +76,9 @@ public function remove($data, array $context = [])
foreach ($this->persisters as $persister) {
if ($persister->supports($data, $context)) {
$persister->remove($data, $context);
if ($persister instanceof ResumableDataPersisterInterface && $persister->resumable($context)) {
continue;
}

return;
}
Expand Down
26 changes: 26 additions & 0 deletions src/DataPersister/ResumableDataPersisterInterface.php
@@ -0,0 +1,26 @@
<?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;

/**
* Control the resumability of the data persister chain.
*/
interface ResumableDataPersisterInterface
{
/**
* Should we continue calling the next DataPersister or stop after this one?
* Defaults to stop the ChainDatapersister if this interface is not implemented.
*/
public function resumable(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\ResumableDataPersisterInterface;
use PHPUnit\Framework\TestCase;

/**
Expand Down Expand Up @@ -111,7 +112,60 @@ public function remove($data)
}
},
]),
[false, true, null],
[false, true, false],
];

yield [
new ChainDataPersister([
new class() implements DataPersisterInterface, ResumableDataPersisterInterface {
public function supports($data): bool
{
return true;
}

public function resumable(array $context = []): bool
{
return true;
}

public function persist($data)
{
}

public function remove($data)
{
}
},
new class() implements DataPersisterInterface {
public function supports($data): bool
{
return false;
}

public function persist($data)
{
}

public function remove($data)
{
}
},
new class() implements DataPersisterInterface {
public function supports($data): bool
{
return true;
}

public function persist($data)
{
}

public function remove($data)
{
}
},
]),
[true, false, true],
];
}
}