8000 feature #51593 [Messenger] Add the `--all` option to the `messenger:f… · symfony/symfony@4b21e21 · GitHub
[go: up one dir, main page]

Skip to content

Commit 4b21e21

Browse files
feature #51593 [Messenger] Add the --all option to the messenger:failed:remove command (alexandre-daubois)
This PR was merged into the 6.4 branch. Discussion ---------- [Messenger] Add the `--all` option to the `messenger:failed:remove` command | Q | A | ------------- | --- | Branch? | 6.4 | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | - | License | MIT | Doc PR | Todo We have a development server (which we don't have direct access to the database). As this server serves as a test for our devs, error messages can accumulate in our failure transport. We wanted to use the `messenger:failed:remove` command to remove them, but unfortunately, we must provide ids individually. This is problematic as we have several hundreds of failed messages. This PR adds the `--all` option to the command. This option **must** be used with the `--force` option (juste like `doctrine:schema:update --force` actually) to work. Example output: ```bash $ bin/console messenger:failed:remove --all --force ... Failed Message Details ====================== [WARNING] Message does not appear to have been sent to this transport after failing ------------ ------------------------- Class App\Message\YourMessage Message Id 6 ------------ ------------------------- ! [NOTE] 4 messages were removed. ``` As you can see, you can of course still use the `--show-messages` option jointly. Commits ------- d1d39c0 [Messenger] Add `--all` option to the `messenger:failed:remove` command
2 parents 1244ec9 + d1d39c0 commit 4b21e21

File tree

3 files changed

+155
-10
lines changed

3 files changed

+155
-10
lines changed

src/Symfony/Component/Messenger/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ CHANGELOG
77
* Deprecate `StopWorkerOnSignalsListener` in favor of using the `SignalableCommandInterface`
88
* Add `HandlerDescriptor::getOptions`
99
* Add support for multiple Redis Sentinel hosts
10+
* Add `--all` option to the `messenger:failed:remove` command
1011

1112
6.3
1213
---

src/Symfony/Component/Messenger/Command/FailedMessagesRemoveCommand.php

+54-8
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
use Symfony\Component\Console\Output\OutputInterface;
2121
use Symfony\Component\Console\Style\SymfonyStyle;
2222
use Symfony\Component\Messenger\Transport\Receiver\ListableReceiverInterface;
23-
use Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface;
23+
use Symfony\Component\Messenger\Transport\Receiver\MessageCountAwareInterface;
2424

2525
/**
2626
* @author Ryan Weaver <ryan@symfonycasts.com>
@@ -32,7 +32,8 @@ protected function configure(): void
3232
{
3333
$this
3434
->setDefinition([
35-
new InputArgument('id', InputArgument::REQUIRED | InputArgument::IS_ARRAY, 'Specific message id(s) to remove'),
35+
new InputArgument('id', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Specific message id(s) to remove'),
36+
new InputOption('all', null, InputOption::VALUE_NONE, 'Remove all failed messages from the transport'),
< 8000 code>3637
new InputOption('force', null, InputOption::VALUE_NONE, 'Force the operation without confirmation'),
3738
new InputOption('transport', null, InputOption::VALUE_OPTIONAL, 'Use a specific failure transport', self::DEFAULT_TRANSPORT_OPTION),
3839
new InputOption('show-messages', null, InputOption::VALUE_NONE, 'Display messages before removing it (if multiple ids are given)'),
@@ -43,6 +44,10 @@ protected function configure(): void
4344
<info>php %command.full_name% {id1} [{id2} ...]</info>
4445
4546
The specific ids can be found via the messenger:failed:show command.
47+
48+
You can remove all failed messages from the failure transport by using the "--all" option:
49+
50+
<info>php %command.full_name% --all</info>
4651
EOF
4752
)
4853
;
@@ -61,18 +66,32 @@ protected function execute(InputInterface $input, OutputInterface $output): int
6166

6267
$shouldForce = $input->getOption('force');
6368
$ids = (array) $input->getArgument('id');
64-
$shouldDisplayMessages = $input->getOption('show-messages') || 1 === \count($ids);
65-
$this->removeMessages($failureTransportName, $ids, $receiver, $io, $shouldForce, $shouldDisplayMessages);
69+
$shouldDeleteAllMessages = $input->getOption('all');
6670

67-
return 0;
68-
}
71+
$idsCount = \count($ids);
72+
if (!$shouldDeleteAllMessages && !$idsCount) {
73+
throw new RuntimeException('Please specify at least one message id. If you want to remove all failed messages, use the "--all" option.');
74+
} elseif ($shouldDeleteAllMessages && $idsCount) {
75+
throw new RuntimeException('You cannot specify message ids when using the "--all" option.');
76+
}
77+
78+
$shouldDisplayMessages = $input->getOption('show-messages') || 1 === $idsCount;
6979

70-
private function removeMessages(string $failureTransportName, array $ids, ReceiverInterface $receiver, SymfonyStyle $io, bool $shouldForce, bool $shouldDisplayMessages): void
71-
{
7280
if (!$receiver instanceof ListableReceiverInterface) {
7381
throw new RuntimeException(sprintf('The "%s" receiver does not support removing specific messages.', $failureTransportName));
7482
}
7583

84+
if ($shouldDeleteAllMessages) {
85+
$this->removeAllMessages($receiver, $io, $shouldForce, $shouldDisplayMessages);
86+
} else {
87+
$this->removeMessagesById($ids, $receiver, $io, $shouldForce, $shouldDisplayMessages);
88+
}
89+
90+
return 0;
91+
}
92+
93+
private function removeMessagesById(array $ids, ListableReceiverInterface $receiver, SymfonyStyle $io, bool $shouldForce, bool $shouldDisplayMessages): void
94+
{
7695
foreach ($ids as $id) {
7796
$this->phpSerializer?->acceptPhpIncompleteClass();
7897
try {
@@ -99,4 +118,31 @@ private function removeMessages(string $failureTransportName, array $ids, Receiv
99118
}
100119
}
101120
}
121+
122+
private function removeAllMessages(ListableReceiverInterface $receiver, SymfonyStyle $io, bool $shouldForce, bool $shouldDisplayMessages): void
123+
{
124+
if (!$shouldForce) {
125+
if ($receiver instanceof MessageCountAwareInterface) {
126+
$question = sprintf('Do you want to permanently remove all (%d) messages?', $receiver->getMessageCount());
127+
} else {
128+
$question = 'Do you want to permanently remove all failed messages?';
129+
}
130+
131+
if (!$io->confirm($question, false)) {
132+
return;
133+
}
134+
}
135+
136+
$count = 0;
137+
foreach ($receiver->all() as $envelope) {
138+
if ($shouldDisplayMessages) {
139+
$this->displaySingleMessage($envelope, $io);
140+
}
141+
142+
$receiver->reject($envelope);
143+
++$count;
144+
}
145+
146+
$io->note(sprintf('%d messages were removed.', $count));
147+
}
102148
}

src/Symfony/Component/Messenger/Tests/Command/FailedMessagesRemoveCommandTest.php

+100-2
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212
namespace Symfony\Component\Messenger\Tests\Command;
1313

1414
use PHPUnit\Framework\T 10000 estCase;
15+
use Symfony\Component\Console\Exception\RuntimeException;
1516
use Symfony\Component\Console\Tester\CommandCompletionTester;
1617
use Symfony\Component\Console\Tester\CommandTester;
1718
use Symfony\Component\DependencyInjection\ServiceLocator;
19+
use Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineReceiver;
1820
use Symfony\Component\Messenger\Command\FailedMessagesRemoveCommand;
1921
use Symfony\Component\Messenger\Envelope;
2022
use Symfony\Component\Messenger\Exception\InvalidArgumentException;
@@ -207,7 +209,7 @@ public function testCompleteId()
207209
$globalFailureReceiverName = 'failure_receiver';
208210

209211
$receiver = $this->createMock(ListableReceiverInterface::class);
210-
$receiver->expects($this->once())->method('all')->with(50)->willReturn([
212+
$receiver->expects($this->once())->method('all')->willReturn([
211213
Envelope::wrap(new \stdClass(), [new TransportMessageIdStamp('2ab50dfa1fbf')]),
212214
Envelope::wrap(new \stdClass(), [new TransportMessageIdStamp('78c2da843723')]),
213215
]);
@@ -233,7 +235,7 @@ public function testCompleteIdWithSpecifiedTransport()
233235
$anotherFailureReceiverName = 'another_receiver';
234236

235237
$receiver = $this->createMock(ListableReceiverInterface::class);
236-
$receiver->expects($this->once())->method('all')->with(50)->willReturn([
238+
$receiver->expects($this->once())->method('all')->willReturn([
237239
Envelope::wrap(new \stdClass(), [new TransportMessageIdStamp('2ab50dfa1fbf')]),
238240
Envelope::wrap(new \stdClass(), [new TransportMessageIdStamp('78c2da843723')]),
239241
]);
@@ -253,4 +255,100 @@ public function testCompleteIdWithSpecifiedTransport()
253255

254256
$this->assertSame(['2ab50dfa1fbf', '78c2da843723'], $suggestions);
255257
}
258+
259+
public function testOptionAllIsSetWithIdsThrows()
260+
{
261+
$globalFailureReceiverName = 'failure_receiver';
262+
263+
$serviceLocator = $this->createMock(ServiceLocator::class);
264+
$serviceLocator->expects($this->once())->method('has')->with($globalFailureReceiverName)->willReturn(true);
265+
$serviceLocator->expects($this->any())->method('get')->with($globalFailureReceiverName)->willReturn($this->createMock(ListableReceiverInterface::class));
266+
267+
$command = new FailedMessagesRemoveCommand('failure_receiver', $serviceLocator);
268+
$tester = new CommandTester($command);
269+
270+
$this->expectException(RuntimeException::class);
271+
$this->expectExceptionMessage('You cannot specify message ids when using the "--all" option.');
272+
$tester->execute(['id' => [20], '--all' => true]);
273+
}
274+
275+
public function testOptionAllIsSetWithoutForceAsksConfirmation()
276+
{
277+
$globalFailureReceiverName = 'failure_receiver';
278+
279+
$receiver = $this->createMock(ListableReceiverInterface::class);
280+
$serviceLocator = $this->createMock(ServiceLocator::class);
281+
$serviceLocator->expects($this->once())->method('has')->with($globalFailureReceiverName)->willReturn(true);
282+
$serviceLocator->expects($this->any())->method('get')->with($globalFailureReceiverName)->willReturn($receiver);
283+
284+
$command = new FailedMessagesRemoveCommand('failure_receiver', $serviceLocator);
285+
$tester = new CommandTester($command);
286+
287+
$tester->execute(['--all' => true]);
288+
289+
$this->assertSame(0, $tester->getStatusCode());
290+
$this->assertStringContainsString('Do you want to permanently remove all failed messages? (yes/no)', $tester->getDisplay());
291+
}
292+
293+
public function testOptionAllIsSetWithoutForceAsksConfirmationOnMessageCountAwareInterface()
294+
{
295+
$globalFailureReceiverName = 'failure_receiver';
296+
297+
$receiver = $this->createMock(DoctrineReceiver::class);
298+
$receiver->expects($this->once())->method('getMessageCount')->willReturn(2);
299+
300+
$serviceLocator = $this->createMock(ServiceLocator::class);
301+
$serviceLocator->expects($this->once())->method('has')->with($globalFailureReceiverName)->willReturn(true);
302+
$serviceLocator->expects($this->any())->method('get')->with($globalFailureReceiverName)->willReturn($receiver);
303+
304+
$command = new FailedMessagesRemoveCommand('failure_receiver', $serviceLocator);
305+
$tester = new CommandTester($command);
306+
307+
$tester->execute(['--all' => true]);
308+
309+
$this->assertSame(0, $tester->getStatusCode());
310+
$this->assertStringContainsString('Do you want to permanently remove all (2) messages? (yes/no)', $tester->getDisplay());
311+
}
312+
313+
public function testOptionAllIsNotSetNorIdsThrows()
314+
{
315+
$globalFailureReceiverName = 'failure_receiver';
316+
317+
$serviceLocator = $this->createMock(ServiceLocator::class);
318+
$serviceLocator->expects($this->once())->method('has')->with($globalFailureReceiverName)->willReturn(true);
319+
$serviceLocator->expects($this->any())->method('get')->with($globalFailureReceiverName)->willReturn($this->createMock(ListableReceiverInterface::class));
320+
321+
$command = new FailedMessagesRemoveCommand('failure_receiver', $serviceLocator);
322+
$tester = new CommandTester($command);
323+
324+
$this->expectException(RuntimeException::class);
325+
$this->expectExceptionMessage('Please specify at least one message id. If you want to remove all failed messages, use the "--all" option.');
326+
$tester->execute([]);
327+
}
328+
329+
public function testRemoveAllMessages()
330+
{
331+
$globalFailureReceiverName = 'failure_receiver';
332+
$receiver = $this->createMock(ListableReceiverInterface::class);
333+
334+
$series = [
335+
new Envelope(new \stdClass()),
336+
new Envelope(new \stdClass()),
337+
new Envelope(new \stdClass()),
338+
new Envelope(new \stdClass()),
339+
];
340+
341+
$receiver->expects($this->once())->method('all')->willReturn($series);
342+
343+
$serviceLocator = $this->createMock(ServiceLocator::class);
344+
$serviceLocator->expects($this->once())->method('has')->with($globalFailureReceiverName)->willReturn(true);
345+
$serviceLocator->expects($this->any())->method('get')->with($globalFailureReceiverName)->willReturn($receiver);
346+
347+
$command = new FailedMessagesRemoveCommand($globalFailureReceiverName, $serviceLocator);
348+
$tester = new CommandTester($command);
349+
$tester->execute(['--all' => true, '--force' => true, '--show-messages' => true]);
350+
351+
$this->assertStringContainsString('Failed Message Details', $tester->getDisplay());
352+
$this->assertStringContainsString('4 messages were removed.', $tester->getDisplay());
353+
}
256354
}

0 commit comments

Comments
 (0)
0