From 167c1febbd0eb083978e8b46e290786326cde53e Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Delhommeau Date: Mon, 30 Jun 2025 13:58:07 +0200 Subject: [PATCH] chore: add exclude-receivers consume parameters --- src/Symfony/Component/Messenger/CHANGELOG.md | 1 + .../Command/ConsumeMessagesCommand.php | 26 +++ .../Command/ConsumeMessagesCommandTest.php | 154 +++++++++++++++++- 3 files changed, 180 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Messenger/CHANGELOG.md b/src/Symfony/Component/Messenger/CHANGELOG.md index c4eae318d3518..068de7984a00a 100644 --- a/src/Symfony/Component/Messenger/CHANGELOG.md +++ b/src/Symfony/Component/Messenger/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 7.3 --- + * Add `--exclude-receivers` option to the `messenger:consume command` * Add `CloseableTransportInterface` to allow closing the transport * Add `SentForRetryStamp` that identifies whether a failed message was sent for retry * Add `Symfony\Component\Messenger\Middleware\DeduplicateMiddleware` and `Symfony\Component\Messenger\Stamp\DeduplicateStamp` diff --git a/src/Symfony/Component/Messenger/Command/ConsumeMessagesCommand.php b/src/Symfony/Component/Messenger/Command/ConsumeMessagesCommand.php index 9fcb6a5e17e80..262aba27ecf66 100644 --- a/src/Symfony/Component/Messenger/Command/ConsumeMessagesCommand.php +++ b/src/Symfony/Component/Messenger/Command/ConsumeMessagesCommand.php @@ -77,6 +77,7 @@ protected function configure(): void new InputOption('queues', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Limit receivers to only consume from the specified queues'), new InputOption('no-reset', null, InputOption::VALUE_NONE, 'Do not reset container services after each message'), new InputOption('all', null, InputOption::VALUE_NONE, 'Consume messages from all receivers'), + new InputOption('exclude-receivers', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Exclude specific receivers/transports from consumption (can only be used with --all)'), new InputOption('keepalive', null, InputOption::VALUE_OPTIONAL, 'Whether to use the transport\'s keepalive mechanism if implemented', self::DEFAULT_KEEPALIVE_INTERVAL), ]) ->setHelp(<<<'EOF' @@ -122,6 +123,10 @@ protected function configure(): void Use the --all option to consume from all receivers: php %command.full_name% --all + +Use the --exclude-receivers option to exclude specific receivers/transports from consumption (can only be used with --all): + + php %command.full_name% --all --exclude-receivers= EOF ) ; @@ -132,6 +137,10 @@ protected function initialize(InputInterface $input, OutputInterface $output): v if ($input->hasParameterOption('--keepalive')) { $this->getApplication()->setAlarmInterval((int) ($input->getOption('keepalive') ?? self::DEFAULT_KEEPALIVE_INTERVAL)); } + + if ($input->getOption('exclude-receivers') && !$input->getOption('all')) { + throw new InvalidOptionException('The --exclude-receivers option can only be used with the --all option.'); + } } protected function interact(InputInterface $input, OutputInterface $output): void @@ -169,9 +178,22 @@ protected function interact(InputInterface $input, OutputInterface $output): voi protected function execute(InputInterface $input, OutputInterface $output): int { + if ($input->getOption('exclude-receivers') && !$input->getOption('all')) { + throw new InvalidOptionException('The --exclude-receivers option can only be used with the --all option.'); + } + $receivers = []; $rateLimiters = []; $receiverNames = $input->getOption('all') ? $this->receiverNames : $input->getArgument('receivers'); + + if ($input->getOption('all') && $excludedTransports = $input->getOption('exclude-receivers')) { + $receiverNames = array_diff($receiverNames, $excludedTransports); + + if (!$receiverNames) { + throw new RuntimeException('All transports/receivers have been excluded. Please specify at least one to consume from.'); + } + } + foreach ($receiverNames as $receiverName) { if (!$this->receiverLocator->has($receiverName)) { $message = \sprintf('The receiver "%s" does not exist.', $receiverName); @@ -276,6 +298,10 @@ public function complete(CompletionInput $input, CompletionSuggestions $suggesti if ($input->mustSuggestOptionValuesFor('bus')) { $suggestions->suggestValues($this->busIds); } + + if ($input->mustSuggestOptionValuesFor('exclude-receivers')) { + $suggestions->suggestValues($this->receiverNames); + } } public function getSubscribedSignals(): array diff --git a/src/Symfony/Component/Messenger/Tests/Command/ConsumeMessagesCommandTest.php b/src/Symfony/Component/Messenger/Tests/Command/ConsumeMessagesCommandTest.php index 8552a64f1a291..3ddc36c952069 100644 --- a/src/Symfony/Component/Messenger/Tests/Command/ConsumeMessagesCommandTest.php +++ b/src/Symfony/Component/Messenger/Tests/Command/ConsumeMessagesCommandTest.php @@ -242,7 +242,7 @@ public function testRunWithMemoryLimit() $busLocator = new Container(); $busLocator->set('dummy-bus', $bus); - $logger = new class() implements LoggerInterface { + $logger = new class implements LoggerInterface { use LoggerTrait; public array $logs = []; @@ -334,4 +334,156 @@ public static function provideCompletionSuggestions() yield 'receiver (no repeat)' => [['async', ''], ['async_high', 'failed']]; yield 'option --bus' => [['--bus', ''], ['messenger.bus.default']]; } + + public function testRunWithExcludeReceiversOption() + { + $envelope1 = new Envelope(new \stdClass(), [new BusNameStamp('dummy-bus')]); + $envelope2 = new Envelope(new \stdClass(), [new BusNameStamp('dummy-bus')]); + $envelope3 = new Envelope(new \stdClass(), [new BusNameStamp('dummy-bus')]); + + $receiver1 = $this->createMock(ReceiverInterface::class); + $receiver1->method('get')->willReturn([$envelope1]); + $receiver2 = $this->createMock(ReceiverInterface::class); + $receiver2->method('get')->willReturn([$envelope2]); + $receiver3 = $this->createMock(ReceiverInterface::class); + $receiver3->method('get')->willReturn([$envelope3]); + + $receiverLocator = new Container(); + $receiverLocator->set('dummy-receiver1', $receiver1); + $receiverLocator->set('dummy-receiver2', $receiver2); + $receiverLocator->set('dummy-receiver3', $receiver3); + + $bus = $this->createMock(MessageBusInterface::class); + // Only 2 messages should be dispatched (receiver1 and receiver3, receiver2 is excluded) + $bus->expects($this->exactly(2))->method('dispatch'); + + $busLocator = new Container(); + $busLocator->set('dummy-bus', $bus); + + $command = new ConsumeMessagesCommand( + new RoutableMessageBus($busLocator), + $receiverLocator, new EventDispatcher(), + receiverNames: ['dummy-receiver1', 'dummy-receiver2', 'dummy-receiver3'] + ); + + $application = new Application(); + if (method_exists($application, 'addCommand')) { + $application->addCommand($command); + } else { + $application->add($command); + } + $tester = new CommandTester($application->get('messenger:consume')); + $tester->execute([ + '--all' => true, + '--exclude-receivers' => ['dummy-receiver2'], + '--limit' => 2, + ]); + + $tester->assertCommandIsSuccessful(); + $this->assertStringContainsString('[OK] Consuming messages from transports "dummy-receiver1, dummy-receiver3"', $tester->getDisplay()); + } + + public function testRunWithExcludeReceiversMultipleQueues() + { + $envelope1 = new Envelope(new \stdClass(), [new BusNameStamp('dummy-bus')]); + $envelope2 = new Envelope(new \stdClass(), [new BusNameStamp('dummy-bus')]); + $envelope3 = new Envelope(new \stdClass(), [new BusNameStamp('dummy-bus')]); + $envelope4 = new Envelope(new \stdClass(), [new BusNameStamp('dummy-bus')]); + + $receiver1 = $this->createMock(ReceiverInterface::class); + $receiver1->method('get')->willReturn([$envelope1]); + $receiver2 = $this->createMock(ReceiverInterface::class); + $receiver2->method('get')->willReturn([$envelope2]); + $receiver3 = $this->createMock(ReceiverInterface::class); + $receiver3->method('get')->willReturn([$envelope3]); + $receiver4 = $this->createMock(ReceiverInterface::class); + $receiver4->method('get')->willReturn([$envelope4]); + + $receiverLocator = new Container(); + $receiverLocator->set('dummy-receiver1', $receiver1); + $receiverLocator->set('dummy-receiver2', $receiver2); + $receiverLocator->set('dummy-receiver3', $receiver3); + $receiverLocator->set('dummy-receiver4', $receiver4); + + $bus = $this->createMock(MessageBusInterface::class); + // Only 2 messages should be dispatched (receiver1 and receiver4, receiver2 and receiver3 are excluded) + $bus->expects($this->exactly(2))->method('dispatch'); + + $busLocator = new Container(); + $busLocator->set('dummy-bus', $bus); + + $command = new ConsumeMessagesCommand( + new RoutableMessageBus($busLocator), + $receiverLocator, new EventDispatcher(), + receiverNames: ['dummy-receiver1', 'dummy-receiver2', 'dummy-receiver3', 'dummy-receiver4'] + ); + + $application = new Application(); + if (method_exists($application, 'addCommand')) { + $application->addCommand($command); + } else { + $application->add($command); + } + $tester = new CommandTester($application->get('messenger:consume')); + $tester->execute([ + '--all' => true, + '--exclude-receivers' => ['dummy-receiver2', 'dummy-receiver3'], + '--limit' => 2, + ]); + + $tester->assertCommandIsSuccessful(); + $this->assertStringContainsString('[OK] Consuming messages from transports "dummy-receiver1, dummy-receiver4"', $tester->getDisplay()); + } + + public function testExcludeReceiverssWithoutAllOptionThrowsException() + { + $receiverLocator = new Container(); + $receiverLocator->set('dummy-receiver', new \stdClass()); + + $command = new ConsumeMessagesCommand(new RoutableMessageBus(new Container()), $receiverLocator, new EventDispatcher()); + + $application = new Application(); + if (method_exists($application, 'addCommand')) { + $application->addCommand($command); + } else { + $application->add($command); + } + $tester = new CommandTester($application->get('messenger:consume')); + + $this->expectException(InvalidOptionException::class); + $this->expectExceptionMessage('The --exclude-receivers option can only be used with the --all option.'); + $tester->execute([ + 'receivers' => ['dummy-receiver'], + '--exclude-receivers' => ['dummy-receiver'], + ]); + } + + public function testExcludeReceiversWithAllQueuesExcludedThrowsException() + { + $receiverLocator = new Container(); + $receiverLocator->set('dummy-receiver1', new \stdClass()); + $receiverLocator->set('dummy-receiver2', new \stdClass()); + + $command = new ConsumeMessagesCommand( + new RoutableMessageBus(new Container()), + $receiverLocator, + new EventDispatcher(), + receiverNames: ['dummy-receiver1', 'dummy-receiver2'] + ); + + $application = new Application(); + if (method_exists($application, 'addCommand')) { + $application->addCommand($command); + } else { + $application->add($command); + } + $tester = new CommandTester($application->get('messenger:consume')); + + $this->expectException(\Symfony\Component\Console\Exception\RuntimeException::class); + $this->expectExceptionMessage('All transports/receivers have been excluded. Please specify at least one to consume from.'); + $tester->execute([ + '--all' => true, + '--exclude-receivers' => ['dummy-receiver1', 'dummy-receiver2'], + ]); + } } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy