Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
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 @@ -4,6 +4,7 @@ CHANGELOG
8.2
---

* Add `FailedMessageRepository` and `FailedMessageFilter` to list, inspect, remove and redispatch failed messages outside the console
* Add `MessengerBundle`, which provides the `messenger` configuration and the services previously provided by `FrameworkBundle` under `framework.messenger`
* Add claim check support with `ClaimCheckSerializer` and PSR-6 cache pools
* Add routing and failure transport information and a `--message` option to the `debug:messenger` command
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,12 @@
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Exception\InvalidArgumentException;
use Symfony\Component\Messenger\Exception\MessageDecodingFailedException;
use Symfony\Component\Messenger\Failure\FailedMessageFilter;
use Symfony\Component\Messenger\Failure\FailedMessageRepository;
use Symfony\Component\Messenger\Stamp\ErrorDetailsStamp;
use Symfony\Component\Messenger\Stamp\MessageDecodingFailedStamp;
use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
use Symfony\Component\Messenger\Stamp\SentToFailureTransportStamp;
use Symfony\Component\Messenger\Stamp\TransportMessageIdStamp;
use Symfony\Component\Messenger\Transport\Receiver\ListableReceiverInterface;
use Symfony\Component\Messenger\Transport\Receiver\MessageCountAwareInterface;
use Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface;
use Symfony\Component\Messenger\Transport\Serialization\PhpSerializer;
use Symfony\Component\VarDumper\Caster\Caster;
use Symfony\Component\VarDumper\Caster\TraceStub;
Expand All @@ -48,27 +46,24 @@ abstract class AbstractFailedMessagesCommand extends Command
{
protected const DEFAULT_TRANSPORT_OPTION = 'choose';

protected FailedMessageRepository $repository;

public function __construct(
private ?string $globalFailureReceiverName,
?string $globalFailureReceiverName,
/**
* @var ServiceProviderInterface<ReceiverInterface>
* @var ServiceProviderInterface<\Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface>
*/
protected ServiceProviderInterface $failureTransports,
protected ?PhpSerializer $phpSerializer = null,
) {
parent::__construct();
}

protected function getGlobalFailureReceiverName(): ?string
{
return $this->globalFailureReceiverName;
$this->repository = new FailedMessageRepository($failureTransports, $globalFailureReceiverName, $phpSerializer);
}

protected function getMessageId(Envelope $envelope): mixed
protected function getGlobalFailureReceiverName(): ?string
{
$stamp = $envelope->last(TransportMessageIdStamp::class);

return $stamp?->getId();
return $this->repository->getGlobalTransportName();
}

protected function displaySingleMessage(Envelope $envelope, SymfonyStyle $io, ?SymfonyStyle $errorIo = null): void
Expand All @@ -85,7 +80,7 @@ protected function displaySingleMessage(Envelope $envelope, SymfonyStyle $io, ?S
['Class', $messageClass],
];

if (null !== $id = $this->getMessageId($envelope)) {
if (null !== $id = FailedMessageRepository::getMessageId($envelope)) {
$rows[] = ['Message Id', $id];
}

Expand Down Expand Up @@ -128,85 +123,35 @@ protected function displaySingleMessage(Envelope $envelope, SymfonyStyle $io, ?S
}
}

protected function printPendingMessagesMessage(ReceiverInterface $receiver, SymfonyStyle $io): void
protected function printPendingMessagesMessage(?string $failureTransportName, SymfonyStyle $io): void
{
if ($receiver instanceof MessageCountAwareInterface) {
if (1 === $receiver->getMessageCount()) {
$io->writeln('There is <info>1</info> message pending in the failure transport.');
} else {
$io->writeln(\sprintf('There are <info>%d</info> messages pending in the failure transport.', $receiver->getMessageCount()));
}
if (null === $count = $this->repository->count($failureTransportName)) {
return;
}
}

/**
* @param bool $hasIds Whether explicit message ids were given, which the filters cannot be combined with
*
* @return array{?string, ?\DateTimeImmutable, ?\DateTimeImmutable} The class name, the earliest and the latest failure time to select
*/
protected function getFilters(InputInterface $input, bool $hasIds): array
{
$classFilter = $input->getOption('class-filter');
$failedAfter = $this->getDateOption($input, 'failed-after');
$failedBefore = $this->getDateOption($input, 'failed-before');

if ($hasIds && (null !== $classFilter || null !== $failedAfter || null !== $failedBefore)) {
throw new RuntimeException('You cannot specify message ids when using the "--class-filter", "--failed-after" or "--failed-before" options.');
if (1 === $count) {
$io->writeln('There is <info>1</info> message pending in the failure transport.');
} else {
$io->writeln(\sprintf('There are <info>%d</info> messages pending in the failure transport.', $count));
}

return [$classFilter, $failedAfter, $failedBefore];
}

/**
* @return list<mixed> The ids of the messages matching every given filter
* @param bool $hasIds Whether explicit message ids were given, which the filters cannot be combined with
*/
protected function getMessageIdsByFilter(ListableReceiverInterface $receiver, ?string $classFilter, ?\DateTimeImmutable $failedAfter, ?\DateTimeImmutable $failedBefore): array
protected function getFilter(InputInterface $input, bool $hasIds): FailedMessageFilter
{
$ids = [];
$filter = new FailedMessageFilter(
$input->getOption('class-filter'),
$this->getDateOption($input, 'failed-after'),
$this->getDateOption($input, 'failed-before'),
);

$this->phpSerializer?->acceptPhpIncompleteClass();
try {
foreach ($receiver->all() as $envelope) {
if ($this->matchesFilter($envelope, $classFilter, $failedAfter, $failedBefore)) {
$ids[] = $this->getMessageId($envelope);
}
}
} finally {
$this->phpSerializer?->rejectPhpIncompleteClass();
}

return $ids;
}

protected function matchesFilter(Envelope $envelope, ?string $classFilter, ?\DateTimeImmutable $failedAfter, ?\DateTimeImmutable $failedBefore): bool
{
if (null !== $classFilter && $classFilter !== $envelope->getMessage()::class) {
return false;
}

if (null === $failedAfter && null === $failedBefore) {
return true;
}

// messages that were never redelivered have no known failure time, so no time window can select them
if (null === $failedAt = $envelope->last(RedeliveryStamp::class)?->getRedeliveredAt()) {
return false;
}

return (null === $failedAfter || $failedAt >= $failedAfter) && (null === $failedBefore || $failedAt <= $failedBefore);
}

protected function getReceiver(?string $name = null): ReceiverInterface
{
if (null === $name ??= $this->globalFailureReceiverName) {
throw new InvalidArgumentException(\sprintf('No default failure transport is defined. Available transports are: "%s".', implode('", "', array_keys($this->failureTransports->getProvidedServices()))));
}

if (!$this->failureTransports->has($name)) {
throw new InvalidArgumentException(\sprintf('The "%s" failure transport was not found. Available transports are: "%s".', $name, implode('", "', array_keys($this->failureTransports->getProvidedServices()))));
if ($hasIds && !$filter->isEmpty()) {
throw new RuntimeException('You cannot specify message ids when using the "--class-filter", "--failed-after" or "--failed-before" options.');
}

return $this->failureTransports->get($name);
return $filter;
}

private function getDateOption(InputInterface $input, string $option): ?\DateTimeImmutable
Expand Down Expand Up @@ -247,7 +192,7 @@ private function createCloner(): ?ClonerInterface

protected function printWarningAvailableFailureTransports(SymfonyStyle $io, ?string $failureTransportName): void
{
$failureTransports = array_keys($this->failureTransports->getProvidedServices());
$failureTransports = $this->repository->getTransportNames();
$failureTransportsCount = \count($failureTransports);
if ($failureTransportsCount > 1) {
$io->writeln([
Expand All @@ -261,7 +206,7 @@ protected function printWarningAvailableFailureTransports(SymfonyStyle $io, ?str

protected function interactiveChooseFailureTransport(SymfonyStyle $io): string
{
$failedTransports = array_keys($this->failureTransports->getProvidedServices());
$failedTransports = $this->repository->getTransportNames();
$question = new ChoiceQuestion('Select failed transport:', $failedTransports, 0);
$question->setMultiselect(false);

Expand All @@ -271,23 +216,22 @@ protected function interactiveChooseFailureTransport(SymfonyStyle $io): string
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->mustSuggestOptionValuesFor('transport')) {
$suggestions->suggestValues(array_keys($this->failureTransports->getProvidedServices()));
$suggestions->suggestValues($this->repository->getTransportNames());

return;
}

if ($input->mustSuggestArgumentValuesFor('id')) {
$transport = $input->getOption('transport');
$transport = self::DEFAULT_TRANSPORT_OPTION === $transport ? $this->getGlobalFailureReceiverName() : $transport;
$receiver = $this->getReceiver($transport);

if (!$receiver instanceof ListableReceiverInterface) {
if (!$this->repository->supportsListing($transport)) {
return;
}

$ids = [];
foreach ($receiver->all(50) as $envelope) {
$ids[] = $this->getMessageId($envelope);
foreach ($this->repository->all($transport, limit: 50) as $envelope) {
$ids[] = FailedMessageRepository::getMessageId($envelope);
}
$suggestions->suggestValues($ids);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@
use Symfony\Component\Console\Input\InputOption;
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\MessageCountAwareInterface;
use Symfony\Component\Messenger\Failure\FailedMessageRepository;

/**
* @author Ryan Weaver <[email protected]>
Expand Down Expand Up @@ -74,22 +73,20 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$errorIo = $io->getErrorStyle();

$ids = (array) $input->getArgument('id');
[$classFilter, $failedAfter, $failedBefore] = $this->getFilters($input, (bool) $ids);
$hasFilters = null !== $classFilter || null !== $failedAfter || null !== $failedBefore;
$filter = $this->getFilter($input, (bool) $ids);
$hasFilters = !$filter->isEmpty();

$failureTransportName = $input->getOption('transport');
if (self::DEFAULT_TRANSPORT_OPTION === $failureTransportName) {
$failureTransportName = $this->getGlobalFailureReceiverName();
}

$receiver = $this->getReceiver($failureTransportName);

$shouldForce = $input->getOption('force');
$shouldDeleteAllMessages = $input->getOption('all');

$idsCount = \count($ids);

if (!$receiver instanceof ListableReceiverInterface) {
if (!$this->repository->supportsListing($failureTransportName)) {
throw new RuntimeException(\sprintf('The "%s" receiver does not support removing specific messages.', $failureTransportName));
}

Expand All @@ -98,7 +95,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}

if ($hasFilters) {
$ids = $this->getMessageIdsByFilter($receiver, $classFilter, $failedAfter, $failedBefore);
$ids = [];
foreach ($this->repository->all($failureTransportName, $filter) as $envelope) {
$ids[] = FailedMessageRepository::getMessageId($envelope);
}
$idsCount = \count($ids);

if (!$idsCount) {
Expand All @@ -119,23 +119,18 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$shouldDisplayMessages = $input->getOption('show-messages') || 1 === $idsCount;

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

return 0;
}

private function removeMessagesById(array $ids, ListableReceiverInterface $receiver, SymfonyStyle $io, SymfonyStyle $errorIo, bool $shouldForce, bool $shouldDisplayMessages): void
private function removeMessagesById(array $ids, string $failureTransportName, SymfonyStyle $io, SymfonyStyle $errorIo, bool $shouldForce, bool $shouldDisplayMessages): void
{
foreach ($ids as $id) {
$this->phpSerializer?->acceptPhpIncompleteClass();
try {
$envelope = $receiver->find($id);
} finally {
$this->phpSerializer?->rejectPhpIncompleteClass();
}
$envelope = $this->repository->find($id, $failureTransportName);

if (null === $envelope) {
$errorIo->error(\sprintf('The message with id "%s" was not found.', $id));
Expand All @@ -147,7 +142,7 @@ private function removeMessagesById(array $ids, ListableReceiverInterface $recei
}

if ($shouldForce || $errorIo->confirm('Do you want to permanently remove this message?', false)) {
$receiver->reject($envelope);
$this->repository->remove($envelope, $failureTransportName);

$io->success(\sprintf('Message with id %s removed.', $id));
} else {
Expand All @@ -156,11 +151,11 @@ private function removeMessagesById(array $ids, ListableReceiverInterface $recei
}
}

private function removeAllMessages(ListableReceiverInterface $receiver, SymfonyStyle $io, SymfonyStyle $errorIo, bool $shouldForce, bool $shouldDisplayMessages): void
private function removeAllMessages(string $failureTransportName, SymfonyStyle $io, SymfonyStyle $errorIo, bool $shouldForce, bool $shouldDisplayMessages): void
{
if (!$shouldForce) {
if ($receiver instanceof MessageCountAwareInterface) {
$question = \sprintf('Do you want to permanently remove all (%d) messages?', $receiver->getMessageCount());
if (null !== $pending = $this->repository->count($failureTransportName)) {
$question = \sprintf('Do you want to permanently remove all (%d) messages?', $pending);
} else {
$question = 'Do you want to permanently remove all failed messages?';
}
Expand All @@ -171,12 +166,12 @@ private function removeAllMessages(ListableReceiverInterface $receiver, SymfonyS
}

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

$receiver->reject($envelope);
$this->repository->remove($envelope, $failureTransportName);
++$count;
}

Expand Down
Loading