[API] Decorate base write state processor to catch messenger exceptions

This commit is contained in:
Rafikooo 2024-08-20 13:16:49 +02:00 committed by Grzegorz Sadowski
parent b9d2bac821
commit 517affc078
7 changed files with 92 additions and 129 deletions

View file

@ -575,31 +575,6 @@ parameters:
count: 1
path: src/Sylius/Bundle/ApiBundle/DataPersister/CountryDataPersister.php
-
message: "#^Method Sylius\\\\Bundle\\\\ApiBundle\\\\DataPersister\\\\MessengerDataPersister\\:\\:persist\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#"
count: 1
path: src/Sylius/Bundle/ApiBundle/DataPersister/MessengerDataPersister.php
-
message: "#^Method Sylius\\\\Bundle\\\\ApiBundle\\\\DataPersister\\\\MessengerDataPersister\\:\\:remove\\(\\) has no return type specified\\.$#"
count: 1
path: src/Sylius/Bundle/ApiBundle/DataPersister/MessengerDataPersister.php
-
message: "#^Method Sylius\\\\Bundle\\\\ApiBundle\\\\DataPersister\\\\MessengerDataPersister\\:\\:remove\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#"
count: 1
path: src/Sylius/Bundle/ApiBundle/DataPersister/MessengerDataPersister.php
-
message: "#^Method Sylius\\\\Bundle\\\\ApiBundle\\\\DataPersister\\\\MessengerDataPersister\\:\\:supports\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#"
count: 1
path: src/Sylius/Bundle/ApiBundle/DataPersister/MessengerDataPersister.php
-
message: "#^Method Sylius\\\\Bundle\\\\ApiBundle\\\\DataPersister\\\\MessengerDataPersister\\:\\:supports\\(\\) has parameter \\$data with no type specified\\.$#"
count: 1
path: src/Sylius/Bundle/ApiBundle/DataPersister/MessengerDataPersister.php
-
message: "#^Method Sylius\\\\Bundle\\\\ApiBundle\\\\DataPersister\\\\ProductDataPersister\\:\\:persist\\(\\) has parameter \\$data with no type specified\\.$#"
count: 1

View file

@ -18,11 +18,6 @@
<services>
<defaults public="true" />
<service id="Sylius\Bundle\ApiBundle\DataPersister\MessengerDataPersister">
<argument type="service" id="api_platform.messenger.data_persister" />
<tag name="api_platform.data_persister" />
</service>
<service id="Sylius\Bundle\ApiBundle\DataPersister\CountryDataPersister">
<argument type="service" id="api_platform.doctrine.orm.data_persister" />
<argument type="service" id="Sylius\Component\Addressing\Checker\CountryProvincesDeletionCheckerInterface" />

View file

@ -112,5 +112,14 @@
<argument type="service" id="sylius_api.creator.taxon_image" />
<tag name="api_platform.state_processor" />
</service>
<service
id="sylius_api.state_processor.shop.messenger.persist"
class="Sylius\Bundle\ApiBundle\StateProcessor\Common\MessengerPersistProcessor"
decorates="api_platform.state_processor.write"
>
<argument type="service" id=".inner" />
<tag name="api_platform.state_processor" />
</service>
</services>
</container>

View file

@ -19,19 +19,19 @@ use ApiPlatform\State\ProcessorInterface;
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
use Sylius\Bundle\ApiBundle\Exception\PaymentMethodCannotBeRemoved;
use Sylius\Component\Core\Model\PaymentMethodInterface;
use Sylius\Component\Core\Model\ProductInterface;
use Webmozart\Assert\Assert;
/** @implements ProcessorInterface<PaymentMethodInterface> */
/** @implements ProcessorInterface<PaymentMethodInterface, void> */
final readonly class RemoveProcessor implements ProcessorInterface
{
/** @param ProcessorInterface<PaymentMethodInterface, void> $removeProcessor */
public function __construct(
private ProcessorInterface $removeProcessor,
) {
}
/**
* @param ProductInterface $data
* @param PaymentMethodInterface $data
*/
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): void
{

View file

@ -11,27 +11,26 @@
declare(strict_types=1);
namespace Sylius\Bundle\ApiBundle\DataPersister;
namespace Sylius\Bundle\ApiBundle\StateProcessor\Common;
use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use Symfony\Component\Messenger\Exception\DelayedMessageHandlingException;
use Symfony\Component\Messenger\Exception\HandlerFailedException;
final class MessengerDataPersister implements ContextAwareDataPersisterInterface
/** @implements ProcessorInterface<object, mixed> */
final readonly class MessengerPersistProcessor implements ProcessorInterface
{
public function __construct(private ContextAwareDataPersisterInterface $decoratedDataPersister)
/** @param ProcessorInterface<object, mixed> $decoratedProcessor */
public function __construct(private ProcessorInterface $decoratedProcessor)
{
}
public function supports($data, array $context = []): bool
{
return $this->decoratedDataPersister->supports($data, $context);
}
public function persist($data, array $context = [])
/** @throws \Throwable */
public function process($data, Operation $operation, array $uriVariables = [], array $context = [])
{
try {
return $this->decoratedDataPersister->persist($data, $context);
return $this->decoratedProcessor->process($data, $operation, $uriVariables, $context);
} catch (DelayedMessageHandlingException|HandlerFailedException $e) {
while ($e instanceof DelayedMessageHandlingException) {
/** @var \Throwable $e */
@ -45,9 +44,4 @@ final class MessengerDataPersister implements ContextAwareDataPersisterInterface
throw $e;
}
}
public function remove($data, array $context = [])
{
$this->decoratedDataPersister->remove($data, $context);
}
}

View file

@ -1,80 +0,0 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace spec\Sylius\Bundle\ApiBundle\DataPersister;
use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Sylius\Bundle\ApiBundle\Command\Checkout\CompleteOrder;
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Exception\DelayedMessageHandlingException;
use Symfony\Component\Messenger\Exception\HandlerFailedException;
final class MessengerDataPersisterSpec extends ObjectBehavior
{
function let(ContextAwareDataPersisterInterface $decoratedDataPersister)
{
$this->beConstructedWith($decoratedDataPersister);
}
function it_calls_supports_method_from_decorated_data_presister(
ContextAwareDataPersisterInterface $decoratedDataPersister,
): void {
$decoratedDataPersister->supports(Argument::any(), [])->shouldBeCalled();
$this->supports(Argument::any(), []);
}
function it_unwraps_delayed_message_handling_exception(ContextAwareDataPersisterInterface $decoratedDataPersister): void
{
$completeOrder = new CompleteOrder('ThankYou');
$completeOrder->setOrderTokenValue('ORDERTOKEN');
$envelope = new Envelope($completeOrder);
if (version_compare(Kernel::VERSION, '6.3.5', '>=')) {
$exception = new DelayedMessageHandlingException([new \RuntimeException('Delayed message exception')], $envelope);
} else {
$exception = new DelayedMessageHandlingException([new \RuntimeException('Delayed message exception')]);
}
$decoratedDataPersister->persist($envelope, [])->willThrow($exception);
$this->shouldThrow(new \RuntimeException('Delayed message exception'))->during('persist', [$envelope, []]);
}
function it_unwraps_handler_failed_exception(ContextAwareDataPersisterInterface $decoratedDataPersister): void
{
$completeOrder = new CompleteOrder('ThankYou');
$completeOrder->setOrderTokenValue('ORDERTOKEN');
$envelope = new Envelope($completeOrder);
$decoratedDataPersister->persist($envelope, [])->willThrow(
new HandlerFailedException(
$envelope,
[new \RuntimeException('Delayed message exception')],
),
);
$this->shouldThrow(new \RuntimeException('Delayed message exception'))->during('persist', [$envelope, []]);
}
function it_calls_remove_method_from_decorated_data_presister(
ContextAwareDataPersisterInterface $decoratedDataPersister,
): void {
$decoratedDataPersister->remove(Argument::any(), [])->shouldBeCalled();
$this->remove(Argument::any(), []);
}
}

View file

@ -0,0 +1,70 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace spec\Sylius\Bundle\ApiBundle\StateProcessor\Common;
use ApiPlatform\Metadata\Post;
use ApiPlatform\State\ProcessorInterface;
use PhpSpec\ObjectBehavior;
use Sylius\Bundle\ApiBundle\Command\Checkout\CompleteOrder;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Exception\DelayedMessageHandlingException;
use Symfony\Component\Messenger\Exception\HandlerFailedException;
final class MessengerPersistProcessorSpec extends ObjectBehavior
{
function let(ProcessorInterface $decoratedProcessor): void
{
$this->beConstructedWith($decoratedProcessor);
}
function it_implements_processor_interface(): void
{
$this->shouldImplement(ProcessorInterface::class);
}
function it_unwraps_delayed_message_handling_exception(ProcessorInterface $decoratedProcessor): void
{
$command = new CompleteOrder('ThankYou', 'token');
$envelope = new Envelope($command);
$operation = new Post();
$exception = new DelayedMessageHandlingException([new \RuntimeException('Delayed message exception')], $envelope);
$decoratedProcessor->process($envelope, $operation, [], [])->willThrow($exception);
$this
->shouldThrow(new \RuntimeException('Delayed message exception'))
->during('process', [$envelope, $operation, [], []])
;
}
function it_unwraps_handler_failed_exception(ProcessorInterface $decoratedProcessor): void
{
$command = new CompleteOrder('ThankYou', 'token');
$envelope = new Envelope($command);
$operation = new Post();
$decoratedProcessor->process($envelope, $operation, [], [])->willThrow(
new HandlerFailedException(
$envelope,
[new \RuntimeException('Delayed message exception')],
),
);
$this
->shouldThrow(new \RuntimeException('Delayed message exception'))
->during('process', [$envelope, $operation, [], []])
;
}
}