Skip to content

Commit fed32dc

Browse files
valtzufabpot
authored andcommitted
[Messenger] Don't drop stamps when message validation fails
1 parent 202199b commit fed32dc

8 files changed

+147
-14
lines changed

src/Symfony/Component/Messenger/Exception/DelayedMessageHandlingException.php

+2-7
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@
1919
*
2020
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
2121
*/
22-
class DelayedMessageHandlingException extends RuntimeException implements WrappedExceptionsInterface
22+
class DelayedMessageHandlingException extends RuntimeException implements WrappedExceptionsInterface, EnvelopeAwareExceptionInterface
2323
{
24+
use EnvelopeAwareExceptionTrait;
2425
use WrappedExceptionsTrait;
2526

2627
private array $exceptions;
27-
private ?Envelope $envelope;
2828

2929
public function __construct(array $exceptions, ?Envelope $envelope = null)
3030
{
@@ -55,9 +55,4 @@ public function getExceptions(): array
5555

5656
return $this->exceptions;
5757
}
58-
59-
public function getEnvelope(): ?Envelope
60-
{
61-
return $this->envelope;
62-
}
6358
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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+
namespace Symfony\Component\Messenger\Exception;
13+
14+
use Symfony\Component\Messenger\Envelope;
15+
16+
/**
17+
* @internal
18+
*/
19+
interface EnvelopeAwareExceptionInterface
20+
{
21+
public function getEnvelope(): ?Envelope;
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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+
namespace Symfony\Component\Messenger\Exception;
13+
14+
use Symfony\Component\Messenger\Envelope;
15+
16+
/**
17+
* @internal
18+
*/
19+
trait EnvelopeAwareExceptionTrait
20+
{
21+
private ?Envelope $envelope = null;
22+
23+
public function getEnvelope(): ?Envelope
24+
{
25+
return $this->envelope;
26+
}
27+
}

src/Symfony/Component/Messenger/Exception/HandlerFailedException.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
use Symfony\Component\Messenger\Envelope;
1515

16-
class HandlerFailedException extends RuntimeException implements WrappedExceptionsInterface
16+
class HandlerFailedException extends RuntimeException implements WrappedExceptionsInterface, EnvelopeAwareExceptionInterface
1717
{
1818
use WrappedExceptionsTrait;
1919

src/Symfony/Component/Messenger/Exception/ValidationFailedException.php

+6-2
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,24 @@
1111

1212
namespace Symfony\Component\Messenger\Exception;
1313

14+
use Symfony\Component\Messenger\Envelope;
1415
use Symfony\Component\Validator\ConstraintViolationListInterface;
1516

1617
/**
1718
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
1819
*/
19-
class ValidationFailedException extends RuntimeException
20+
class ValidationFailedException extends RuntimeException implements EnvelopeAwareExceptionInterface
2021
{
22+
use EnvelopeAwareExceptionTrait;
23+
2124
private ConstraintViolationListInterface $violations;
2225
private object $violatingMessage;
2326

24-
public function __construct(object $violatingMessage, ConstraintViolationListInterface $violations)
27+
public function __construct(object $violatingMessage, ConstraintViolationListInterface $violations, ?Envelope $envelope = null)
2528
{
2629
$this->violatingMessage = $violatingMessage;
2730
$this->violations = $violations;
31+
$this->envelope = $envelope;
2832

2933
parent::__construct(sprintf('Message of type "%s" failed validation.', $this->violatingMessage::class));
3034
}

src/Symfony/Component/Messenger/Middleware/ValidationMiddleware.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public function handle(Envelope $envelope, StackInterface $stack): Envelope
3939

4040
$violations = $this->validator->validate($message, null, $groups);
4141
if (\count($violations)) {
42-
throw new ValidationFailedException($message, $violations);
42+
throw new ValidationFailedException($message, $violations, $envelope);
4343
}
4444

4545
return $stack->next()->handle($envelope, $stack);

src/Symfony/Component/Messenger/Tests/FailureIntegrationTest.php

+86
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
use Symfony\Component\Messenger\EventListener\StopWorkerOnMessageLimitListener;
2525
use Symfony\Component\Messenger\Exception\DelayedMessageHandlingException;
2626
use Symfony\Component\Messenger\Exception\HandlerFailedException;
27+
use Symfony\Component\Messenger\Exception\ValidationFailedException;
2728
use Symfony\Component\Messenger\Handler\HandlerDescriptor;
2829
use Symfony\Component\Messenger\Handler\HandlersLocator;
2930
use Symfony\Component\Messenger\MessageBus;
@@ -32,6 +33,7 @@
3233
use Symfony\Component\Messenger\Middleware\FailedMessageProcessingMiddleware;
3334
use Symfony\Component\Messenger\Middleware\HandleMessageMiddleware;
3435
use Symfony\Component\Messenger\Middleware\SendMessageMiddleware;
36+
use Symfony\Component\Messenger\Middleware\ValidationMiddleware;
3537
use Symfony\Component\Messenger\Retry\MultiplierRetryStrategy;
3638
use Symfony\Component\Messenger\Stamp\BusNameStamp;
3739
use Symfony\Component\Messenger\Stamp\DispatchAfterCurrentBusStamp;
@@ -42,6 +44,9 @@
4244
use Symfony\Component\Messenger\Transport\Sender\SenderInterface;
4345
use Symfony\Component\Messenger\Transport\Sender\SendersLocator;
4446
use Symfony\Component\Messenger\Worker;
47+
use Symfony\Component\Validator\ConstraintViolation;
48+
use Symfony\Component\Validator\ConstraintViolationList;
49+
use Symfony\Component\Validator\Validator\ValidatorInterface;
4550

4651
class FailureIntegrationTest extends TestCase
4752
{
@@ -440,6 +445,87 @@ public function testStampsAddedByMiddlewaresDontDisappearWhenDelayedMessageFails
440445
$this->assertCount(1, $messagesWaiting);
441446
$this->assertSame('some.bus', $messagesWaiting[0]->last(BusNameStamp::class)?->getBusName());
442447
}
448+
449+
public function testStampsAddedByMiddlewaresDontDisappearWhenValidationFails()
450+
{
451+
$transport1 = new DummyFailureTestSenderAndReceiver();
452+
453+
$transports = [
454+
'transport1' => $transport1,
455+
];
456+
457+
$locator = $this->createMock(ContainerInterface::class);
458+
$locator->expects($this->any())
459+
->method('has')
460+
->willReturn(true);
461+
$locator->expects($this->any())
462+
->method('get')
463+
->willReturnCallback(fn ($transportName) => $transports[$transportName]);
464+
$senderLocator = new SendersLocator([], $locator);
465+
466+
$retryStrategyLocator = $this->createMock(ContainerInterface::class);
467+
$retryStrategyLocator->expects($this->any())
468+
->method('has')
469+
->willReturn(true);
470+
$retryStrategyLocator->expects($this->any())
471+
->method('get')
472+
->willReturn(new MultiplierRetryStrategy(1));
473+
474+
$violationList = new ConstraintViolationList([new ConstraintViolation('validation failed', null, [], null, null, null)]);
475+
$validator = $this->createMock(ValidatorInterface::class);
476+
$validator->expects($this->once())->method('validate')->willReturn($violationList);
477+
478+
$middlewareStack = new \ArrayIterator([
479+
new AddBusNameStampMiddleware('some.bus'),
480+
new ValidationMiddleware($validator),
481+
new SendMessageMiddleware($senderLocator),
482+
]);
483+
484+
$bus = new MessageBus($middlewareStack);
485+
486+
$transport1Handler = fn () => $bus->dispatch(new \stdClass(), [new DispatchAfterCurrentBusStamp()]);
487+
488+
$handlerLocator = new HandlersLocator([
489+
DummyMessage::class => [new HandlerDescriptor($transport1Handler)],
490+
]);
491+
492+
$middlewareStack->append(new HandleMessageMiddleware($handlerLocator));
493+
494+
$dispatcher = new EventDispatcher();
495+
496+
$dispatcher->addSubscriber(new SendFailedMessageForRetryListener($locator, $retryStrategyLocator));
497+
$dispatcher->addSubscriber(new StopWorkerOnMessageLimitListener(1));
498+
499+
$runWorker = function (string $transportName) use ($transports, $bus, $dispatcher): ?\Throwable {
500+
$throwable = null;
501+
$failedListener = function (WorkerMessageFailedEvent $event) use (&$throwable) {
502+
$throwable = $event->getThrowable();
503+
};
504+
$dispatcher->addListener(WorkerMessageFailedEvent::class, $failedListener);
505+
506+
$worker = new Worker([$transportName => $transports[$transportName]], $bus, $dispatcher);
507+
508+
$worker->run();
509+
510+
$dispatcher->removeListener(WorkerMessageFailedEvent::class, $failedListener);
511+
512+
return $throwable;
513+
};
514+
515+
// Simulate receive from external source
516+
$transport1->send(new Envelope(new DummyMessage('API')));
517+
518+
// Receive the message from "transport1"
519+
$throwable = $runWorker('transport1');
520+
521+
$this->assertInstanceOf(ValidationFailedException::class, $throwable, $throwable->getMessage());
522+
523+
$messagesWaiting = $transport1->getMessagesWaitingToBeReceived();
524+
525+
// Stamps should not be dropped on message that's queued for retry
526+
$this->assertCount(1, $messagesWaiting);
527+
$this->assertSame('some.bus', $messagesWaiting[0]->last(BusNameStamp::class)?->getBusName());
528+
}
443529
}
444530

445531
class DummyFailureTestSenderAndReceiver implements ReceiverInterface, SenderInterface

src/Symfony/Component/Messenger/Worker.php

+2-3
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@
2222
use Symfony\Component\Messenger\Event\WorkerRunningEvent;
2323
use Symfony\Component\Messenger\Event\WorkerStartedEvent;
2424
use Symfony\Component\Messenger\Event\WorkerStoppedEvent;
25-
use Symfony\Component\Messenger\Exception\DelayedMessageHandlingException;
26-
use Symfony\Component\Messenger\Exception\HandlerFailedException;
25+
use Symfony\Component\Messenger\Exception\EnvelopeAwareExceptionInterface;
2726
use Symfony\Component\Messenger\Exception\RejectRedeliveredMessageException;
2827
use Symfony\Component\Messenger\Exception\RuntimeException;
2928
use Symfony\Component\Messenger\Stamp\AckStamp;
@@ -189,7 +188,7 @@ private function ack(): bool
189188
$receiver->reject($envelope);
190189
}
191190

192-
if ($e instanceof HandlerFailedException || ($e instanceof DelayedMessageHandlingException && null !== $e->getEnvelope())) {
191+
if ($e instanceof EnvelopeAwareExceptionInterface && null !== $e->getEnvelope()) {
193192
$envelope = $e->getEnvelope();
194193
}
195194

0 commit comments

Comments
 (0)