8000 [Messenger] Add the `--all` option to the `messenger:failed:remove` command by alexandre-daubois · Pull Request #51593 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[Messenger] Add the --all option to the messenger:failed:remove command #51593

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Symfony/Component/Messenger/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ CHANGELOG
* Deprecate `StopWorkerOnSignalsListener` in favor of using the `SignalableCommandInterface`
* Add `HandlerDescriptor::getOptions`
* Add support for multiple Redis Sentinel hosts
* Add `--all` option to the `messenger:failed:remove` command

6.3
---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Messenger\Transport\Receiver\ListableReceiverInterface;
use Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface;
use Symfony\Component\Messenger\Transport\Receiver\MessageCountAwareInterface;

/**
* @author Ryan Weaver <ryan@symfonycasts.com>
Expand All @@ -32,7 +32,8 @@ protected function configure(): void
{
$this
->setDefinition([
new InputArgument('id', InputArgument::REQUIRED | InputArgument::IS_ARRAY, 'Specific message id(s) to remove'),
new InputArgument('id', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Specific message id(s) to remove'),
new InputOption('all', null, InputOption::VALUE_NONE, 'Remove all failed messages from the transport'),
new InputOption('force', null, InputOption::VALUE_NONE, 'Force the operation without confirmation'),
new InputOption('transport', null, InputOption::VALUE_OPTIONAL, 'Use a specific failure transport', self::DEFAULT_TRANSPORT_OPTION),
new InputOption('show-messages', null, InputOption::VALUE_NONE, 'Display messages before removing it (if multiple ids are given)'),
Expand All @@ -43,6 +44,10 @@ protected function configure(): void
<info>php %command.full_name% {id1} [{id2} ...]</info>

The specific ids can be found via the messenger:failed:show command.

You can remove all failed messages from the failure transport by using the "--all" option:

<info>php %command.full_name% --all</info>
EOF
)
;
Expand All @@ -61,18 +66,32 @@ protected function execute(InputInterface $input, OutputInterface $output): int

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

return 0;
}
$idsCount = \count($ids);
if (!$shouldDeleteAllMessages && !$idsCount) {
throw new RuntimeException('Please specify at least one message id. If you want to remove all failed messages, use the "--all" option.');
} elseif ($shouldDeleteAllMessages && $idsCount) {
throw new RuntimeException('You cannot specify message ids when using the "--all" option.');
}

$shouldDisplayMessages = $input->getOption('show-messages') || 1 === $idsCount;

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

if ($shouldDeleteAllMessages) {
$this->removeAllMessages($receiver, $io, $shouldForce, $shouldDisplayMessages);
} else {
$this->removeMessagesById($ids, $receiver, $io, $shouldForce, $shouldDisplayMessages);
}

return 0;
}

private function removeMessagesById(array $ids, ListableReceiverInterface $receiver, SymfonyStyle $io, bool $shouldForce, bool $shouldDisplayMessages): void
{
foreach ($ids as $id) {
$this->phpSerializer?->acceptPhpIncompleteClass();
try {
Expand All @@ -99,4 +118,31 @@ private function removeMessages(string $failureTransportName, array $ids, Receiv
}
}
}

private function removeAllMessages(ListableReceiverInterface $receiver, SymfonyStyle $io, bool $shouldForce, bool $shouldDisplayMessages): void
{
if (!$shouldForce) {
if ($receiver instanceof MessageCountAwareInterface) {
$question = sprintf('Do you want to permanently remove all (%d) messages?', $receiver->getMessageCount());
} else {
$question = 'Do you want to permanently remove all failed messages?';
}

if (!$io->confirm($question, false)) {
return;
}
}

$count = 0;
foreach ($receiver->all() as $envelope) {
if ($shouldDisplayMessages) {
$this->displaySingleMessage($envelope, $io);
}

$receiver->reject($envelope);
++$count;
}

$io->note(sprintf('%d messages were removed.', $count));
}
}
67E6
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@
namespace Symfony\Component\Messenger\Tests\Command;

use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Tester\CommandCompletionTester;
use Symfony\Component\Console\Tester\CommandTester;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineReceiver;
use Symfony\Component\Messenger\Command\FailedMessagesRemoveCommand;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Exception\InvalidArgumentException;
Expand Down Expand Up @@ -207,7 +209,7 @@ public function testCompleteId()
$globalFailureReceiverName = 'failure_receiver';

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

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

$this->assertSame(['2ab50dfa1fbf', '78c2da843723'], $suggestions);
}

public function testOptionAllIsSetWithIdsThrows()
{
$globalFailureReceiverName = 'failure_receiver';

$serviceLocator = $this->createMock(ServiceLocator::class);
$serviceLocator->expects($this->once())->method('has')->with($globalFailureReceiverName)->willReturn(true);
$serviceLocator->expects($this->any())->method('get')->with($globalFailureReceiverName)->willReturn($this->createMock(ListableReceiverInterface::class));

$command = new FailedMessagesRemoveCommand('failure_receiver', $serviceLocator);
$tester = new CommandTester($command);

$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('You cannot specify message ids when using the "--all" option.');
$tester->execute(['id' => [20], '--all' => true]);
}

public function testOptionAllIsSetWithoutForceAsksConfirmation()
{
$globalFailureReceiverName = 'failure_receiver';

$receiver = $this->createMock(ListableReceiverInterface::class);
$serviceLocator = $this->createMock(ServiceLocator::class);
$serviceLocator->expects($this->once())->method('has')->with($globalFailureReceiverName)->willReturn(true);
$serviceLocator->expects($this->any())->method('get')->with($globalFailureReceiverName)->willReturn($receiver);

$command = new FailedMessagesRemoveCommand('failure_receiver', $serviceLocator);
$tester = new CommandTester($command);

$tester->execute(['--all' => true]);

$this->assertSame(0, $tester->getStatusCode());
$this->assertStringContainsString('Do you want to permanently remove all failed messages? (yes/no)', $tester->getDisplay());
}

public function testOptionAllIsSetWithoutForceAsksConfirmationOnMessageCountAwareInterface()
{
$globalFailureReceiverName = 'failure_receiver';

$receiver = $this->createMock(DoctrineReceiver::class);
$receiver->expects($this->once())->method('getMessageCount')->willReturn(2);

$serviceLocator = $this->createMock(ServiceLocator::class);
$serviceLocator->expects($this->once())->method('has')->with($globalFailureReceiverName)->willReturn(true);
$serviceLocator->expects($this->any())->method('get')->with($globalFailureReceiverName)->willReturn($receiver);

$command = new FailedMessagesRemoveCommand('failure_receiver', $serviceLocator);
$tester = new CommandTester($command);

$tester->execute(['--all' => true]);

$this->assertSame(0, $tester->getStatusCode());
$this->assertStringContainsString('Do you want to permanently remove all (2) messages? (yes/no)', $tester->getDisplay());
}

public function testOptionAllIsNotSetNorIdsThrows()
{
$globalFailureReceiverName = 'failure_receiver';

$serviceLocator = $this->createMock(ServiceLocator::class);
$serviceLocator->expects($this->once())->method('has')->with($globalFailureReceiverName)->willReturn(true);
$serviceLocator->expects($this->any())->method('get')->with($globalFailureReceiverName)->willReturn($this->createMock(ListableReceiverInterface::class));

$command = new FailedMessagesRemoveCommand('failure_receiver', $serviceLocator);
$tester = new CommandTester($command);

$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('Please specify at least one message id. If you want to remove all failed messages, use the "--all" option.');
$tester->execute([]);
}

public function testRemoveAllMessages()
{
$globalFailureReceiverName = 'failure_receiver';
$receiver = $this->createMock(ListableReceiverInterface::class);

$series = [
new Envelope(new \stdClass()),
new Envelope(new \stdClass()),
new Envelope(new \stdClass()),
new Envelope(new \stdClass()),
];

$receiver->expects($this->once())->method('all')->willReturn($series);

$serviceLocator = $this->createMock(ServiceLocator::class);
$serviceLocator->expects($this->once())->method('has')->with($globalFailureReceiverName)->willReturn(true);
$serviceLocator->expects($this->any())->method('get')->with($globalFailureReceiverName)->willReturn($receiver);

$command = new FailedMessagesRemoveCommand($globalFailureReceiverName, $serviceLocator);
$tester = new CommandTester($command);
$tester->execute(['--all' => true, '--force' => true, '--show-messages' => true]);

$this->assertStringContainsString('Failed Message Details', $tester->getDisplay());
$this->assertStringContainsString('4 messages were removed.', $tester->getDisplay());
}
}
0