Skip to content

Commit

Permalink
Merge branch '6.4' into 7.0
Browse files Browse the repository at this point in the history
* 6.4:
  [Messenger] Don't drop stamps when message validation fails
  [WebProfilerBundle] Fix assignment to constant variable
  [Mailer] [Sendgrid] Use DataPart::getContentId() when DataPart::setContentId() is used.
  • Loading branch information
fabpot committed May 7, 2024
2 parents 361f5ba + 34b7fd3 commit bb96e8e
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 14 deletions.
9 changes: 2 additions & 7 deletions Exception/DelayedMessageHandlingException.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@
*
* @author Tobias Nyholm <[email protected]>
*/
class DelayedMessageHandlingException extends RuntimeException implements WrappedExceptionsInterface
class DelayedMessageHandlingException extends RuntimeException implements WrappedExceptionsInterface, EnvelopeAwareExceptionInterface
{
use EnvelopeAwareExceptionTrait;
use WrappedExceptionsTrait;

private array $exceptions;
private ?Envelope $envelope;

public function __construct(array $exceptions, ?Envelope $envelope = null)
{
Expand All @@ -45,9 +45,4 @@ public function __construct(array $exceptions, ?Envelope $envelope = null)

parent::__construct($message, 0, $exceptions[array_key_first($exceptions)]);
}

public function getEnvelope(): ?Envelope
{
return $this->envelope;
}
}
22 changes: 22 additions & 0 deletions Exception/EnvelopeAwareExceptionInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Messenger\Exception;

use Symfony\Component\Messenger\Envelope;

/**
* @internal
*/
interface EnvelopeAwareExceptionInterface
{
public function getEnvelope(): ?Envelope;
}
27 changes: 27 additions & 0 deletions Exception/EnvelopeAwareExceptionTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Messenger\Exception;

use Symfony\Component\Messenger\Envelope;

/**
* @internal
*/
trait EnvelopeAwareExceptionTrait
{
private ?Envelope $envelope = null;

public function getEnvelope(): ?Envelope
{
return $this->envelope;
}
}
2 changes: 1 addition & 1 deletion Exception/HandlerFailedException.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

use Symfony\Component\Messenger\Envelope;

class HandlerFailedException extends RuntimeException implements WrappedExceptionsInterface
class HandlerFailedException extends RuntimeException implements WrappedExceptionsInterface, EnvelopeAwareExceptionInterface
{
use WrappedExceptionsTrait;

Expand Down
8 changes: 6 additions & 2 deletions Exception/ValidationFailedException.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,24 @@

namespace Symfony\Component\Messenger\Exception;

use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Validator\ConstraintViolationListInterface;

/**
* @author Tobias Nyholm <[email protected]>
*/
class ValidationFailedException extends RuntimeException
class ValidationFailedException extends RuntimeException implements EnvelopeAwareExceptionInterface
{
use EnvelopeAwareExceptionTrait;

private ConstraintViolationListInterface $violations;
private object $violatingMessage;

public function __construct(object $violatingMessage, ConstraintViolationListInterface $violations)
public function __construct(object $violatingMessage, ConstraintViolationListInterface $violations, ?Envelope $envelope = null)
{
$this->violatingMessage = $violatingMessage;
$this->violations = $violations;
$this->envelope = $envelope;

parent::__construct(sprintf('Message of type "%s" failed validation.', $this->violatingMessage::class));
}
Expand Down
2 changes: 1 addition & 1 deletion Middleware/ValidationMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public function handle(Envelope $envelope, StackInterface $stack): Envelope

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

return $stack->next()->handle($envelope, $stack);
Expand Down
86 changes: 86 additions & 0 deletions Tests/FailureIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
use Symfony\Component\Messenger\EventListener\StopWorkerOnMessageLimitListener;
use Symfony\Component\Messenger\Exception\DelayedMessageHandlingException;
use Symfony\Component\Messenger\Exception\HandlerFailedException;
use Symfony\Component\Messenger\Exception\ValidationFailedException;
use Symfony\Component\Messenger\Handler\HandlerDescriptor;
use Symfony\Component\Messenger\Handler\HandlersLocator;
use Symfony\Component\Messenger\MessageBus;
Expand All @@ -32,6 +33,7 @@
use Symfony\Component\Messenger\Middleware\FailedMessageProcessingMiddleware;
use Symfony\Component\Messenger\Middleware\HandleMessageMiddleware;
use Symfony\Component\Messenger\Middleware\SendMessageMiddleware;
use Symfony\Component\Messenger\Middleware\ValidationMiddleware;
use Symfony\Component\Messenger\Retry\MultiplierRetryStrategy;
use Symfony\Component\Messenger\Stamp\BusNameStamp;
use Symfony\Component\Messenger\Stamp\DispatchAfterCurrentBusStamp;
Expand All @@ -42,6 +44,9 @@
use Symfony\Component\Messenger\Transport\Sender\SenderInterface;
use Symfony\Component\Messenger\Transport\Sender\SendersLocator;
use Symfony\Component\Messenger\Worker;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationList;
use Symfony\Component\Validator\Validator\ValidatorInterface;

class FailureIntegrationTest extends TestCase
{
Expand Down Expand Up @@ -440,6 +445,87 @@ public function testStampsAddedByMiddlewaresDontDisappearWhenDelayedMessageFails
$this->assertCount(1, $messagesWaiting);
$this->assertSame('some.bus', $messagesWaiting[0]->last(BusNameStamp::class)?->getBusName());
}

public function testStampsAddedByMiddlewaresDontDisappearWhenValidationFails()
{
$transport1 = new DummyFailureTestSenderAndReceiver();

$transports = [
'transport1' => $transport1,
];

$locator = $this->createMock(ContainerInterface::class);
$locator->expects($this->any())
->method('has')
->willReturn(true);
$locator->expects($this->any())
->method('get')
->willReturnCallback(fn ($transportName) => $transports[$transportName]);
$senderLocator = new SendersLocator([], $locator);

$retryStrategyLocator = $this->createMock(ContainerInterface::class);
$retryStrategyLocator->expects($this->any())
->method('has')
->willReturn(true);
$retryStrategyLocator->expects($this->any())
->method('get')
->willReturn(new MultiplierRetryStrategy(1));

$violationList = new ConstraintViolationList([new ConstraintViolation('validation failed', null, [], null, null, null)]);
$validator = $this->createMock(ValidatorInterface::class);
$validator->expects($this->once())->method('validate')->willReturn($violationList);

$middlewareStack = new \ArrayIterator([
new AddBusNameStampMiddleware('some.bus'),
new ValidationMiddleware($validator),
new SendMessageMiddleware($senderLocator),
]);

$bus = new MessageBus($middlewareStack);

$transport1Handler = fn () => $bus->dispatch(new \stdClass(), [new DispatchAfterCurrentBusStamp()]);

$handlerLocator = new HandlersLocator([
DummyMessage::class => [new HandlerDescriptor($transport1Handler)],
]);

$middlewareStack->append(new HandleMessageMiddleware($handlerLocator));

$dispatcher = new EventDispatcher();

$dispatcher->addSubscriber(new SendFailedMessageForRetryListener($locator, $retryStrategyLocator));
$dispatcher->addSubscriber(new StopWorkerOnMessageLimitListener(1));

$runWorker = function (string $transportName) use ($transports, $bus, $dispatcher): ?\Throwable {
$throwable = null;
$failedListener = function (WorkerMessageFailedEvent $event) use (&$throwable) {
$throwable = $event->getThrowable();
};
$dispatcher->addListener(WorkerMessageFailedEvent::class, $failedListener);

$worker = new Worker([$transportName => $transports[$transportName]], $bus, $dispatcher);

$worker->run();

$dispatcher->removeListener(WorkerMessageFailedEvent::class, $failedListener);

return $throwable;
};

// Simulate receive from external source
$transport1->send(new Envelope(new DummyMessage('API')));

// Receive the message from "transport1"
$throwable = $runWorker('transport1');

$this->assertInstanceOf(ValidationFailedException::class, $throwable, $throwable->getMessage());

$messagesWaiting = $transport1->getMessagesWaitingToBeReceived();

// Stamps should not be dropped on message that's queued for retry
$this->assertCount(1, $messagesWaiting);
$this->assertSame('some.bus', $messagesWaiting[0]->last(BusNameStamp::class)?->getBusName());
}
}

class DummyFailureTestSenderAndReceiver implements ReceiverInterface, SenderInterface
Expand Down
5 changes: 2 additions & 3 deletions Worker.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@
use Symfony\Component\Messenger\Event\WorkerRunningEvent;
use Symfony\Component\Messenger\Event\WorkerStartedEvent;
use Symfony\Component\Messenger\Event\WorkerStoppedEvent;
use Symfony\Component\Messenger\Exception\DelayedMessageHandlingException;
use Symfony\Component\Messenger\Exception\HandlerFailedException;
use Symfony\Component\Messenger\Exception\EnvelopeAwareExceptionInterface;
use Symfony\Component\Messenger\Exception\RejectRedeliveredMessageException;
use Symfony\Component\Messenger\Exception\RuntimeException;
use Symfony\Component\Messenger\Stamp\AckStamp;
Expand Down Expand Up @@ -189,7 +188,7 @@ private function ack(): bool
$receiver->reject($envelope);
}

if ($e instanceof HandlerFailedException || ($e instanceof DelayedMessageHandlingException && null !== $e->getEnvelope())) {
if ($e instanceof EnvelopeAwareExceptionInterface && null !== $e->getEnvelope()) {
$envelope = $e->getEnvelope();
}

Expand Down

0 comments on commit bb96e8e

Please sign in to comment.