diff --git a/UPGRADE-4.2.md b/UPGRADE-4.2.md
index 670fe3c7cfc70..3d2ae32351735 100644
--- a/UPGRADE-4.2.md
+++ b/UPGRADE-4.2.md
@@ -88,7 +88,9 @@ Messenger
---------
* The `handle` method of the `Symfony\Component\Messenger\Middleware\ValidationMiddleware` and `Symfony\Component\Messenger\Asynchronous\Middleware\SendMessageMiddleware` middlewares now requires an `Envelope` object to be given (because they implement the `EnvelopeAwareInterface`). When using these middleware with the provided `MessageBus`, you will not have to do anything. If you use the middlewares any other way, you can use `Envelope::wrap($message)` to create an envelope for your message.
-
+ * The method `getConnectionCredentials` of the AMQP transport class `Symfony\Component\Messenger\Transport\AmqpExt\Connection`
+ has been renamed to `getConnectionConfiguration`.
+
Security
--------
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.xml
index 0b09978d33870..5e3b5a6db2d89 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.xml
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.xml
@@ -64,6 +64,7 @@
%kernel.debug%
+
diff --git a/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/AmqpReceiverTest.php b/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/AmqpReceiverTest.php
index 5666598337655..0508670503547 100644
--- a/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/AmqpReceiverTest.php
+++ b/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/AmqpReceiverTest.php
@@ -69,7 +69,7 @@ public function testItNonAcknowledgeTheMessageIfAnExceptionHappened()
$connection = $this->getMockBuilder(Connection::class)->disableOriginalConstructor()->getMock();
$connection->method('get')->willReturn($envelope);
- $connection->expects($this->once())->method('nack')->with($envelope);
+ $connection->expects($this->once())->method('nack')->with($envelope, AMQP_REQUEUE);
$receiver = new AmqpReceiver($serializer, $connection);
$receiver->receive(function () {
@@ -101,6 +101,60 @@ public function testItRejectsTheMessageIfTheExceptionIsARejectMessageExceptionIn
throw new WillNeverWorkException('Well...');
});
}
+
+ public function testItPublishesTheMessageForRetryIfSuchConfiguration()
+ {
+ $serializer = new Serializer(
+ new SerializerComponent\Serializer(array(new ObjectNormalizer()), array('json' => new JsonEncoder()))
+ );
+
+ $envelope = $this->getMockBuilder(\AMQPEnvelope::class)->getMock();
+ $envelope->method('getBody')->willReturn('{"message": "Hi"}');
+ $envelope->method('getHeaders')->willReturn(array(
+ 'type' => DummyMessage::class,
+ ));
+
+ $connection = $this->getMockBuilder(Connection::class)->disableOriginalConstructor()->getMock();
+ $connection->method('get')->willReturn($envelope);
+ $connection->method('getConnectionConfiguration')->willReturn(array('retry' => array('attempts' => 3)));
+ $connection->method('publishForRetry')->with($envelope)->willReturn(true);
+
+ $connection->expects($this->once())->method('ack')->with($envelope);
+
+ $receiver = new AmqpReceiver($serializer, $connection);
+ $receiver->receive(function (Envelope $envelope) use ($receiver) {
+ $this->assertEquals(new DummyMessage('Hi'), $envelope->getMessage());
+ $receiver->stop();
+ });
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Messenger\Tests\Transport\AmqpExt\InterruptException
+ */
+ public function testItThrowsTheExceptionIfTheRetryPublishDidNotWork()
+ {
+ $serializer = new Serializer(
+ new SerializerComponent\Serializer(array(new ObjectNormalizer()), array('json' => new JsonEncoder()))
+ );
+
+ $envelope = $this->getMockBuilder(\AMQPEnvelope::class)->getMock();
+ $envelope->method('getBody')->willReturn('{"message": "Hi"}');
+ $envelope->method('getHeaders')->willReturn(array(
+ 'type' => DummyMessage::class,
+ ));
+
+ $connection = $this->getMockBuilder(Connection::class)->disableOriginalConstructor()->getMock();
+ $connection->method('get')->willReturn($envelope);
+ $connection->method('getConnectionConfiguration')->willReturn(array('retry' => array('attempts' => 3)));
+ $connection->method('publishForRetry')->with($envelope)->willReturn(false);
+
+ $connection->expects($this->never())->method('ack')->with($envelope);
+
+ $receiver = new AmqpReceiver($serializer, $connection);
+ $receiver->receive(function () {
+ throw new InterruptException('Well...');
+ });
+ }
}
class InterruptException extends \Exception
diff --git a/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/AmqpTransportFactoryTest.php b/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/AmqpTransportFactoryTest.php
index 53a98e2263a07..0e2396f63af1b 100644
--- a/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/AmqpTransportFactoryTest.php
+++ b/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/AmqpTransportFactoryTest.php
@@ -41,7 +41,7 @@ public function testItCreatesTheTransport()
true
);
- $expectedTransport = new AmqpTransport($encoder, $decoder, Connection::fromDsn('amqp://localhost', array('foo' => 'bar'), true), array('foo' => 'bar'), true);
+ $expectedTransport = new AmqpTransport($encoder, $decoder, Connection::fromDsn('amqp://localhost', array('foo' => 'bar'), true));
$this->assertEquals($expectedTransport, $factory->createTransport('amqp://localhost', array('foo' => 'bar')));
}
diff --git a/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/ConnectionTest.php b/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/ConnectionTest.php
index 511f8fe3c4414..56b809dd364ea 100644
--- a/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/ConnectionTest.php
+++ b/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/ConnectionTest.php
@@ -189,6 +189,98 @@ public function testItCanDisableTheSetup()
$connection = Connection::fromDsn('amqp://localhost/%2f/messages?queue[routing_key]=my_key&auto-setup=false', array(), true, $factory);
$connection->publish('body');
}
+
+ public function testItRetriesTheMessage()
+ {
+ $amqpConnection = $this->getMockBuilder(\AMQPConnection::class)->disableOriginalConstructor()->getMock();
+ $amqpChannel = $this->getMockBuilder(\AMQPChannel::class)->disableOriginalConstructor()->getMock();
+ $retryQueue = $this->getMockBuilder(\AMQPQueue::class)->disableOriginalConstructor()->getMock();
+
+ $factory = $this->getMockBuilder(AmqpFactory::class)->getMock();
+ $factory->method('createConnection')->willReturn($amqpConnection);
+ $factory->method('createChannel')->willReturn($amqpChannel);
+ $factory->method('createQueue')->willReturn($retryQueue);
+ $factory->method('createExchange')->will($this->onConsecutiveCalls(
+ $retryExchange = $this->getMockBuilder(\AMQPExchange::class)->disableOriginalConstructor()->getMock(),
+ $amqpExchange = $this->getMockBuilder(\AMQPExchange::class)->disableOriginalConstructor()->getMock()
+ ));
+
+ $amqpExchange->expects($this->once())->method('setName')->with('messages');
+ $amqpExchange->method('getName')->willReturn('messages');
+
+ $retryExchange->expects($this->once())->method('setName')->with('retry');
+ $retryExchange->expects($this->once())->method('declareExchange');
+ $retryExchange->method('getName')->willReturn('retry');
+
+ $retryQueue->expects($this->once())->method('setName')->with('retry_queue_1');
+ $retryQueue->expects($this->once())->method('setArguments')->with(array(
+ 'x-message-ttl' => 10000,
+ 'x-dead-letter-exchange' => 'messages',
+ ));
+
+ $retryQueue->expects($this->once())->method('declareQueue');
+ $retryQueue->expects($this->once())->method('bind')->with('retry', 'attempt_1');
+
+ $envelope = $this->getMockBuilder(\AMQPEnvelope::class)->getMock();
+ $envelope->method('getHeader')->with('symfony-messenger-attempts')->willReturn(false);
+ $envelope->method('getHeaders')->willReturn(array('x-some-headers' => 'foo'));
+ $envelope->method('getBody')->willReturn('{}');
+
+ $retryExchange->expects($this->once())->method('publish')->with('{}', 'attempt_1', AMQP_NOPARAM, array('headers' => array('x-some-headers' => 'foo', 'symfony-messenger-attempts' => 1)));
+
+ $connection = Connection::fromDsn('amqp://localhost/%2f/messages', array('retry' => array('attempts' => 3)), false, $factory);
+ $connection->publishForRetry($envelope);
+ }
+
+ public function testItRetriesTheMessageWithADifferentRoutingKeyAndTTLs()
+ {
+ $amqpConnection = $this->getMockBuilder(\AMQPConnection::class)->disableOriginalConstructor()->getMock();
+ $amqpChannel = $this->getMockBuilder(\AMQPChannel::class)->disableOriginalConstructor()->getMock();
+ $retryQueue = $this->getMockBuilder(\AMQPQueue::class)->disableOriginalConstructor()->getMock();
+
+ $factory = $this->getMockBuilder(AmqpFactory::class)->getMock();
+ $factory->method('createConnection')->willReturn($amqpConnection);
+ $factory->method('createChannel')->willReturn($amqpChannel);
+ $factory->method('createQueue')->willReturn($retryQueue);
+ $factory->method('createExchange')->will($this->onConsecutiveCalls(
+ $retryExchange = $this->getMockBuilder(\AMQPExchange::class)->disableOriginalConstructor()->getMock(),
+ $amqpExchange = $this->getMockBuilder(\AMQPExchange::class)->disableOriginalConstructor()->getMock()
+ ));
+
+ $amqpExchange->expects($this->once())->method('setName')->with('messages');
+ $amqpExchange->method('getName')->willReturn('messages');
+
+ $retryExchange->expects($this->once())->method('setName')->with('retry');
+ $retryExchange->expects($this->once())->method('declareExchange');
+ $retryExchange->method('getName')->willReturn('retry');
+
+ $connectionOptions = array(
+ 'retry' => array(
+ 'attempts' => 3,
+ 'dead_routing_key' => 'my_dead_routing_key',
+ 'ttl' => array(30000, 60000, 120000),
+ ),
+ );
+
+ $connection = Connection::fromDsn('amqp://localhost/%2f/messages', $connectionOptions, false, $factory);
+
+ $messageRetriedTwice = $this->getMockBuilder(\AMQPEnvelope::class)->getMock();
+ $messageRetriedTwice->method('getHeader')->with('symfony-messenger-attempts')->willReturn('2');
+ $messageRetriedTwice->method('getHeaders')->willReturn(array('symfony-messenger-attempts' => '2'));
+ $messageRetriedTwice->method('getBody')->willReturn('{}');
+
+ $retryQueue->expects($this->once())->method('setName')->with('retry_queue_3');
+ $retryQueue->expects($this->once())->method('setArguments')->with(array(
+ 'x-message-ttl' => 120000,
+ 'x-dead-letter-exchange' => 'messages',
+ ));
+
+ $retryQueue->expects($this->once())->method('declareQueue');
+ $retryQueue->expects($this->once())->method('bind')->with('retry', 'attempt_3');
+
+ $retryExchange->expects($this->once())->method('publish')->with('{}', 'attempt_3', AMQP_NOPARAM, array('headers' => array('symfony-messenger-attempts' => 3)));
+ $connection->publishForRetry($messageRetriedTwice);
+ }
}
class TestAmqpFactory extends AmqpFactory
diff --git a/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpReceiver.php b/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpReceiver.php
index 0e6fbff8ee340..4c88e6166b845 100644
--- a/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpReceiver.php
+++ b/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpReceiver.php
@@ -11,6 +11,7 @@
namespace Symfony\Component\Messenger\Transport\AmqpExt;
+use Psr\Log\LoggerInterface;
use Symfony\Component\Messenger\Transport\AmqpExt\Exception\RejectMessageExceptionInterface;
use Symfony\Component\Messenger\Transport\ReceiverInterface;
use Symfony\Component\Messenger\Transport\Serialization\DecoderInterface;
@@ -22,14 +23,18 @@
*/
class AmqpReceiver implements ReceiverInterface
{
+ private const DEFAULT_LOOP_SLEEP_IN_MICRO_SECONDS = 200000;
+
private $decoder;
private $connection;
+ private $logger;
private $shouldStop;
- public function __construct(DecoderInterface $decoder, Connection $connection)
+ public function __construct(DecoderInterface $decoder, Connection $connection, LoggerInterface $logger = null)
{
$this->decoder = $decoder;
$this->connection = $connection;
+ $this->logger = $logger;
}
/**
@@ -39,10 +44,11 @@ public function receive(callable $handler): void
{
while (!$this->shouldStop) {
$AMQPEnvelope = $this->connection->get();
+
if (null === $AMQPEnvelope) {
$handler(null);
- usleep($this->connection->getConnectionCredentials()['loop_sleep'] ?? 200000);
+ usleep($this->connection->getConnectionConfiguration()['loop_sleep'] ?? self::DEFAULT_LOOP_SLEEP_IN_MICRO_SECONDS);
if (\function_exists('pcntl_signal_dispatch')) {
pcntl_signal_dispatch();
}
@@ -62,9 +68,25 @@ public function receive(callable $handler): void
throw $e;
} catch (\Throwable $e) {
- $this->connection->nack($AMQPEnvelope, AMQP_REQUEUE);
+ try {
+ $retried = $this->connection->publishForRetry($AMQPEnvelope);
+ } catch (\Throwable $retryException) {
+ $this->logger && $this->logger->warning(sprintf('Retrying message #%s failed. Requeuing it now.', $AMQPEnvelope->getMessageId()), array(
+ 'retryException' => $retryException,
+ 'exception' => $e,
+ ));
- throw $e;
+ $retried = false;
+ }
+
+ if (!$retried) {
+ $this->connection->nack($AMQPEnvelope, AMQP_REQUEUE);
+
+ throw $e;
+ }
+
+ // Acknowledge current message as another one as been requeued.
+ $this->connection->ack($AMQPEnvelope);
} finally {
if (\function_exists('pcntl_signal_dispatch')) {
pcntl_signal_dispatch();
@@ -73,6 +95,9 @@ public function receive(callable $handler): void
}
}
+ /**
+ * {@inheritdoc}
+ */
public function stop(): void
{
$this->shouldStop = true;
diff --git a/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpTransport.php b/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpTransport.php
index 3edefd0ab1c8a..d97dce3a3a23d 100644
--- a/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpTransport.php
+++ b/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpTransport.php
@@ -11,6 +11,7 @@
namespace Symfony\Component\Messenger\Transport\AmqpExt;
+use Psr\Log\LoggerInterface;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Transport\Serialization\DecoderInterface;
use Symfony\Component\Messenger\Transport\Serialization\EncoderInterface;
@@ -26,12 +27,14 @@ class AmqpTransport implements TransportInterface
private $connection;
private $receiver;
private $sender;
+ private $logger;
- public function __construct(EncoderInterface $encoder, DecoderInterface $decoder, Connection $connection)
+ public function __construct(EncoderInterface $encoder, DecoderInterface $decoder, Connection $connection, LoggerInterface $logger = null)
{
$this->encoder = $encoder;
$this->decoder = $decoder;
$this->connection = $connection;
+ $this->logger = $logger;
}
/**
@@ -60,7 +63,7 @@ public function send(Envelope $envelope): void
private function getReceiver()
{
- return $this->receiver = new AmqpReceiver($this->decoder, $this->connection);
+ return $this->receiver = new AmqpReceiver($this->decoder, $this->connection, $this->logger);
}
private function getSender()
diff --git a/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpTransportFactory.php b/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpTransportFactory.php
index 29fb4ae4aa3e0..88f547285f97a 100644
--- a/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpTransportFactory.php
+++ b/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpTransportFactory.php
@@ -11,6 +11,7 @@
namespace Symfony\Component\Messenger\Transport\AmqpExt;
+use Psr\Log\LoggerInterface;
use Symfony\Component\Messenger\Transport\Serialization\DecoderInterface;
use Symfony\Component\Messenger\Transport\Serialization\EncoderInterface;
use Symfony\Component\Messenger\Transport\TransportFactoryInterface;
@@ -24,17 +25,19 @@ class AmqpTransportFactory implements TransportFactoryInterface
private $encoder;
private $decoder;
private $debug;
+ private $logger;
- public function __construct(EncoderInterface $encoder, DecoderInterface $decoder, bool $debug)
+ public function __construct(EncoderInterface $encoder, DecoderInterface $decoder, bool $debug, LoggerInterface $logger = null)
{
$this->encoder = $encoder;
$this->decoder = $decoder;
$this->debug = $debug;
+ $this->logger = $logger;
}
public function createTransport(string $dsn, array $options): TransportInterface
{
- return new AmqpTransport($this->encoder, $this->decoder, Connection::fromDsn($dsn, $options, $this->debug));
+ return new AmqpTransport($this->encoder, $this->decoder, Connection::fromDsn($dsn, $options, $this->debug), $this->logger);
}
public function supports(string $dsn, array $options): bool
diff --git a/src/Symfony/Component/Messenger/Transport/AmqpExt/Connection.php b/src/Symfony/Component/Messenger/Transport/AmqpExt/Connection.php
index 6f50bd76d5e65..0c1baf40be68b 100644
--- a/src/Symfony/Component/Messenger/Transport/AmqpExt/Connection.php
+++ b/src/Symfony/Component/Messenger/Transport/AmqpExt/Connection.php
@@ -18,7 +18,10 @@
*/
class Connection
{
- private $connectionCredentials;
+ private const DEFAULT_MESSAGE_TTL_IN_MILLI_SECONDS = 10000;
+ private const DEFAULT_MAX_ATTEMPTS = 3;
+
+ private $connectionConfiguration;
private $exchangeConfiguration;
private $queueConfiguration;
private $debug;
@@ -39,9 +42,50 @@ class Connection
*/
private $amqpQueue;
- public function __construct(array $connectionCredentials, array $exchangeConfiguration, array $queueConfiguration, bool $debug = false, AmqpFactory $amqpFactory = null)
+ /**
+ * @var \AMQPExchange|null
+ */
+ private $amqpRetryExchange;
+
+ /**
+ * Available options:
+ *
+ * * host: Hostname of the AMQP service
+ * * port: Port of the AMQP service
+ * * vhost: Virtual Host to use with the AMQP service
+ * * user: Username to use to connect the the AMQP service
+ * * password: Username to use the connect to the AMQP service
+ * * queue:
+ * * name: Name of the queue
+ * * routing_key: The routing key (if any) to use to push the messages to
+ * * flags: Queue flags (Default: AMQP_DURABLE)
+ * * arguments: Extra arguments
+ * * exchange:
+ * * name: Name of the exchange
+ * * type: Type of exchange (Default: fanout)
+ * * flags: Exchange flags (Default: AMQP_DURABLE)
+ * * arguments: Extra arguments
+ * * retry:
+ * * attempts: Number of times it will try to retry
+ * * routing_key_pattern: The pattern of the routing key (Default: "attempt_%attempt%")
+ * * dead_queue: Name of the queue in which messages that retry more than attempts time are pushed to
+ * * dead_routing_key: Routing key name for the dead queue (Default: "dead")
+ * * queue_name_pattern: Pattern to use to create the queues (Default: "retry_queue_%attempt%")
+ * * exchange_name: Name of the exchange to be used for the retried messages (Default: "retry")
+ * * ttl: Key-value pairs of attempt number -> seconds to wait. If not configured, 10 seconds will be waited each attempt.
+ * * auto-setup: Enable or not the auto-setup of queues and exchanges (Default: true)
+ * * loop_sleep: Amount of micro-seconds to wait if no message are available (Default: 200000)
+ */
+ public function __construct(array $connectionConfiguration, array $exchangeConfiguration, array $queueConfiguration, bool $debug = false, AmqpFactory $amqpFactory = null)
{
- $this->connectionCredentials = $connectionCredentials;
+ $this->connectionConfiguration = array_replace_recursive(array(
+ 'retry' => array(
+ 'routing_key_pattern' => 'attempt_%attempt%',
+ 'dead_routing_key' => 'dead',
+ 'exchange_name' => 'retry',
+ 'queue_name_pattern' => 'retry_queue_%attempt%',
+ ),
+ ), $connectionConfiguration);
$this->debug = $debug;
$this->exchangeConfiguration = $exchangeConfiguration;
$this->queueConfiguration = $queueConfiguration;
@@ -101,6 +145,108 @@ public function publish(string $body, array $headers = array()): void
$this->exchange()->publish($body, null, AMQP_NOPARAM, array('headers' => $headers));
}
+ /**
+ * @throws \AMQPException
+ */
+ public function publishForRetry(\AMQPEnvelope $message): bool
+ {
+ if (!isset($this->connectionConfiguration['retry'])) {
+ return false;
+ }
+
+ $retryConfiguration = $this->connectionConfiguration['retry'];
+ $attemptNumber = ((int) $message->getHeader('symfony-messenger-attempts') ?: 0) + 1;
+
+ if ($this->shouldSetup()) {
+ $this->setupRetry($retryConfiguration, $attemptNumber);
+ }
+
+ $maximumAttempts = $retryConfiguration['attempts'] ?? self::DEFAULT_MAX_ATTEMPTS;
+ $routingKey = str_replace('%attempt%', $attemptNumber, $retryConfiguration['routing_key_pattern']);
+
+ if ($attemptNumber > $maximumAttempts) {
+ if (!isset($retryConfiguration['dead_queue'])) {
+ return false;
+ }
+
+ $routingKey = $retryConfiguration['dead_routing_key'];
+ }
+
+ $retriedMessageAttributes = array(
+ 'headers' => array_merge($message->getHeaders(), array('symfony-messenger-attempts' => (string) $attemptNumber)),
+ );
+
+ if ($deliveryMode = $message->getDeliveryMode()) {
+ $retriedMessageAttributes['delivery_mode'] = $deliveryMode;
+ }
+ if ($userId = $message->getUserId()) {
+ $retriedMessageAttributes['user_id'] = $userId;
+ }
+ if (null !== $priority = $message->getPriority()) {
+ $retriedMessageAttributes['priority'] = $priority;
+ }
+ if ($replyTo = $message->getReplyTo()) {
+ $retriedMessageAttributes['reply_to'] = $replyTo;
+ }
+
+ $this->retryExchange($retryConfiguration)->publish(
+ $message->getBody(),
+ $routingKey,
+ AMQP_NOPARAM,
+ $retriedMessageAttributes
+ );
+
+ return true;
+ }
+
+ private function setupRetry(array $retryConfiguration, int $attemptNumber)
+ {
+ if (!$this->channel()->isConnected()) {
+ $this->clear();
+ }
+
+ $exchange = $this->retryExchange($retryConfiguration);
+ $exchange->declareExchange();
+
+ $queue = $this->retryQueue($retryConfiguration, $attemptNumber);
+ $queue->declareQueue();
+ $queue->bind($exchange->getName(), str_replace('%attempt%', $attemptNumber, $retryConfiguration['routing_key_pattern']));
+
+ if (isset($retryConfiguration['dead_queue'])) {
+ $queue = $this->amqpFactory->createQueue($this->channel());
+ $queue->setName($retryConfiguration['dead_queue']);
+ $queue->declareQueue();
+ $queue->bind($exchange->getName(), $retryConfiguration['dead_routing_key']);
+ }
+ }
+
+ private function retryExchange(array $retryConfiguration): \AMQPExchange
+ {
+ if (null === $this->amqpRetryExchange) {
+ $this->amqpRetryExchange = $this->amqpFactory->createExchange($this->channel());
+ $this->amqpRetryExchange->setName($retryConfiguration['exchange_name']);
+ $this->amqpRetryExchange->setType(AMQP_EX_TYPE_DIRECT);
+ }
+
+ return $this->amqpRetryExchange;
+ }
+
+ private function retryQueue(array $retryConfiguration, int $attemptNumber)
+ {
+ $queue = $this->amqpFactory->createQueue($this->channel());
+ $queue->setName(str_replace('%attempt%', $attemptNumber, $retryConfiguration['queue_name_pattern']));
+ $queue->setArguments(array(
+ 'x-message-ttl' => $retryConfiguration['ttl'][$attemptNumber - 1] ?? self::DEFAULT_MESSAGE_TTL_IN_MILLI_SECONDS,
+ 'x-dead-letter-exchange' => $this->exchange()->getName(),
+ ));
+
+ if (isset($this->queueConfiguration['routing_key'])) {
+ $queue->setArgument('x-dead-letter-routing-key', $this->queueConfiguration['routing_key']);
+ }
+
+ return $queue;
+ }
+
/**
* Waits and gets a message from the configured queue.
*
@@ -160,8 +306,8 @@ public function setup(): void
public function channel(): \AMQPChannel
{
if (null === $this->amqpChannel) {
- $connection = $this->amqpFactory->createConnection($this->connectionCredentials);
- $connectMethod = 'true' === ($this->connectionCredentials['persistent'] ?? 'false') ? 'pconnect' : 'connect';
+ $connection = $this->amqpFactory->createConnection($this->connectionConfiguration);
+ $connectMethod = 'true' === ($this->connectionConfiguration['persistent'] ?? 'false') ? 'pconnect' : 'connect';
if (false === $connection->{$connectMethod}()) {
throw new \AMQPException('Could not connect to the AMQP server. Please verify the provided DSN.');
@@ -204,9 +350,9 @@ public function exchange(): \AMQPExchange
return $this->amqpExchange;
}
- public function getConnectionCredentials(): array
+ public function getConnectionConfiguration(): array
{
- return $this->connectionCredentials;
+ return $this->connectionConfiguration;
}
private function clear(): void
@@ -218,6 +364,6 @@ private function clear(): void
private function shouldSetup(): bool
{
- return !array_key_exists('auto-setup', $this->connectionCredentials) || !\in_array($this->connectionCredentials['auto-setup'], array(false, 'false'), true);
+ return !array_key_exists('auto-setup', $this->connectionConfiguration) || !\in_array($this->connectionConfiguration['auto-setup'], array(false, 'false'), true);
}
}
pFad - Phonifier reborn
Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies:
Alternative Proxy
pFad Proxy
pFad v3 Proxy
pFad v4 Proxy