diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 2dd6ed95ee808..764da8fd81444 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -2415,8 +2415,10 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder throw new LogicException(\sprintf('Invalid Messenger configuration: the failure transport "%s" is not a valid transport or service id.', $config['failure_transport'])); } - $container->setAlias('messenger.failure_transports.default', 'messenger.transport.'.$config['failure_transport']); - $failureTransports[] = $config['failure_transport']; + if (!isset($config['transports'][$config['failure_transport']]['failure_transport'])) { + $container->setAlias('messenger.failure_transports.default', 'messenger.transport.'.$config['failure_transport']); + $failureTransports[] = $config['failure_transport']; + } } $failureTransportsByName = []; @@ -2429,6 +2431,12 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder } } + foreach ($failureTransports as $key => $failureTransport) { + if (isset($config['transports'][$failureTransport]['failure_transport'])) { + unset($failureTransports[$key]); + } + } + $senderAliases = []; $transportRetryReferences = []; $transportRateLimiterReferences = []; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_failure_transport_chain.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_failure_transport_chain.php new file mode 100644 index 0000000000000..2e7e51015f4bb --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_failure_transport_chain.php @@ -0,0 +1,21 @@ +loadFromExtension('framework', [ + 'annotations' => false, + 'http_method_override' => false, + 'handle_all_throwables' => true, + 'php_errors' => ['log' => true], + 'messenger' => [ + 'transports' => [ + 'transport_1' => [ + 'dsn' => 'null://', + 'failure_transport' => 'transport_2', + ], + 'transport_2' => [ + 'dsn' => 'null://', + 'failure_transport' => 'failure_transport_1', + ], + 'failure_transport_1' => 'null://', + ], + ], +]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_failure_transport_chain.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_failure_transport_chain.xml new file mode 100644 index 0000000000000..df311501921f1 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_failure_transport_chain.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_failure_transport_chain.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_failure_transport_chain.yml new file mode 100644 index 0000000000000..9108cfede72e4 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_failure_transport_chain.yml @@ -0,0 +1,15 @@ +framework: + annotations: false + http_method_override: false + handle_all_throwables: true + php_errors: + log: true + messenger: + transports: + transport_1: + dsn: 'null://' + failure_transport: transport_2 + transport_2: + dsn: 'null://' + failure_transport: failure_transport_1 + failure_transport_1: 'null://' diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php index d942c122c826a..b4ca76c98ec78 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php @@ -900,6 +900,41 @@ public function testMessengerMultipleFailureTransports() $this->assertEquals($expectedTransportsByFailureTransports, $failureTransportsReferences); } + public function testMessengerFailureTransportChain() + { + $container = $this->createContainerFromFile('messenger_failure_transport_chain'); + + $failureTransport1Definition = $container->getDefinition('messenger.transport.failure_transport_1'); + $failureTransport1Tags = $failureTransport1Definition->getTag('messenger.receiver')[0]; + + $this->assertEquals([ + 'alias' => 'failure_transport_1', + 'is_failure_transport' => true, + ], $failureTransport1Tags); + + $intermediateFailureTransportDefinition = $container->getDefinition('messenger.transport.transport_2'); + $failureTransport3Tags = $intermediateFailureTransportDefinition->getTag('messenger.receiver')[0]; + + $this->assertEquals([ + 'alias' => 'transport_2', + 'is_failure_transport' => false, + ], $failureTransport3Tags); + + $failureTransportsByTransportNameServiceLocator = $container->getDefinition('messenger.failure.send_failed_message_to_failure_transport_listener')->getArgument(0); + $failureTransports = $container->getDefinition((string) $failureTransportsByTransportNameServiceLocator)->getArgument(0); + $expectedTransportsByFailureTransports = [ + 'transport_1' => new Reference('messenger.transport.transport_2'), + 'transport_2' => new Reference('messenger.transport.failure_transport_1'), + ]; + + $failureTransportsReferences = array_map(function (ServiceClosureArgument $serviceClosureArgument) { + $values = $serviceClosureArgument->getValues(); + + return array_shift($values); + }, $failureTransports); + $this->assertEquals($expectedTransportsByFailureTransports, $failureTransportsReferences); + } + public function testMessengerMultipleFailureTransportsWithGlobalFailureTransport() { $container = $this->createContainerFromFile('messenger_multiple_failure_transports_global'); diff --git a/src/Symfony/Component/Messenger/CHANGELOG.md b/src/Symfony/Component/Messenger/CHANGELOG.md index c4eae318d3518..b00afbfaf8c0d 100644 --- a/src/Symfony/Component/Messenger/CHANGELOG.md +++ b/src/Symfony/Component/Messenger/CHANGELOG.md @@ -10,6 +10,7 @@ CHANGELOG * Add `--class-filter` option to the `messenger:failed:remove` command * Add `$stamps` parameter to `HandleTrait::handle` * Add `Symfony\Component\Messenger\EventListener\ResetMemoryUsageListener` to reset PHP's peak memory usage for each processed message + * Add ability to chain failure transports. A failed message will process through the respective failure transports until arriving at the final one. 7.2 --- diff --git a/src/Symfony/Component/Messenger/EventListener/SendFailedMessageToFailureTransportListener.php b/src/Symfony/Component/Messenger/EventListener/SendFailedMessageToFailureTransportListener.php index 363403f627437..0452f34e8d03d 100644 --- a/src/Symfony/Component/Messenger/EventListener/SendFailedMessageToFailureTransportListener.php +++ b/src/Symfony/Component/Messenger/EventListener/SendFailedMessageToFailureTransportListener.php @@ -47,9 +47,11 @@ public function onMessageFailed(WorkerMessageFailedEvent $event): void $envelope = $event->getEnvelope(); - // avoid re-sending to the failed sender - if (null !== $envelope->last(SentToFailureTransportStamp::class)) { - return; + // avoid re-sending to same failed sender again + foreach ($envelope->all(SentToFailureTransportStamp::class) as $stamp) { + if ($stamp->getOriginalReceiverName() === $event->getReceiverName()) { + return; + } } $envelope = $envelope->with( diff --git a/src/Symfony/Component/Messenger/Tests/EventListener/SendFailedMessageToFailureTransportListenerTest.php b/src/Symfony/Component/Messenger/Tests/EventListener/SendFailedMessageToFailureTransportListenerTest.php index 9060ff515ed84..a0a6c1a09e77f 100644 --- a/src/Symfony/Component/Messenger/Tests/EventListener/SendFailedMessageToFailureTransportListenerTest.php +++ b/src/Symfony/Component/Messenger/Tests/EventListener/SendFailedMessageToFailureTransportListenerTest.php @@ -124,4 +124,37 @@ public function testItSendsToTheFailureTransportWithMultipleFailedTransports() $listener->onMessageFailed($event); } + + public function testItResendsToTheFailureTransportWithSenderLocator() + { + $receiverName = 'my_receiver'; + $sender = $this->createMock(SenderInterface::class); + $sender->expects($this->once())->method('send')->with($this->callback(function ($envelope) use ($receiverName) { + /* @var Envelope $envelope */ + $this->assertInstanceOf(Envelope::class, $envelope); + + $this->assertCount(2, $envelope->all(SentToFailureTransportStamp::class)); + + /** @var SentToFailureTransportStamp $sentToFailureTransportStamp */ + $sentToFailureTransportStamp = $envelope->last(SentToFailureTransportStamp::class); + $this->assertNotNull($sentToFailureTransportStamp); + $this->assertSame($receiverName, $sentToFailureTransportStamp->getOriginalReceiverName()); + + return true; + }))->willReturnArgument(0); + + $serviceLocator = $this->createMock(ServiceLocator::class); + $serviceLocator->expects($this->once())->method('has')->willReturn(true); + $serviceLocator->expects($this->once())->method('get')->with($receiverName)->willReturn($sender); + $listener = new SendFailedMessageToFailureTransportListener($serviceLocator); + + $exception = new \Exception('no!'); + $envelope = new Envelope( + new \stdClass(), + [new SentToFailureTransportStamp('my_other_receiver')], + ); + $event = new WorkerMessageFailedEvent($envelope, 'my_receiver', $exception); + + $listener->onMessageFailed($event); + } }