vendor/symfony/messenger/Command/AbstractFailedMessagesCommand.php line 150

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Component\Messenger\Command;
  11. use Symfony\Component\Console\Command\Command;
  12. use Symfony\Component\Console\Completion\CompletionInput;
  13. use Symfony\Component\Console\Completion\CompletionSuggestions;
  14. use Symfony\Component\Console\Helper\Dumper;
  15. use Symfony\Component\Console\Question\ChoiceQuestion;
  16. use Symfony\Component\Console\Style\SymfonyStyle;
  17. use Symfony\Component\ErrorHandler\Exception\FlattenException;
  18. use Symfony\Component\Messenger\Envelope;
  19. use Symfony\Component\Messenger\Exception\InvalidArgumentException;
  20. use Symfony\Component\Messenger\Stamp\ErrorDetailsStamp;
  21. use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
  22. use Symfony\Component\Messenger\Stamp\SentToFailureTransportStamp;
  23. use Symfony\Component\Messenger\Stamp\TransportMessageIdStamp;
  24. use Symfony\Component\Messenger\Transport\Receiver\ListableReceiverInterface;
  25. use Symfony\Component\Messenger\Transport\Receiver\MessageCountAwareInterface;
  26. use Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface;
  27. use Symfony\Component\VarDumper\Caster\Caster;
  28. use Symfony\Component\VarDumper\Caster\TraceStub;
  29. use Symfony\Component\VarDumper\Cloner\ClonerInterface;
  30. use Symfony\Component\VarDumper\Cloner\Stub;
  31. use Symfony\Component\VarDumper\Cloner\VarCloner;
  32. use Symfony\Contracts\Service\ServiceProviderInterface;
  33. /**
  34.  * @author Ryan Weaver <ryan@symfonycasts.com>
  35.  *
  36.  * @internal
  37.  */
  38. abstract class AbstractFailedMessagesCommand extends Command
  39. {
  40.     protected const DEFAULT_TRANSPORT_OPTION 'choose';
  41.     protected $failureTransports;
  42.     private ?string $globalFailureReceiverName;
  43.     public function __construct(?string $globalFailureReceiverNameServiceProviderInterface $failureTransports)
  44.     {
  45.         $this->failureTransports $failureTransports;
  46.         $this->globalFailureReceiverName $globalFailureReceiverName;
  47.         parent::__construct();
  48.     }
  49.     protected function getGlobalFailureReceiverName(): ?string
  50.     {
  51.         return $this->globalFailureReceiverName;
  52.     }
  53.     protected function getMessageId(Envelope $envelope): mixed
  54.     {
  55.         /** @var TransportMessageIdStamp $stamp */
  56.         $stamp $envelope->last(TransportMessageIdStamp::class);
  57.         return $stamp?->getId();
  58.     }
  59.     protected function displaySingleMessage(Envelope $envelopeSymfonyStyle $io)
  60.     {
  61.         $io->title('Failed Message Details');
  62.         /** @var SentToFailureTransportStamp|null $sentToFailureTransportStamp */
  63.         $sentToFailureTransportStamp $envelope->last(SentToFailureTransportStamp::class);
  64.         /** @var RedeliveryStamp|null $lastRedeliveryStamp */
  65.         $lastRedeliveryStamp $envelope->last(RedeliveryStamp::class);
  66.         /** @var ErrorDetailsStamp|null $lastErrorDetailsStamp */
  67.         $lastErrorDetailsStamp $envelope->last(ErrorDetailsStamp::class);
  68.         $rows = [
  69.             ['Class'\get_class($envelope->getMessage())],
  70.         ];
  71.         if (null !== $id $this->getMessageId($envelope)) {
  72.             $rows[] = ['Message Id'$id];
  73.         }
  74.         if (null === $sentToFailureTransportStamp) {
  75.             $io->warning('Message does not appear to have been sent to this transport after failing');
  76.         } else {
  77.             $failedAt '';
  78.             $errorMessage '';
  79.             $errorCode '';
  80.             $errorClass '(unknown)';
  81.             if (null !== $lastRedeliveryStamp) {
  82.                 $failedAt $lastRedeliveryStamp->getRedeliveredAt()->format('Y-m-d H:i:s');
  83.             }
  84.             if (null !== $lastErrorDetailsStamp) {
  85.                 $errorMessage $lastErrorDetailsStamp->getExceptionMessage();
  86.                 $errorCode $lastErrorDetailsStamp->getExceptionCode();
  87.                 $errorClass $lastErrorDetailsStamp->getExceptionClass();
  88.             }
  89.             $rows array_merge($rows, [
  90.                 ['Failed at'$failedAt],
  91.                 ['Error'$errorMessage],
  92.                 ['Error Code'$errorCode],
  93.                 ['Error Class'$errorClass],
  94.                 ['Transport'$sentToFailureTransportStamp->getOriginalReceiverName()],
  95.             ]);
  96.         }
  97.         $io->table([], $rows);
  98.         /** @var RedeliveryStamp[] $redeliveryStamps */
  99.         $redeliveryStamps $envelope->all(RedeliveryStamp::class);
  100.         $io->writeln(' Message history:');
  101.         foreach ($redeliveryStamps as $redeliveryStamp) {
  102.             $io->writeln(sprintf('  * Message failed at <info>%s</info> and was redelivered'$redeliveryStamp->getRedeliveredAt()->format('Y-m-d H:i:s')));
  103.         }
  104.         $io->newLine();
  105.         if ($io->isVeryVerbose()) {
  106.             $io->title('Message:');
  107.             $dump = new Dumper($ionull$this->createCloner());
  108.             $io->writeln($dump($envelope->getMessage()));
  109.             $io->title('Exception:');
  110.             $flattenException $lastErrorDetailsStamp?->getFlattenException();
  111.             $io->writeln(null === $flattenException '(no data)' $dump($flattenException));
  112.         } else {
  113.             $io->writeln(' Re-run command with <info>-vv</info> to see more message & error details.');
  114.         }
  115.     }
  116.     protected function printPendingMessagesMessage(ReceiverInterface $receiverSymfonyStyle $io)
  117.     {
  118.         if ($receiver instanceof MessageCountAwareInterface) {
  119.             if (=== $receiver->getMessageCount()) {
  120.                 $io->writeln('There is <comment>1</comment> message pending in the failure transport.');
  121.             } else {
  122.                 $io->writeln(sprintf('There are <comment>%d</comment> messages pending in the failure transport.'$receiver->getMessageCount()));
  123.             }
  124.         }
  125.     }
  126.     protected function getReceiver(string $name null): ReceiverInterface
  127.     {
  128.         if (null === $name ??= $this->globalFailureReceiverName) {
  129.             throw new InvalidArgumentException(sprintf('No default failure transport is defined. Available transports are: "%s".'implode('", "'array_keys($this->failureTransports->getProvidedServices()))));
  130.         }
  131.         if (!$this->failureTransports->has($name)) {
  132.             throw new InvalidArgumentException(sprintf('The "%s" failure transport was not found. Available transports are: "%s".'$nameimplode('", "'array_keys($this->failureTransports->getProvidedServices()))));
  133.         }
  134.         return $this->failureTransports->get($name);
  135.     }
  136.     private function createCloner(): ?ClonerInterface
  137.     {
  138.         if (!class_exists(VarCloner::class)) {
  139.             return null;
  140.         }
  141.         $cloner = new VarCloner();
  142.         $cloner->addCasters([FlattenException::class => function (FlattenException $flattenException, array $aStub $stub): array {
  143.             $stub->class $flattenException->getClass();
  144.             return [
  145.                 Caster::PREFIX_VIRTUAL.'message' => $flattenException->getMessage(),
  146.                 Caster::PREFIX_VIRTUAL.'code' => $flattenException->getCode(),
  147.                 Caster::PREFIX_VIRTUAL.'file' => $flattenException->getFile(),
  148.                 Caster::PREFIX_VIRTUAL.'line' => $flattenException->getLine(),
  149.                 Caster::PREFIX_VIRTUAL.'trace' => new TraceStub($flattenException->getTrace()),
  150.             ];
  151.         }]);
  152.         return $cloner;
  153.     }
  154.     protected function printWarningAvailableFailureTransports(SymfonyStyle $io, ?string $failureTransportName): void
  155.     {
  156.         $failureTransports array_keys($this->failureTransports->getProvidedServices());
  157.         $failureTransportsCount \count($failureTransports);
  158.         if ($failureTransportsCount 1) {
  159.             $io->writeln([
  160.                 sprintf('> Loading messages from the <comment>global</comment> failure transport <comment>%s</comment>.'$failureTransportName),
  161.                 '> To use a different failure transport, pass <comment>--transport=</comment>.',
  162.                 sprintf('> Available failure transports are: <comment>%s</comment>'implode(', '$failureTransports)),
  163.                 "\n",
  164.             ]);
  165.         }
  166.     }
  167.     protected function interactiveChooseFailureTransport(SymfonyStyle $io)
  168.     {
  169.         $failedTransports array_keys($this->failureTransports->getProvidedServices());
  170.         $question = new ChoiceQuestion('Select failed transport:'$failedTransports0);
  171.         $question->setMultiselect(false);
  172.         return $io->askQuestion($question);
  173.     }
  174.     public function complete(CompletionInput $inputCompletionSuggestions $suggestions): void
  175.     {
  176.         if ($input->mustSuggestOptionValuesFor('transport')) {
  177.             $suggestions->suggestValues(array_keys($this->failureTransports->getProvidedServices()));
  178.             return;
  179.         }
  180.         if ($input->mustSuggestArgumentValuesFor('id')) {
  181.             $transport $input->getOption('transport');
  182.             $transport self::DEFAULT_TRANSPORT_OPTION === $transport $this->getGlobalFailureReceiverName() : $transport;
  183.             $receiver $this->getReceiver($transport);
  184.             if (!$receiver instanceof ListableReceiverInterface) {
  185.                 return;
  186.             }
  187.             $ids = [];
  188.             foreach ($receiver->all(50) as $envelope) {
  189.                 $ids[] = $this->getMessageId($envelope);
  190.             }
  191.             $suggestions->suggestValues($ids);
  192.             return;
  193.         }
  194.     }
  195. }