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

Skip to content

Commit 3a6080f

Browse files
committed
[WIP] Adding failed transport support
1 parent 65b46a5 commit 3a6080f

15 files changed

+419
-30
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,6 +1195,10 @@ function ($a) {
11951195
->end()
11961196
->end()
11971197
->end()
1198+
->scalarNode('failure_transport')
1199+
->defaultNull()
1200+
->info('Transport name to send failed messages to (after all retries have failed).')
1201+
->end()
11981202
->scalarNode('default_bus')->defaultNull()->end()
11991203
->arrayNode('buses')
12001204
->defaultValue(['messenger.bus.default' => ['default_middleware' => true, 'middleware' => []]])

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@
4040
use Symfony\Component\Console\Application;
4141
use Symfony\Component\Console\Command\Command;
4242
use Symfony\Component\DependencyInjection\Alias;
43-
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
4443
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
4544
use Symfony\Component\DependencyInjection\ChildDefinition;
45+
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
4646
use Symfony\Component\DependencyInjection\ContainerBuilder;
4747
use Symfony\Component\DependencyInjection\ContainerInterface;
4848
use Symfony\Component\DependencyInjection\Definition;
@@ -1724,22 +1724,37 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder
17241724
if ('*' !== $message && !class_exists($message) && !interface_exists($message, false)) {
17251725
throw new LogicException(sprintf('Invalid Messenger routing configuration: class or interface "%s" not found.', $message));
17261726
}
1727-
$senders = [];
1727+
1728+
// make sure senderAliases contains all senders
17281729
foreach ($messageConfiguration['senders'] as $sender) {
1729-
$senders[$sender] = new Reference($senderAliases[$sender] ?? $sender);
1730+
if (!isset($senderAliases[$sender])) {
1731+
$senderAliases[$sender] = $sender;
1732+
}
17301733
}
17311734

1732-
$messageToSendersMapping[$message] = new IteratorArgument($senders);
1735+
$messageToSendersMapping[$message] = $messageConfiguration['senders'];
17331736
$messagesToSendAndHandle[$message] = $messageConfiguration['send_and_handle'];
17341737
}
17351738

1739+
$senderReferences = [];
1740+
foreach ($senderAliases as $alias => $serviceId) {
1741+
$senderReferences[$alias] = new Reference($serviceId);
1742+
}
1743+
17361744
$container->getDefinition('messenger.senders_locator')
17371745
->replaceArgument(0, $messageToSendersMapping)
1738-
->replaceArgument(1, $messagesToSendAndHandle)
1746+
->replaceArgument(1, ServiceLocatorTagPass::register($container, $senderReferences))
1747+
->replaceArgument(2, $messagesToSendAndHandle)
17391748
;
17401749

17411750
$container->getDefinition('messenger.retry_strategy_locator')
17421751
->replaceArgument(0, $transportRetryReferences);
1752+
1753+
$container->getDefinition('messenger.failure.send_failed_message_to_failed_transport_listener')
1754+
->replaceArgument(1, $config['failure_transport']);
1755+
1756+
$container->getDefinition('console.command.messenger_failed_messages_retry')
1757+
->replaceArgument(0, $config['failure_transport']);
17431758
}
17441759

17451760
private function registerCacheConfiguration(array $config, ContainerBuilder $container)

src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,17 @@
109109
<tag name="console.command" command="messenger:stop-workers" />
110110
</service>
111111

112+
<service id="console.command.messenger_failed_messages_retry" class="Symfony\Component\Messenger\Command\FailedMessagesRetryCommand">
113+
<argument /> <!-- Receiver name -->
114+
<argument /> <!-- Receiver locator -->
115+
<argument /> <!-- Message bus locator -->
116+
<argument type="service" id="event_dispatcher" />
117+
<argument type="service" id="logger" on-invalid="null" />
118+
119+
<tag name="console.command" command="messenger:failed:retry" />
120+
<tag name="monolog.logger" channel="messenger" />
121+
</service>
122+
112123
<service id="console.command.router_debug" class="Symfony\Bundle\FrameworkBundle\Command\RouterDebugCommand">
113124
<argument type="service" id="router" />
114125
<argument type="service" id="debug.file_link_formatter" on-invalid="null" />

src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.xml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99

1010
<!-- Asynchronous -->
1111
<service id="messenger.senders_locator" class="Symfony\Component\Messenger\Transport\Sender\SendersLocator">
12-
<argument type="collection" /> <!-- Per message sender iterators -->
12+
<argument type="collection" /> <!-- Per message senders map -->
13+
<argument /> <!-- senders locator -->
1314
<argument type="collection" /> <!-- Messages to send and handle -->
1415
</service>
1516
<service id="messenger.middleware.send_message" class="Symfony\Component\Messenger\Middleware\SendMessageMiddleware">
@@ -87,5 +88,14 @@
8788
<argument /> <!-- multiplier -->
8889
<argument /> <!-- max delay ms -->
8990
</service>
91+
92+
<!-- failed handling -->
93+
<service id="messenger.failure.send_failed_message_to_failed_transport_listener" class="Symfony\Component\Messenger\EventListener\SendFailedMessageToFailedTransportListener">
94+
<tag name="kernel.event_subscriber" />
95+
<tag name="monolog.logger" channel="messenger" />
96+
<argument /> <!-- Message bus locator -->
97+
<argument /> <!-- Failed transport name -->
98+
<argument type="service" id="logger" on-invalid="ignore" />
99+
</service>
90100
</services>
91101
</container>

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class ConsumeMessagesCommand extends Command
4949
/** @var CacheItemPoolInterface|null */
5050
private $restartSignalCachePool;
5151

52-
public function __construct(ContainerInterface $busLocator, ContainerInterface $receiverLocator, LoggerInterface $logger = null, array $receiverNames = [], /* ContainerInterface */ $retryStrategyLocator = null, EventDispatcherInterface $eventDispatcher = null)
52+
public function __construct(ContainerInterface $busLocator, ContainerInterface $receiverLocatorLocator, LoggerInterface $logger = null, array $receiverNames = [], /* ContainerInterface */$retryStrategyLocator = null, EventDispatcherInterface $eventDispatcher = null)
5353
{
5454
if (\is_array($retryStrategyLocator)) {
5555
@trigger_error(sprintf('The 5th argument of the class "%s" should be a retry-strategy locator, an array of bus names as a value is deprecated since Symfony 4.3.', __CLASS__), E_USER_DEPRECATED);
@@ -58,7 +58,7 @@ public function __construct(ContainerInterface $busLocator, ContainerInterface $
5858
}
5959

6060
$this->busLocator = $busLocator;
61-
$this->receiverLocator = $receiverLocator;
61+
$this->receiverLocator = $receiverLocatorLocator;
6262
$this->logger = $logger;
6363
$this->receiverNames = $receiverNames;
6464
$this->retryStrategyLocator = $retryStrategyLocator;
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Messenger\Command;
13+
14+
use Psr\Container\ContainerInterface;
15+
use Psr\Log\LoggerInterface;
16+
use Symfony\Component\Console\Command\Command;
17+
use Symfony\Component\Console\Exception\RuntimeException;
18+
use Symfony\Component\Console\Input\InputArgument;
19+
use Symfony\Component\Console\Input\InputInterface;
20+
use Symfony\Component\Console\Output\ConsoleOutputInterface;
21+
use Symfony\Component\Console\Output\OutputInterface;
22+
use Symfony\Component\Console\Style\SymfonyStyle;
23+
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
24+
use Symfony\Component\Messenger\Envelope;
25+
use Symfony\Component\Messenger\Event\WorkerMessageReceivedEvent;
26+
use Symfony\Component\Messenger\RoutableMessageBus;
27+
use Symfony\Component\Messenger\Transport\Receiver\MessageCountAwareInterface;
28+
use Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface;
29+
use Symfony\Component\Messenger\Worker;
30+
31+
/**
32+
* @author Ryan Weaver <[email protected]>
33+
*
34+
* @experimental in 4.3
35+
*/
36+
class FailedMessagesRetryCommand extends Command
37+
{
38+
protected static $defaultName = 'messenger:failed:retry';
39+
40+
private $receiverName;
41+
private $receiverLocator;
42+
private $busLocator;
43+
private $eventDispatcher;
44+
private $logger;
45+
46+
public function __construct(string $receiverName, ContainerInterface $receiverLocator, ContainerInterface $busLocator, EventDispatcherInterface $eventDispatcher = null, LoggerInterface $logger = null)
47+
{
48+
$this->receiverName = $receiverName;
49+
$this->receiverLocator = $receiverLocator;
50+
$this->busLocator = $busLocator;
51+
$this->eventDispatcher = $eventDispatcher;
52+
$this->logger = $logger;
53+
54+
parent::__construct();
55+
}
56+
57+
/**
58+
* {@inheritdoc}
59+
*/
60+
protected function configure(): void
61+
{
62+
$this
63+
->setDefinition([
64+
new InputArgument('id', InputArgument::OPTIONAL, 'Specific message id to retry'),
65+
])
66+
->setDescription('Retries one or more messages from the failure transport.')
67+
->setHelp(<<<'EOF'
68+
The <info>%command.name%</info> retries message in the failure transport.
69+
70+
<info>php %command.full_name%</info>
71+
72+
The command will interactively ask if each command should be retried
73+
or discarded.
74+
75+
<info>php %command.full_name% {id}</info>
76+
77+
Some transports support retrying a specific message id, which comes
78+
from the <info>messenger:failed:show</info> command.
79+
EOF
80+
)
81+
;
82+
}
83+
84+
/**
85+
* {@inheritdoc}
86+
*/
87+
protected function execute(InputInterface $input, OutputInterface $output)
88+
{
89+
$io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output);
90+
$io->comment('Re-run the command with a -vv option to see logs about consumed messages.');
91+
92+
$receiver = $this->getReceiver();
93+
if ($receiver instanceof MessageCountAwareInterface) {
94+
if (1 === $receiver->getMessageCount()) {
95+
$io->writeln('There is <comment>1</comment> message waiting in the failed transport.');
96+
} else {
97+
$io->writeln(sprintf('There are <comment>%d</comment> messages waiting in the failed transport.', $receiver->getMessageCount()));
98+
}
99+
}
100+
101+
if (null === $id = $input->getArgument('id')) {
102+
if (!$input->isInteractive()) {
103+
throw new RuntimeException('Message id must be passed when in non-interactive mode.');
104+
}
105+
106+
return $this->runInteractive($io);
107+
}
108+
109+
throw new \Exception('TODO');
110+
}
111+
112+
private function runInteractive(SymfonyStyle $io)
113+
{
114+
$io->title('Press Ctrl+C at any time to quit.');
115+
116+
$this->eventDispatcher->addListener(WorkerMessageReceivedEvent::class, function (WorkerMessageReceivedEvent $messageReceivedEvent) use ($io) {
117+
$envelope = $messageReceivedEvent->getEnvelope();
118+
119+
$io->writeln(sprintf(' Found a message to retry: <info>%s</info>', \get_class($envelope->getMessage())));
120+
121+
if ($io->isVeryVerbose() && \function_exists('dump')) {
122+
dump($envelope->getMessage());
123+
}
124+
125+
$shouldHandle = $io->confirm('Do you want to retry (yes) or delete this message (no)?');
126+
127+
if ($shouldHandle) {
128+
return;
129+
}
130+
131+
$messageReceivedEvent->setShouldHandle(false);
132+
$this->getReceiver()->reject($envelope);
133+
});
134+
135+
$worker = new Worker(
136+
[$this->receiverName => $this->getReceiver()],
137+
new RoutableMessageBus($this->busLocator),
138+
[],
139+
$this->eventDispatcher,
140+
$this->logger
141+
);
142+
143+
$worker->run([], function (?Envelope $envelope) use ($worker, $io) {
144+
if (null === $envelope) {
145+
$worker->stop();
146+
147+
$io->success('All failed messages have been handled or removed!');
148+
}
149+
});
150+
}
151+
152+
private function getReceiver(): ReceiverInterface
153+
{
154+
if (false === $this->receiverLocator->has($this->receiverName)) {
155+
throw new RuntimeException(sprintf('Receiver "%s" not found.', $this->receiverName));
156+
}
157+
158+
return $this->receiverLocator->get($this->receiverName);
159+
}
160+
}

src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -240,12 +240,13 @@ private function registerReceivers(ContainerBuilder $container, array $busIds)
240240
foreach ($receiverMapping as $name => $reference) {
241241
$receiverNames[(string) $reference] = $name;
242242
}
243-
if ($container->hasDefinition('console.command.messenger_consume_messages')) {
244-
$buses = [];
245-
foreach ($busIds as $busId) {
246-
$buses[$busId] = new Reference($busId);
247-
}
248243

244+
$buses = [];
245+
foreach ($busIds as $busId) {
246+
$buses[$busId] = new Reference($busId);
247+
}
248+
249+
if ($container->hasDefinition('console.command.messenger_consume_messages')) {
249250
$container->getDefinition('console.command.messenger_consume_messages')
250251
->replaceArgument(0, ServiceLocatorTagPass::register($container, $buses))
251252
->replaceArgument(3, array_values($receiverNames));
@@ -257,6 +258,17 @@ private function registerReceivers(ContainerBuilder $container, array $busIds)
257258
}
258259

259260
$container->getDefinition('messenger.receiver_locator')->replaceArgument(0, $receiverMapping);
261+
262+
if ($container->hasDefinition('messenger.failure.send_failed_message_to_failed_transport_listener')) {
263+
$container->getDefinition('messenger.failure.send_failed_message_to_failed_transport_listener')
264+
->replaceArgument(0, ServiceLocatorTagPass::register($container, $buses));
265+
}
266+
267+
if ($container->hasDefinition('console.command.messenger_failed_messages_retry')) {
268+
$container->getDefinition('console.command.messenger_failed_messages_retry')
269+
->replaceArgument(1, ServiceLocatorTagPass::register($container, $receiverMapping))
270+
->replaceArgument(2, ServiceLocatorTagPass::register($container, $buses));
271+
}
260272
}
261273

262274
private function registerBusToCollector(ContainerBuilder $container, string $busId)

src/Symfony/Component/Messenger/Event/WorkerMessageReceivedEvent.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,15 @@
2020
*/
2121
class WorkerMessageReceivedEvent extends AbstractWorkerMessageEvent
2222
{
23+
private $shouldHandle = true;
24+
25+
public function setShouldHandle(bool $shouldHandle)
26+
{
27+
$this->shouldHandle = $shouldHandle;
28+
}
29+
30+
public function getShouldHandle(): bool
31+
{
32+
return $this->shouldHandle;
33+
}
2334
}

0 commit comments

Comments
 (0)