+ */
+final class AmqpStamp implements NonSendableStampInterface
+{
+ private $routingKey;
+ private $flags;
+ private $attributes;
+
+ public function __construct(string $routingKey = null, int $flags = AMQP_NOPARAM, array $attributes = [])
+ {
+ $this->routingKey = $routingKey;
+ $this->flags = $flags;
+ $this->attributes = $attributes;
+ }
+
+ public function getRoutingKey(): ?string
+ {
+ return $this->routingKey;
+ }
+
+ public function getFlags(): int
+ {
+ return $this->flags;
+ }
+
+ public function getAttributes(): array
+ {
+ return $this->attributes;
+ }
+
+ public static function createFromAmqpEnvelope(\AMQPEnvelope $amqpEnvelope, self $previousStamp = null): self
+ {
+ $attr = $previousStamp->attributes ?? [];
+
+ $attr['headers'] = $attr['headers'] ?? $amqpEnvelope->getHeaders();
+ $attr['content_type'] = $attr['content_type'] ?? $amqpEnvelope->getContentType();
+ $attr['content_encoding'] = $attr['content_encoding'] ?? $amqpEnvelope->getContentEncoding();
+ $attr['delivery_mode'] = $attr['delivery_mode'] ?? $amqpEnvelope->getDeliveryMode();
+ $attr['priority'] = $attr['priority'] ?? $amqpEnvelope->getPriority();
+ $attr['timestamp'] = $attr['timestamp'] ?? $amqpEnvelope->getTimestamp();
+ $attr['app_id'] = $attr['app_id'] ?? $amqpEnvelope->getAppId();
+ $attr['message_id'] = $attr['message_id'] ?? $amqpEnvelope->getMessageId();
+ $attr['user_id'] = $attr['user_id'] ?? $amqpEnvelope->getUserId();
+ $attr['expiration'] = $attr['expiration'] ?? $amqpEnvelope->getExpiration();
+ $attr['type'] = $attr['type'] ?? $amqpEnvelope->getType();
+ $attr['reply_to'] = $attr['reply_to'] ?? $amqpEnvelope->getReplyTo();
+
+ return new self($previousStamp->routingKey ?? $amqpEnvelope->getRoutingKey(), $previousStamp->flags ?? AMQP_NOPARAM, $attr);
+ }
+
+ public static function createWithAttributes(array $attributes, self $previousStamp = null): self
+ {
+ return new self(
+ $previousStamp->routingKey ?? null,
+ $previousStamp->flags ?? AMQP_NOPARAM,
+ array_merge($previousStamp->attributes ?? [], $attributes)
+ );
+ }
+}
+class_alias(AmqpStamp::class, \Symfony\Component\Messenger\Transport\AmqpExt\AmqpStamp::class);
diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpTransport.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpTransport.php
new file mode 100644
index 0000000000..030046851f
--- /dev/null
+++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpTransport.php
@@ -0,0 +1,95 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Bridge\Amqp\Transport;
+
+use Symfony\Component\Messenger\Envelope;
+use Symfony\Component\Messenger\Transport\Receiver\MessageCountAwareInterface;
+use Symfony\Component\Messenger\Transport\Serialization\PhpSerializer;
+use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
+use Symfony\Component\Messenger\Transport\SetupableTransportInterface;
+use Symfony\Component\Messenger\Transport\TransportInterface;
+
+/**
+ * @author Nicolas Grekas
+ */
+class AmqpTransport implements TransportInterface, SetupableTransportInterface, MessageCountAwareInterface
+{
+ private $serializer;
+ private $connection;
+ private $receiver;
+ private $sender;
+
+ public function __construct(Connection $connection, SerializerInterface $serializer = null)
+ {
+ $this->connection = $connection;
+ $this->serializer = $serializer ?? new PhpSerializer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get(): iterable
+ {
+ return ($this->receiver ?? $this->getReceiver())->get();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function ack(Envelope $envelope): void
+ {
+ ($this->receiver ?? $this->getReceiver())->ack($envelope);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reject(Envelope $envelope): void
+ {
+ ($this->receiver ?? $this->getReceiver())->reject($envelope);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function send(Envelope $envelope): Envelope
+ {
+ return ($this->sender ?? $this->getSender())->send($envelope);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setup(): void
+ {
+ $this->connection->setup();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMessageCount(): int
+ {
+ return ($this->receiver ?? $this->getReceiver())->getMessageCount();
+ }
+
+ private function getReceiver(): AmqpReceiver
+ {
+ return $this->receiver = new AmqpReceiver($this->connection, $this->serializer);
+ }
+
+ private function getSender(): AmqpSender
+ {
+ return $this->sender = new AmqpSender($this->connection, $this->serializer);
+ }
+}
+class_alias(AmqpTransport::class, \Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransport::class);
diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpTransportFactory.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpTransportFactory.php
new file mode 100644
index 0000000000..b9767214f1
--- /dev/null
+++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/AmqpTransportFactory.php
@@ -0,0 +1,35 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Bridge\Amqp\Transport;
+
+use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
+use Symfony\Component\Messenger\Transport\TransportFactoryInterface;
+use Symfony\Component\Messenger\Transport\TransportInterface;
+
+/**
+ * @author Samuel Roze
+ */
+class AmqpTransportFactory implements TransportFactoryInterface
+{
+ public function createTransport(string $dsn, array $options, SerializerInterface $serializer): TransportInterface
+ {
+ unset($options['transport_name']);
+
+ return new AmqpTransport(Connection::fromDsn($dsn, $options), $serializer);
+ }
+
+ public function supports(string $dsn, array $options): bool
+ {
+ return 0 === strpos($dsn, 'amqp://');
+ }
+}
+class_alias(AmqpTransportFactory::class, \Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransportFactory::class);
diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php
new file mode 100644
index 0000000000..a2709946b3
--- /dev/null
+++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php
@@ -0,0 +1,474 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Bridge\Amqp\Transport;
+
+use Symfony\Component\Messenger\Exception\InvalidArgumentException;
+use Symfony\Component\Messenger\Exception\LogicException;
+
+/**
+ * An AMQP connection.
+ *
+ * @author Samuel Roze
+ *
+ * @final
+ */
+class Connection
+{
+ private const ARGUMENTS_AS_INTEGER = [
+ 'x-delay',
+ 'x-expires',
+ 'x-max-length',
+ 'x-max-length-bytes',
+ 'x-max-priority',
+ 'x-message-ttl',
+ ];
+
+ private $connectionOptions;
+ private $exchangeOptions;
+ private $queuesOptions;
+ private $amqpFactory;
+
+ /**
+ * @var \AMQPChannel|null
+ */
+ private $amqpChannel;
+
+ /**
+ * @var \AMQPExchange|null
+ */
+ private $amqpExchange;
+
+ /**
+ * @var \AMQPQueue[]|null
+ */
+ private $amqpQueues = [];
+
+ /**
+ * @var \AMQPExchange|null
+ */
+ private $amqpDelayExchange;
+
+ public function __construct(array $connectionOptions, array $exchangeOptions, array $queuesOptions, AmqpFactory $amqpFactory = null)
+ {
+ if (!\extension_loaded('amqp')) {
+ throw new LogicException(sprintf('You cannot use the "%s" as the "amqp" extension is not installed.', __CLASS__));
+ }
+
+ $this->connectionOptions = array_replace_recursive([
+ 'delay' => [
+ 'exchange_name' => 'delays',
+ 'queue_name_pattern' => 'delay_%exchange_name%_%routing_key%_%delay%',
+ ],
+ ], $connectionOptions);
+ $this->exchangeOptions = $exchangeOptions;
+ $this->queuesOptions = $queuesOptions;
+ $this->amqpFactory = $amqpFactory ?: new AmqpFactory();
+ }
+
+ /**
+ * Creates a connection based on the DSN and options.
+ *
+ * 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: Password to use the connect to the AMQP service
+ * * queues[name]: An array of queues, keyed by the name
+ * * binding_keys: The binding keys (if any) to bind to this queue
+ * * binding_arguments: Arguments to be used while binding the queue.
+ * * flags: Queue flags (Default: AMQP_DURABLE)
+ * * arguments: Extra arguments
+ * * exchange:
+ * * name: Name of the exchange
+ * * type: Type of exchange (Default: fanout)
+ * * default_publish_routing_key: Routing key to use when publishing, if none is specified on the message
+ * * flags: Exchange flags (Default: AMQP_DURABLE)
+ * * arguments: Extra arguments
+ * * delay:
+ * * queue_name_pattern: Pattern to use to create the queues (Default: "delay_%exchange_name%_%routing_key%_%delay%")
+ * * exchange_name: Name of the exchange to be used for the delayed/retried messages (Default: "delays")
+ * * auto_setup: Enable or not the auto-setup of queues and exchanges (Default: true)
+ * * prefetch_count: set channel prefetch count
+ */
+ public static function fromDsn(string $dsn, array $options = [], AmqpFactory $amqpFactory = null): self
+ {
+ if (false === $parsedUrl = parse_url($dsn)) {
+ // this is a valid URI that parse_url cannot handle when you want to pass all parameters as options
+ if ('amqp://' !== $dsn) {
+ throw new InvalidArgumentException(sprintf('The given AMQP DSN "%s" is invalid.', $dsn));
+ }
+
+ $parsedUrl = [];
+ }
+
+ $pathParts = isset($parsedUrl['path']) ? explode('/', trim($parsedUrl['path'], '/')) : [];
+ $exchangeName = $pathParts[1] ?? 'messages';
+ parse_str($parsedUrl['query'] ?? '', $parsedQuery);
+
+ $amqpOptions = array_replace_recursive([
+ 'host' => $parsedUrl['host'] ?? 'localhost',
+ 'port' => $parsedUrl['port'] ?? 5672,
+ 'vhost' => isset($pathParts[0]) ? urldecode($pathParts[0]) : '/',
+ 'exchange' => [
+ 'name' => $exchangeName,
+ ],
+ ], $options, $parsedQuery);
+
+ if (isset($parsedUrl['user'])) {
+ $amqpOptions['login'] = $parsedUrl['user'];
+ }
+
+ if (isset($parsedUrl['pass'])) {
+ $amqpOptions['password'] = $parsedUrl['pass'];
+ }
+
+ if (!isset($amqpOptions['queues'])) {
+ $amqpOptions['queues'][$exchangeName] = [];
+ }
+
+ $exchangeOptions = $amqpOptions['exchange'];
+ $queuesOptions = $amqpOptions['queues'];
+ unset($amqpOptions['queues'], $amqpOptions['exchange']);
+
+ $queuesOptions = array_map(function ($queueOptions) {
+ if (!\is_array($queueOptions)) {
+ $queueOptions = [];
+ }
+ if (\is_array($queueOptions['arguments'] ?? false)) {
+ $queueOptions['arguments'] = self::normalizeQueueArguments($queueOptions['arguments']);
+ }
+
+ return $queueOptions;
+ }, $queuesOptions);
+
+ return new self($amqpOptions, $exchangeOptions, $queuesOptions, $amqpFactory);
+ }
+
+ private static function normalizeQueueArguments(array $arguments): array
+ {
+ foreach (self::ARGUMENTS_AS_INTEGER as $key) {
+ if (!\array_key_exists($key, $arguments)) {
+ continue;
+ }
+
+ if (!is_numeric($arguments[$key])) {
+ throw new InvalidArgumentException(sprintf('Integer expected for queue argument "%s", %s given.', $key, \gettype($arguments[$key])));
+ }
+
+ $arguments[$key] = (int) $arguments[$key];
+ }
+
+ return $arguments;
+ }
+
+ /**
+ * @throws \AMQPException
+ */
+ public function publish(string $body, array $headers = [], int $delayInMs = 0, AmqpStamp $amqpStamp = null): void
+ {
+ $this->clearWhenDisconnected();
+
+ if (0 !== $delayInMs) {
+ $this->publishWithDelay($body, $headers, $delayInMs, $amqpStamp);
+
+ return;
+ }
+
+ if ($this->shouldSetup()) {
+ $this->setupExchangeAndQueues();
+ }
+
+ $this->publishOnExchange(
+ $this->exchange(),
+ $body,
+ $this->getRoutingKeyForMessage($amqpStamp),
+ $headers,
+ $amqpStamp
+ );
+ }
+
+ /**
+ * Returns an approximate count of the messages in defined queues.
+ */
+ public function countMessagesInQueues(): int
+ {
+ return array_sum(array_map(function ($queueName) {
+ return $this->queue($queueName)->declareQueue();
+ }, $this->getQueueNames()));
+ }
+
+ /**
+ * @throws \AMQPException
+ */
+ private function publishWithDelay(string $body, array $headers, int $delay, AmqpStamp $amqpStamp = null)
+ {
+ $routingKey = $this->getRoutingKeyForMessage($amqpStamp);
+
+ $this->setupDelay($delay, $routingKey);
+
+ $this->publishOnExchange(
+ $this->getDelayExchange(),
+ $body,
+ $this->getRoutingKeyForDelay($delay, $routingKey),
+ $headers,
+ $amqpStamp
+ );
+ }
+
+ private function publishOnExchange(\AMQPExchange $exchange, string $body, string $routingKey = null, array $headers = [], AmqpStamp $amqpStamp = null)
+ {
+ $attributes = $amqpStamp ? $amqpStamp->getAttributes() : [];
+ $attributes['headers'] = array_merge($attributes['headers'] ?? [], $headers);
+ $attributes['delivery_mode'] = $attributes['delivery_mode'] ?? 2;
+
+ $exchange->publish(
+ $body,
+ $routingKey,
+ $amqpStamp ? $amqpStamp->getFlags() : AMQP_NOPARAM,
+ $attributes
+ );
+ }
+
+ private function setupDelay(int $delay, ?string $routingKey)
+ {
+ if ($this->shouldSetup()) {
+ $this->setup(); // setup delay exchange and normal exchange for delay queue to DLX messages to
+ }
+
+ $queue = $this->createDelayQueue($delay, $routingKey);
+ $queue->declareQueue(); // the delay queue always need to be declared because the name is dynamic and cannot be declared in advance
+ $queue->bind($this->connectionOptions['delay']['exchange_name'], $this->getRoutingKeyForDelay($delay, $routingKey));
+ }
+
+ private function getDelayExchange(): \AMQPExchange
+ {
+ if (null === $this->amqpDelayExchange) {
+ $this->amqpDelayExchange = $this->amqpFactory->createExchange($this->channel());
+ $this->amqpDelayExchange->setName($this->connectionOptions['delay']['exchange_name']);
+ $this->amqpDelayExchange->setType(AMQP_EX_TYPE_DIRECT);
+ $this->amqpDelayExchange->setFlags(AMQP_DURABLE);
+ }
+
+ return $this->amqpDelayExchange;
+ }
+
+ /**
+ * Creates a delay queue that will delay for a certain amount of time.
+ *
+ * This works by setting message TTL for the delay and pointing
+ * the dead letter exchange to the original exchange. The result
+ * is that after the TTL, the message is sent to the dead-letter-exchange,
+ * which is the original exchange, resulting on it being put back into
+ * the original queue.
+ */
+ private function createDelayQueue(int $delay, ?string $routingKey): \AMQPQueue
+ {
+ $queue = $this->amqpFactory->createQueue($this->channel());
+ $queue->setName(str_replace(
+ ['%delay%', '%exchange_name%', '%routing_key%'],
+ [$delay, $this->exchangeOptions['name'], $routingKey ?? ''],
+ $this->connectionOptions['delay']['queue_name_pattern']
+ ));
+ $queue->setFlags(AMQP_DURABLE);
+ $queue->setArguments([
+ 'x-message-ttl' => $delay,
+ // delete the delay queue 10 seconds after the message expires
+ // publishing another message redeclares the queue which renews the lease
+ 'x-expires' => $delay + 10000,
+ 'x-dead-letter-exchange' => $this->exchangeOptions['name'],
+ // after being released from to DLX, make sure the original routing key will be used
+ // we must use an empty string instead of null for the argument to be picked up
+ 'x-dead-letter-routing-key' => $routingKey ?? '',
+ ]);
+
+ return $queue;
+ }
+
+ private function getRoutingKeyForDelay(int $delay, ?string $finalRoutingKey): string
+ {
+ return str_replace(
+ ['%delay%', '%exchange_name%', '%routing_key%'],
+ [$delay, $this->exchangeOptions['name'], $finalRoutingKey ?? ''],
+ $this->connectionOptions['delay']['queue_name_pattern']
+ );
+ }
+
+ /**
+ * Gets a message from the specified queue.
+ *
+ * @throws \AMQPException
+ */
+ public function get(string $queueName): ?\AMQPEnvelope
+ {
+ $this->clearWhenDisconnected();
+
+ if ($this->shouldSetup()) {
+ $this->setupExchangeAndQueues();
+ }
+
+ try {
+ if (false !== $message = $this->queue($queueName)->get()) {
+ return $message;
+ }
+ } catch (\AMQPQueueException $e) {
+ if (404 === $e->getCode() && $this->shouldSetup()) {
+ // If we get a 404 for the queue, it means we need to set up the exchange & queue.
+ $this->setupExchangeAndQueues();
+
+ return $this->get();
+ }
+
+ throw $e;
+ }
+
+ return null;
+ }
+
+ public function ack(\AMQPEnvelope $message, string $queueName): bool
+ {
+ return $this->queue($queueName)->ack($message->getDeliveryTag());
+ }
+
+ public function nack(\AMQPEnvelope $message, string $queueName, int $flags = AMQP_NOPARAM): bool
+ {
+ return $this->queue($queueName)->nack($message->getDeliveryTag(), $flags);
+ }
+
+ public function setup(): void
+ {
+ $this->setupExchangeAndQueues();
+ $this->getDelayExchange()->declareExchange();
+ }
+
+ private function setupExchangeAndQueues(): void
+ {
+ $this->exchange()->declareExchange();
+
+ foreach ($this->queuesOptions as $queueName => $queueConfig) {
+ $this->queue($queueName)->declareQueue();
+ foreach ($queueConfig['binding_keys'] ?? [null] as $bindingKey) {
+ $this->queue($queueName)->bind($this->exchangeOptions['name'], $bindingKey, $queueConfig['binding_arguments'] ?? []);
+ }
+ }
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getQueueNames(): array
+ {
+ return array_keys($this->queuesOptions);
+ }
+
+ public function channel(): \AMQPChannel
+ {
+ if (null === $this->amqpChannel) {
+ $connection = $this->amqpFactory->createConnection($this->connectionOptions);
+ $connectMethod = 'true' === ($this->connectionOptions['persistent'] ?? 'false') ? 'pconnect' : 'connect';
+
+ try {
+ $connection->{$connectMethod}();
+ } catch (\AMQPConnectionException $e) {
+ $credentials = $this->connectionOptions;
+ $credentials['password'] = '********';
+ unset($credentials['delay']);
+
+ throw new \AMQPException(sprintf('Could not connect to the AMQP server. Please verify the provided DSN. (%s)', json_encode($credentials)), 0, $e);
+ }
+ $this->amqpChannel = $this->amqpFactory->createChannel($connection);
+
+ if (isset($this->connectionOptions['prefetch_count'])) {
+ $this->amqpChannel->setPrefetchCount($this->connectionOptions['prefetch_count']);
+ }
+ }
+
+ return $this->amqpChannel;
+ }
+
+ public function queue(string $queueName): \AMQPQueue
+ {
+ if (!isset($this->amqpQueues[$queueName])) {
+ $queueConfig = $this->queuesOptions[$queueName];
+
+ $amqpQueue = $this->amqpFactory->createQueue($this->channel());
+ $amqpQueue->setName($queueName);
+ $amqpQueue->setFlags($queueConfig['flags'] ?? AMQP_DURABLE);
+
+ if (isset($queueConfig['arguments'])) {
+ $amqpQueue->setArguments($queueConfig['arguments']);
+ }
+
+ $this->amqpQueues[$queueName] = $amqpQueue;
+ }
+
+ return $this->amqpQueues[$queueName];
+ }
+
+ public function exchange(): \AMQPExchange
+ {
+ if (null === $this->amqpExchange) {
+ $this->amqpExchange = $this->amqpFactory->createExchange($this->channel());
+ $this->amqpExchange->setName($this->exchangeOptions['name']);
+ $this->amqpExchange->setType($this->exchangeOptions['type'] ?? AMQP_EX_TYPE_FANOUT);
+ $this->amqpExchange->setFlags($this->exchangeOptions['flags'] ?? AMQP_DURABLE);
+
+ if (isset($this->exchangeOptions['arguments'])) {
+ $this->amqpExchange->setArguments($this->exchangeOptions['arguments']);
+ }
+ }
+
+ return $this->amqpExchange;
+ }
+
+ private function clearWhenDisconnected(): void
+ {
+ if (!$this->channel()->isConnected()) {
+ $this->amqpChannel = null;
+ $this->amqpQueues = [];
+ $this->amqpExchange = null;
+ $this->amqpDelayExchange = null;
+ }
+ }
+
+ private function shouldSetup(): bool
+ {
+ if (!\array_key_exists('auto_setup', $this->connectionOptions)) {
+ return true;
+ }
+
+ if (\in_array($this->connectionOptions['auto_setup'], [false, 'false'], true)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private function getDefaultPublishRoutingKey(): ?string
+ {
+ return $this->exchangeOptions['default_publish_routing_key'] ?? null;
+ }
+
+ public function purgeQueues()
+ {
+ foreach ($this->getQueueNames() as $queueName) {
+ $this->queue($queueName)->purge();
+ }
+ }
+
+ private function getRoutingKeyForMessage(?AmqpStamp $amqpStamp): ?string
+ {
+ return (null !== $amqpStamp ? $amqpStamp->getRoutingKey() : null) ?? $this->getDefaultPublishRoutingKey();
+ }
+}
+class_alias(Connection::class, \Symfony\Component\Messenger\Transport\AmqpExt\Connection::class);
diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/composer.json b/src/Symfony/Component/Messenger/Bridge/Amqp/composer.json
new file mode 100644
index 0000000000..0522064e26
--- /dev/null
+++ b/src/Symfony/Component/Messenger/Bridge/Amqp/composer.json
@@ -0,0 +1,40 @@
+{
+ "name": "symfony/amqp-messenger",
+ "type": "symfony-bridge",
+ "description": "Symfony AMQP extension Messenger Bridge",
+ "keywords": [],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": "^7.2.5",
+ "symfony/messenger": "^5.1"
+ },
+ "require-dev": {
+ "symfony/property-access": "^4.4|^5.0",
+ "symfony/serializer": "^4.4|^5.0",
+ "symfony/event-dispatcher": "^4.4|^5.0",
+ "symfony/process": "^4.4|^5.0"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Component\\Messenger\\Bridge\\Amqp\\": "" },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.1-dev"
+ }
+ }
+}
diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/phpunit.xml.dist b/src/Symfony/Component/Messenger/Bridge/Amqp/phpunit.xml.dist
new file mode 100644
index 0000000000..755a4676f7
--- /dev/null
+++ b/src/Symfony/Component/Messenger/Bridge/Amqp/phpunit.xml.dist
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+ ./Tests/
+
+
+
+
+
+ ./
+
+ ./Tests
+ ./vendor
+
+
+
+
diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/.gitattributes b/src/Symfony/Component/Messenger/Bridge/Doctrine/.gitattributes
new file mode 100644
index 0000000000..ebb9287043
--- /dev/null
+++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/.gitattributes
@@ -0,0 +1,3 @@
+/Tests export-ignore
+/phpunit.xml.dist export-ignore
+/.gitignore export-ignore
diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/.gitignore b/src/Symfony/Component/Messenger/Bridge/Doctrine/.gitignore
new file mode 100644
index 0000000000..c49a5d8df5
--- /dev/null
+++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/.gitignore
@@ -0,0 +1,3 @@
+vendor/
+composer.lock
+phpunit.xml
diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/CHANGELOG.md b/src/Symfony/Component/Messenger/Bridge/Doctrine/CHANGELOG.md
new file mode 100644
index 0000000000..db21756b0c
--- /dev/null
+++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/CHANGELOG.md
@@ -0,0 +1,7 @@
+CHANGELOG
+=========
+
+5.1.0
+-----
+
+ * Introduced the Doctrine bridge.
diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/LICENSE b/src/Symfony/Component/Messenger/Bridge/Doctrine/LICENSE
new file mode 100644
index 0000000000..69d925ba75
--- /dev/null
+++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2018-2020 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/README.md b/src/Symfony/Component/Messenger/Bridge/Doctrine/README.md
new file mode 100644
index 0000000000..068490d574
--- /dev/null
+++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/README.md
@@ -0,0 +1,12 @@
+Doctrine Messenger
+==================
+
+Provides Doctrine integration for Symfony Messenger.
+
+Resources
+---------
+
+ * [Contributing](https://symfony.com/doc/current/contributing/index.html)
+ * [Report issues](https://github.com/symfony/symfony/issues) and
+ [send Pull Requests](https://github.com/symfony/symfony/pulls)
+ in the [main Symfony repository](https://github.com/symfony/symfony)
diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Fixtures/DummyMessage.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Fixtures/DummyMessage.php
new file mode 100644
index 0000000000..4ee9f6ef95
--- /dev/null
+++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Fixtures/DummyMessage.php
@@ -0,0 +1,18 @@
+message = $message;
+ }
+
+ public function getMessage(): string
+ {
+ return $this->message;
+ }
+}
diff --git a/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/ConnectionTest.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php
similarity index 98%
rename from src/Symfony/Component/Messenger/Tests/Transport/Doctrine/ConnectionTest.php
rename to src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php
index bd7fff769b..d685df5100 100644
--- a/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/ConnectionTest.php
+++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
-namespace Symfony\Component\Messenger\Tests\Transport\Doctrine;
+namespace Symfony\Component\Messenger\Bridge\Doctrine\Tests\Transport;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Driver\Statement;
@@ -19,8 +19,9 @@ use Doctrine\DBAL\Schema\AbstractSchemaManager;
use Doctrine\DBAL\Schema\SchemaConfig;
use Doctrine\DBAL\Schema\Synchronizer\SchemaSynchronizer;
use PHPUnit\Framework\TestCase;
-use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
-use Symfony\Component\Messenger\Transport\Doctrine\Connection;
+use Symfony\Component\Messenger\Bridge\Doctrine\Tests\Fixtures\DummyMessage;
+use Symfony\Component\Messenger\Bridge\Doctrine\Transport\Connection;
+
class ConnectionTest extends TestCase
{
diff --git a/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/DoctrineIntegrationTest.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineIntegrationTest.php
similarity index 97%
rename from src/Symfony/Component/Messenger/Tests/Transport/Doctrine/DoctrineIntegrationTest.php
rename to src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineIntegrationTest.php
index a01e68db39..64398d00e4 100644
--- a/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/DoctrineIntegrationTest.php
+++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineIntegrationTest.php
@@ -9,12 +9,12 @@
* file that was distributed with this source code.
*/
-namespace Symfony\Component\Messenger\Tests\Transport\Doctrine;
+namespace Symfony\Component\Messenger\Bridge\Doctrine\Tests\Transport;
use Doctrine\DBAL\DriverManager;
use PHPUnit\Framework\TestCase;
-use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
-use Symfony\Component\Messenger\Transport\Doctrine\Connection;
+use Symfony\Component\Messenger\Bridge\Doctrine\Tests\Fixtures\DummyMessage;
+use Symfony\Component\Messenger\Bridge\Doctrine\Transport\Connection;
/**
* @requires extension pdo_sqlite
diff --git a/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/DoctrineReceiverTest.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineReceiverTest.php
similarity index 93%
rename from src/Symfony/Component/Messenger/Tests/Transport/Doctrine/DoctrineReceiverTest.php
rename to src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineReceiverTest.php
index 45e4dd3b91..cc2c969a28 100644
--- a/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/DoctrineReceiverTest.php
+++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineReceiverTest.php
@@ -9,19 +9,19 @@
* file that was distributed with this source code.
*/
-namespace Symfony\Component\Messenger\Tests\Transport\Doctrine;
+namespace Symfony\Component\Messenger\Bridge\Doctrine\Tests\Transport;
use Doctrine\DBAL\Driver\PDOException;
use Doctrine\DBAL\Exception\DeadlockException;
use PHPUnit\Framework\TestCase;
+use Symfony\Component\Messenger\Bridge\Doctrine\Tests\Fixtures\DummyMessage;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Exception\MessageDecodingFailedException;
use Symfony\Component\Messenger\Exception\TransportException;
use Symfony\Component\Messenger\Stamp\TransportMessageIdStamp;
-use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
-use Symfony\Component\Messenger\Transport\Doctrine\Connection;
-use Symfony\Component\Messenger\Transport\Doctrine\DoctrineReceivedStamp;
-use Symfony\Component\Messenger\Transport\Doctrine\DoctrineReceiver;
+use Symfony\Component\Messenger\Bridge\Doctrine\Transport\Connection;
+use Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineReceivedStamp;
+use Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineReceiver;
use Symfony\Component\Messenger\Transport\Serialization\PhpSerializer;
use Symfony\Component\Messenger\Transport\Serialization\Serializer;
use Symfony\Component\Serializer as SerializerComponent;
diff --git a/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/DoctrineSenderTest.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineSenderTest.php
similarity index 88%
rename from src/Symfony/Component/Messenger/Tests/Transport/Doctrine/DoctrineSenderTest.php
rename to src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineSenderTest.php
index cb2d194ae1..c2953a524d 100644
--- a/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/DoctrineSenderTest.php
+++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineSenderTest.php
@@ -9,15 +9,15 @@
* file that was distributed with this source code.
*/
-namespace Symfony\Component\Messenger\Tests\Transport\Doctrine;
+namespace Symfony\Component\Messenger\Bridge\Doctrine\Tests\Transport;
use PHPUnit\Framework\TestCase;
+use Symfony\Component\Messenger\Bridge\Doctrine\Tests\Fixtures\DummyMessage;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Stamp\DelayStamp;
use Symfony\Component\Messenger\Stamp\TransportMessageIdStamp;
-use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
-use Symfony\Component\Messenger\Transport\Doctrine\Connection;
-use Symfony\Component\Messenger\Transport\Doctrine\DoctrineSender;
+use Symfony\Component\Messenger\Bridge\Doctrine\Transport\Connection;
+use Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineSender;
use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
class DoctrineSenderTest extends TestCase
diff --git a/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/DoctrineTransportFactoryTest.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineTransportFactoryTest.php
similarity index 89%
rename from src/Symfony/Component/Messenger/Tests/Transport/Doctrine/DoctrineTransportFactoryTest.php
rename to src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineTransportFactoryTest.php
index e124bf94e8..a5ed0d4a2a 100644
--- a/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/DoctrineTransportFactoryTest.php
+++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineTransportFactoryTest.php
@@ -9,15 +9,15 @@
* file that was distributed with this source code.
*/
-namespace Symfony\Component\Messenger\Tests\Transport\Doctrine;
+namespace Symfony\Component\Messenger\Bridge\Doctrine\Tests\Transport;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
use Doctrine\DBAL\Schema\SchemaConfig;
use Doctrine\Persistence\ConnectionRegistry;
use PHPUnit\Framework\TestCase;
-use Symfony\Component\Messenger\Transport\Doctrine\Connection;
-use Symfony\Component\Messenger\Transport\Doctrine\DoctrineTransport;
-use Symfony\Component\Messenger\Transport\Doctrine\DoctrineTransportFactory;
+use Symfony\Component\Messenger\Bridge\Doctrine\Transport\Connection;
+use Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineTransport;
+use Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineTransportFactory;
use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
class DoctrineTransportFactoryTest extends TestCase
diff --git a/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/DoctrineTransportTest.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineTransportTest.php
similarity index 85%
rename from src/Symfony/Component/Messenger/Tests/Transport/Doctrine/DoctrineTransportTest.php
rename to src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineTransportTest.php
index 96b5078b3a..f04764dded 100644
--- a/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/DoctrineTransportTest.php
+++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineTransportTest.php
@@ -9,13 +9,13 @@
* file that was distributed with this source code.
*/
-namespace Symfony\Component\Messenger\Tests\Transport\Doctrine;
+namespace Symfony\Component\Messenger\Bridge\Doctrine\Tests\Transport;
use PHPUnit\Framework\TestCase;
+use Symfony\Component\Messenger\Bridge\Doctrine\Tests\Fixtures\DummyMessage;
use Symfony\Component\Messenger\Envelope;
-use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
-use Symfony\Component\Messenger\Transport\Doctrine\Connection;
-use Symfony\Component\Messenger\Transport\Doctrine\DoctrineTransport;
+use Symfony\Component\Messenger\Bridge\Doctrine\Transport\Connection;
+use Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineTransport;
use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
use Symfony\Component\Messenger\Transport\TransportInterface;
diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php
new file mode 100644
index 0000000000..fa5abd6614
--- /dev/null
+++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php
@@ -0,0 +1,347 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Bridge\Doctrine\Transport;
+
+use Doctrine\DBAL\Connection as DBALConnection;
+use Doctrine\DBAL\DBALException;
+use Doctrine\DBAL\Driver\ResultStatement;
+use Doctrine\DBAL\Exception\TableNotFoundException;
+use Doctrine\DBAL\Query\QueryBuilder;
+use Doctrine\DBAL\Schema\Schema;
+use Doctrine\DBAL\Schema\Synchronizer\SchemaSynchronizer;
+use Doctrine\DBAL\Schema\Synchronizer\SingleDatabaseSynchronizer;
+use Doctrine\DBAL\Types\Type;
+use Symfony\Component\Messenger\Exception\InvalidArgumentException;
+use Symfony\Component\Messenger\Exception\TransportException;
+
+/**
+ * @author Vincent Touzet
+ *
+ * @final
+ */
+class Connection
+{
+ private const DEFAULT_OPTIONS = [
+ 'table_name' => 'messenger_messages',
+ 'queue_name' => 'default',
+ 'redeliver_timeout' => 3600,
+ 'auto_setup' => true,
+ ];
+
+ /**
+ * Configuration of the connection.
+ *
+ * Available options:
+ *
+ * * table_name: name of the table
+ * * connection: name of the Doctrine's entity manager
+ * * queue_name: name of the queue
+ * * redeliver_timeout: Timeout before redeliver messages still in handling state (i.e: delivered_at is not null and message is still in table). Default 3600
+ * * auto_setup: Whether the table should be created automatically during send / get. Default : true
+ */
+ private $configuration = [];
+ private $driverConnection;
+ private $schemaSynchronizer;
+ private $autoSetup;
+
+ public function __construct(array $configuration, DBALConnection $driverConnection, SchemaSynchronizer $schemaSynchronizer = null)
+ {
+ $this->configuration = array_replace_recursive(self::DEFAULT_OPTIONS, $configuration);
+ $this->driverConnection = $driverConnection;
+ $this->schemaSynchronizer = $schemaSynchronizer ?? new SingleDatabaseSynchronizer($this->driverConnection);
+ $this->autoSetup = $this->configuration['auto_setup'];
+ }
+
+ public function getConfiguration(): array
+ {
+ return $this->configuration;
+ }
+
+ public static function buildConfiguration(string $dsn, array $options = []): array
+ {
+ if (false === $components = parse_url($dsn)) {
+ throw new InvalidArgumentException(sprintf('The given Doctrine Messenger DSN "%s" is invalid.', $dsn));
+ }
+
+ $query = [];
+ if (isset($components['query'])) {
+ parse_str($components['query'], $query);
+ }
+
+ $configuration = ['connection' => $components['host']];
+ $configuration += $options + $query + self::DEFAULT_OPTIONS;
+
+ $configuration['auto_setup'] = filter_var($configuration['auto_setup'], FILTER_VALIDATE_BOOLEAN);
+
+ // check for extra keys in options
+ $optionsExtraKeys = array_diff(array_keys($options), array_keys(self::DEFAULT_OPTIONS));
+ if (0 < \count($optionsExtraKeys)) {
+ throw new InvalidArgumentException(sprintf('Unknown option found : [%s]. Allowed options are [%s]', implode(', ', $optionsExtraKeys), implode(', ', self::DEFAULT_OPTIONS)));
+ }
+
+ // check for extra keys in options
+ $queryExtraKeys = array_diff(array_keys($query), array_keys(self::DEFAULT_OPTIONS));
+ if (0 < \count($queryExtraKeys)) {
+ throw new InvalidArgumentException(sprintf('Unknown option found in DSN: [%s]. Allowed options are [%s]', implode(', ', $queryExtraKeys), implode(', ', self::DEFAULT_OPTIONS)));
+ }
+
+ return $configuration;
+ }
+
+ /**
+ * @param int $delay The delay in milliseconds
+ *
+ * @return string The inserted id
+ *
+ * @throws \Doctrine\DBAL\DBALException
+ */
+ public function send(string $body, array $headers, int $delay = 0): string
+ {
+ $now = new \DateTime();
+ $availableAt = (clone $now)->modify(sprintf('+%d seconds', $delay / 1000));
+
+ $queryBuilder = $this->driverConnection->createQueryBuilder()
+ ->insert($this->configuration['table_name'])
+ ->values([
+ 'body' => '?',
+ 'headers' => '?',
+ 'queue_name' => '?',
+ 'created_at' => '?',
+ 'available_at' => '?',
+ ]);
+
+ $this->executeQuery($queryBuilder->getSQL(), [
+ $body,
+ json_encode($headers),
+ $this->configuration['queue_name'],
+ $now,
+ $availableAt,
+ ], [
+ null,
+ null,
+ null,
+ Type::DATETIME,
+ Type::DATETIME,
+ ]);
+
+ return $this->driverConnection->lastInsertId();
+ }
+
+ public function get(): ?array
+ {
+ get:
+ $this->driverConnection->beginTransaction();
+ try {
+ $query = $this->createAvailableMessagesQueryBuilder()
+ ->orderBy('available_at', 'ASC')
+ ->setMaxResults(1);
+
+ // use SELECT ... FOR UPDATE to lock table
+ $doctrineEnvelope = $this->executeQuery(
+ $query->getSQL().' '.$this->driverConnection->getDatabasePlatform()->getWriteLockSQL(),
+ $query->getParameters(),
+ $query->getParameterTypes()
+ )->fetch();
+
+ if (false === $doctrineEnvelope) {
+ $this->driverConnection->commit();
+
+ return null;
+ }
+
+ $doctrineEnvelope = $this->decodeEnvelopeHeaders($doctrineEnvelope);
+
+ $queryBuilder = $this->driverConnection->createQueryBuilder()
+ ->update($this->configuration['table_name'])
+ ->set('delivered_at', '?')
+ ->where('id = ?');
+ $now = new \DateTime();
+ $this->executeQuery($queryBuilder->getSQL(), [
+ $now,
+ $doctrineEnvelope['id'],
+ ], [
+ Type::DATETIME,
+ ]);
+
+ $this->driverConnection->commit();
+
+ return $doctrineEnvelope;
+ } catch (\Throwable $e) {
+ $this->driverConnection->rollBack();
+
+ if ($this->autoSetup && $e instanceof TableNotFoundException) {
+ $this->setup();
+ goto get;
+ }
+
+ throw $e;
+ }
+ }
+
+ public function ack(string $id): bool
+ {
+ try {
+ return $this->driverConnection->delete($this->configuration['table_name'], ['id' => $id]) > 0;
+ } catch (DBALException $exception) {
+ throw new TransportException($exception->getMessage(), 0, $exception);
+ }
+ }
+
+ public function reject(string $id): bool
+ {
+ try {
+ return $this->driverConnection->delete($this->configuration['table_name'], ['id' => $id]) > 0;
+ } catch (DBALException $exception) {
+ throw new TransportException($exception->getMessage(), 0, $exception);
+ }
+ }
+
+ public function setup(): void
+ {
+ $configuration = $this->driverConnection->getConfiguration();
+ // Since Doctrine 2.9 the getFilterSchemaAssetsExpression is deprecated
+ $hasFilterCallback = method_exists($configuration, 'getSchemaAssetsFilter');
+
+ if ($hasFilterCallback) {
+ $assetFilter = $this->driverConnection->getConfiguration()->getSchemaAssetsFilter();
+ $this->driverConnection->getConfiguration()->setSchemaAssetsFilter(null);
+ } else {
+ $assetFilter = $this->driverConnection->getConfiguration()->getFilterSchemaAssetsExpression();
+ $this->driverConnection->getConfiguration()->setFilterSchemaAssetsExpression(null);
+ }
+
+ $this->schemaSynchronizer->updateSchema($this->getSchema(), true);
+
+ if ($hasFilterCallback) {
+ $this->driverConnection->getConfiguration()->setSchemaAssetsFilter($assetFilter);
+ } else {
+ $this->driverConnection->getConfiguration()->setFilterSchemaAssetsExpression($assetFilter);
+ }
+
+ $this->autoSetup = false;
+ }
+
+ public function getMessageCount(): int
+ {
+ $queryBuilder = $this->createAvailableMessagesQueryBuilder()
+ ->select('COUNT(m.id) as message_count')
+ ->setMaxResults(1);
+
+ return $this->executeQuery($queryBuilder->getSQL(), $queryBuilder->getParameters(), $queryBuilder->getParameterTypes())->fetchColumn();
+ }
+
+ public function findAll(int $limit = null): array
+ {
+ $queryBuilder = $this->createAvailableMessagesQueryBuilder();
+ if (null !== $limit) {
+ $queryBuilder->setMaxResults($limit);
+ }
+
+ $data = $this->executeQuery($queryBuilder->getSQL(), $queryBuilder->getParameters(), $queryBuilder->getParameterTypes())->fetchAll();
+
+ return array_map(function ($doctrineEnvelope) {
+ return $this->decodeEnvelopeHeaders($doctrineEnvelope);
+ }, $data);
+ }
+
+ public function find($id): ?array
+ {
+ $queryBuilder = $this->createQueryBuilder()
+ ->where('m.id = ?');
+
+ $data = $this->executeQuery($queryBuilder->getSQL(), [
+ $id,
+ ])->fetch();
+
+ return false === $data ? null : $this->decodeEnvelopeHeaders($data);
+ }
+
+ private function createAvailableMessagesQueryBuilder(): QueryBuilder
+ {
+ $now = new \DateTime();
+ $redeliverLimit = (clone $now)->modify(sprintf('-%d seconds', $this->configuration['redeliver_timeout']));
+
+ return $this->createQueryBuilder()
+ ->where('m.delivered_at is null OR m.delivered_at < ?')
+ ->andWhere('m.available_at <= ?')
+ ->andWhere('m.queue_name = ?')
+ ->setParameters([
+ $redeliverLimit,
+ $now,
+ $this->configuration['queue_name'],
+ ], [
+ Type::DATETIME,
+ Type::DATETIME,
+ ]);
+ }
+
+ private function createQueryBuilder(): QueryBuilder
+ {
+ return $this->driverConnection->createQueryBuilder()
+ ->select('m.*')
+ ->from($this->configuration['table_name'], 'm');
+ }
+
+ private function executeQuery(string $sql, array $parameters = [], array $types = []): ResultStatement
+ {
+ try {
+ $stmt = $this->driverConnection->executeQuery($sql, $parameters, $types);
+ } catch (TableNotFoundException $e) {
+ if ($this->driverConnection->isTransactionActive()) {
+ throw $e;
+ }
+
+ // create table
+ if ($this->autoSetup) {
+ $this->setup();
+ }
+ $stmt = $this->driverConnection->executeQuery($sql, $parameters, $types);
+ }
+
+ return $stmt;
+ }
+
+ private function getSchema(): Schema
+ {
+ $schema = new Schema([], [], $this->driverConnection->getSchemaManager()->createSchemaConfig());
+ $table = $schema->createTable($this->configuration['table_name']);
+ $table->addColumn('id', Type::BIGINT)
+ ->setAutoincrement(true)
+ ->setNotnull(true);
+ $table->addColumn('body', Type::TEXT)
+ ->setNotnull(true);
+ $table->addColumn('headers', Type::TEXT)
+ ->setNotnull(true);
+ $table->addColumn('queue_name', Type::STRING)
+ ->setNotnull(true);
+ $table->addColumn('created_at', Type::DATETIME)
+ ->setNotnull(true);
+ $table->addColumn('available_at', Type::DATETIME)
+ ->setNotnull(true);
+ $table->addColumn('delivered_at', Type::DATETIME)
+ ->setNotnull(false);
+ $table->setPrimaryKey(['id']);
+ $table->addIndex(['queue_name']);
+ $table->addIndex(['available_at']);
+ $table->addIndex(['delivered_at']);
+
+ return $schema;
+ }
+
+ private function decodeEnvelopeHeaders(array $doctrineEnvelope): array
+ {
+ $doctrineEnvelope['headers'] = json_decode($doctrineEnvelope['headers'], true);
+
+ return $doctrineEnvelope;
+ }
+}
+class_alias(Connection::class, \Symfony\Component\Messenger\Transport\Doctrine\Connection::class);
diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineReceivedStamp.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineReceivedStamp.php
new file mode 100644
index 0000000000..6ec3389ab6
--- /dev/null
+++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineReceivedStamp.php
@@ -0,0 +1,33 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Bridge\Doctrine\Transport;
+
+use Symfony\Component\Messenger\Stamp\NonSendableStampInterface;
+
+/**
+ * @author Vincent Touzet
+ */
+class DoctrineReceivedStamp implements NonSendableStampInterface
+{
+ private $id;
+
+ public function __construct(string $id)
+ {
+ $this->id = $id;
+ }
+
+ public function getId(): string
+ {
+ return $this->id;
+ }
+}
+class_alias(DoctrineReceivedStamp::class, \Symfony\Component\Messenger\Transport\Doctrine\DoctrineReceivedStamp::class);
diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineReceiver.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineReceiver.php
new file mode 100644
index 0000000000..872e0c9278
--- /dev/null
+++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineReceiver.php
@@ -0,0 +1,173 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Bridge\Doctrine\Transport;
+
+use Doctrine\DBAL\DBALException;
+use Doctrine\DBAL\Exception\RetryableException;
+use Symfony\Component\Messenger\Envelope;
+use Symfony\Component\Messenger\Exception\LogicException;
+use Symfony\Component\Messenger\Exception\MessageDecodingFailedException;
+use Symfony\Component\Messenger\Exception\TransportException;
+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\Messenger\Transport\Serialization\SerializerInterface;
+
+/**
+ * @author Vincent Touzet
+ */
+class DoctrineReceiver implements ReceiverInterface, MessageCountAwareInterface, ListableReceiverInterface
+{
+ private const MAX_RETRIES = 3;
+ private $retryingSafetyCounter = 0;
+ private $connection;
+ private $serializer;
+
+ public function __construct(Connection $connection, SerializerInterface $serializer = null)
+ {
+ $this->connection = $connection;
+ $this->serializer = $serializer ?? new PhpSerializer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get(): iterable
+ {
+ try {
+ $doctrineEnvelope = $this->connection->get();
+ $this->retryingSafetyCounter = 0; // reset counter
+ } catch (RetryableException $exception) {
+ // Do nothing when RetryableException occurs less than "MAX_RETRIES"
+ // as it will likely be resolved on the next call to get()
+ // Problem with concurrent consumers and database deadlocks
+ if (++$this->retryingSafetyCounter >= self::MAX_RETRIES) {
+ $this->retryingSafetyCounter = 0; // reset counter
+ throw new TransportException($exception->getMessage(), 0, $exception);
+ }
+
+ return [];
+ } catch (DBALException $exception) {
+ throw new TransportException($exception->getMessage(), 0, $exception);
+ }
+
+ if (null === $doctrineEnvelope) {
+ return [];
+ }
+
+ return [$this->createEnvelopeFromData($doctrineEnvelope)];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function ack(Envelope $envelope): void
+ {
+ try {
+ $this->connection->ack($this->findDoctrineReceivedStamp($envelope)->getId());
+ } catch (DBALException $exception) {
+ throw new TransportException($exception->getMessage(), 0, $exception);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reject(Envelope $envelope): void
+ {
+ try {
+ $this->connection->reject($this->findDoctrineReceivedStamp($envelope)->getId());
+ } catch (DBALException $exception) {
+ throw new TransportException($exception->getMessage(), 0, $exception);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMessageCount(): int
+ {
+ try {
+ return $this->connection->getMessageCount();
+ } catch (DBALException $exception) {
+ throw new TransportException($exception->getMessage(), 0, $exception);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function all(int $limit = null): iterable
+ {
+ try {
+ $doctrineEnvelopes = $this->connection->findAll($limit);
+ } catch (DBALException $exception) {
+ throw new TransportException($exception->getMessage(), 0, $exception);
+ }
+
+ foreach ($doctrineEnvelopes as $doctrineEnvelope) {
+ yield $this->createEnvelopeFromData($doctrineEnvelope);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function find($id): ?Envelope
+ {
+ try {
+ $doctrineEnvelope = $this->connection->find($id);
+ } catch (DBALException $exception) {
+ throw new TransportException($exception->getMessage(), 0, $exception);
+ }
+
+ if (null === $doctrineEnvelope) {
+ return null;
+ }
+
+ return $this->createEnvelopeFromData($doctrineEnvelope);
+ }
+
+ private function findDoctrineReceivedStamp(Envelope $envelope): DoctrineReceivedStamp
+ {
+ /** @var DoctrineReceivedStamp|null $doctrineReceivedStamp */
+ $doctrineReceivedStamp = $envelope->last(DoctrineReceivedStamp::class);
+
+ if (null === $doctrineReceivedStamp) {
+ throw new LogicException('No DoctrineReceivedStamp found on the Envelope.');
+ }
+
+ return $doctrineReceivedStamp;
+ }
+
+ private function createEnvelopeFromData(array $data): Envelope
+ {
+ try {
+ $envelope = $this->serializer->decode([
+ 'body' => $data['body'],
+ 'headers' => $data['headers'],
+ ]);
+ } catch (MessageDecodingFailedException $exception) {
+ $this->connection->reject($data['id']);
+
+ throw $exception;
+ }
+
+ return $envelope->with(
+ new DoctrineReceivedStamp($data['id']),
+ new TransportMessageIdStamp($data['id'])
+ );
+ }
+}
+class_alias(DoctrineReceiver::class, \Symfony\Component\Messenger\Transport\Doctrine\DoctrineReceiver::class);
diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineSender.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineSender.php
new file mode 100644
index 0000000000..db46afd2b3
--- /dev/null
+++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineSender.php
@@ -0,0 +1,57 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Bridge\Doctrine\Transport;
+
+use Doctrine\DBAL\DBALException;
+use Symfony\Component\Messenger\Envelope;
+use Symfony\Component\Messenger\Exception\TransportException;
+use Symfony\Component\Messenger\Stamp\DelayStamp;
+use Symfony\Component\Messenger\Stamp\TransportMessageIdStamp;
+use Symfony\Component\Messenger\Transport\Sender\SenderInterface;
+use Symfony\Component\Messenger\Transport\Serialization\PhpSerializer;
+use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
+
+/**
+ * @author Vincent Touzet
+ */
+class DoctrineSender implements SenderInterface
+{
+ private $connection;
+ private $serializer;
+
+ public function __construct(Connection $connection, SerializerInterface $serializer = null)
+ {
+ $this->connection = $connection;
+ $this->serializer = $serializer ?? new PhpSerializer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function send(Envelope $envelope): Envelope
+ {
+ $encodedMessage = $this->serializer->encode($envelope);
+
+ /** @var DelayStamp|null $delayStamp */
+ $delayStamp = $envelope->last(DelayStamp::class);
+ $delay = null !== $delayStamp ? $delayStamp->getDelay() : 0;
+
+ try {
+ $id = $this->connection->send($encodedMessage['body'], $encodedMessage['headers'] ?? [], $delay);
+ } catch (DBALException $exception) {
+ throw new TransportException($exception->getMessage(), 0, $exception);
+ }
+
+ return $envelope->with(new TransportMessageIdStamp($id));
+ }
+}
+class_alias(DoctrineSender::class, \Symfony\Component\Messenger\Transport\Doctrine\DoctrineSender::class);
diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineTransport.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineTransport.php
new file mode 100644
index 0000000000..e9695e03a1
--- /dev/null
+++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineTransport.php
@@ -0,0 +1,111 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Bridge\Doctrine\Transport;
+
+use Symfony\Component\Messenger\Envelope;
+use Symfony\Component\Messenger\Transport\Receiver\ListableReceiverInterface;
+use Symfony\Component\Messenger\Transport\Receiver\MessageCountAwareInterface;
+use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
+use Symfony\Component\Messenger\Transport\SetupableTransportInterface;
+use Symfony\Component\Messenger\Transport\TransportInterface;
+
+/**
+ * @author Vincent Touzet
+ */
+class DoctrineTransport implements TransportInterface, SetupableTransportInterface, MessageCountAwareInterface, ListableReceiverInterface
+{
+ private $connection;
+ private $serializer;
+ private $receiver;
+ private $sender;
+
+ public function __construct(Connection $connection, SerializerInterface $serializer)
+ {
+ $this->connection = $connection;
+ $this->serializer = $serializer;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get(): iterable
+ {
+ return ($this->receiver ?? $this->getReceiver())->get();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function ack(Envelope $envelope): void
+ {
+ ($this->receiver ?? $this->getReceiver())->ack($envelope);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reject(Envelope $envelope): void
+ {
+ ($this->receiver ?? $this->getReceiver())->reject($envelope);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMessageCount(): int
+ {
+ return ($this->receiver ?? $this->getReceiver())->getMessageCount();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function all(int $limit = null): iterable
+ {
+ return ($this->receiver ?? $this->getReceiver())->all($limit);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function find($id): ?Envelope
+ {
+ return ($this->receiver ?? $this->getReceiver())->find($id);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function send(Envelope $envelope): Envelope
+ {
+ return ($this->sender ?? $this->getSender())->send($envelope);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setup(): void
+ {
+ $this->connection->setup();
+ }
+
+ private function getReceiver(): DoctrineReceiver
+ {
+ return $this->receiver = new DoctrineReceiver($this->connection, $this->serializer);
+ }
+
+ private function getSender(): DoctrineSender
+ {
+ return $this->sender = new DoctrineSender($this->connection, $this->serializer);
+ }
+}
+class_alias(DoctrineTransport::class, \Symfony\Component\Messenger\Transport\Doctrine\DoctrineTransport::class);
diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineTransportFactory.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineTransportFactory.php
new file mode 100644
index 0000000000..3cd9089110
--- /dev/null
+++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineTransportFactory.php
@@ -0,0 +1,58 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Bridge\Doctrine\Transport;
+
+use Doctrine\Persistence\ConnectionRegistry;
+use Symfony\Bridge\Doctrine\RegistryInterface;
+use Symfony\Component\Messenger\Exception\TransportException;
+use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
+use Symfony\Component\Messenger\Transport\TransportFactoryInterface;
+use Symfony\Component\Messenger\Transport\TransportInterface;
+
+/**
+ * @author Vincent Touzet
+ */
+class DoctrineTransportFactory implements TransportFactoryInterface
+{
+ private $registry;
+
+ public function __construct($registry)
+ {
+ if (!$registry instanceof RegistryInterface && !$registry instanceof ConnectionRegistry) {
+ throw new \TypeError(sprintf('Expected an instance of %s or %s, but got %s.', RegistryInterface::class, ConnectionRegistry::class, \is_object($registry) ? \get_class($registry) : \gettype($registry)));
+ }
+
+ $this->registry = $registry;
+ }
+
+ public function createTransport(string $dsn, array $options, SerializerInterface $serializer): TransportInterface
+ {
+ unset($options['transport_name']);
+ $configuration = Connection::buildConfiguration($dsn, $options);
+
+ try {
+ $driverConnection = $this->registry->getConnection($configuration['connection']);
+ } catch (\InvalidArgumentException $e) {
+ throw new TransportException(sprintf('Could not find Doctrine connection from Messenger DSN "%s".', $dsn), 0, $e);
+ }
+
+ $connection = new Connection($configuration, $driverConnection);
+
+ return new DoctrineTransport($connection, $serializer);
+ }
+
+ public function supports(string $dsn, array $options): bool
+ {
+ return 0 === strpos($dsn, 'doctrine://');
+ }
+}
+class_alias(DoctrineTransportFactory::class, \Symfony\Component\Messenger\Transport\Doctrine\DoctrineTransportFactory::class);
diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/composer.json b/src/Symfony/Component/Messenger/Bridge/Doctrine/composer.json
new file mode 100644
index 0000000000..9b4c738268
--- /dev/null
+++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/composer.json
@@ -0,0 +1,43 @@
+{
+ "name": "symfony/doctrine-messenger",
+ "type": "symfony-bridge",
+ "description": "Symfony Doctrine Messenger Bridge",
+ "keywords": [],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": "^7.2.5",
+ "doctrine/dbal": "^2.6",
+ "doctrine/persistence": "^1.3",
+ "symfony/messenger": "^5.1"
+ },
+ "require-dev": {
+ "symfony/serializer": "^4.4|^5.0",
+ "symfony/property-access": "^4.4|^5.0"
+ },
+ "conflict": {
+ "doctrine/persistence": "<1.3"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Component\\Messenger\\Bridge\\Doctrine\\": "" },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.1-dev"
+ }
+ }
+}
diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/phpunit.xml.dist b/src/Symfony/Component/Messenger/Bridge/Doctrine/phpunit.xml.dist
new file mode 100644
index 0000000000..ed2aa6014f
--- /dev/null
+++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/phpunit.xml.dist
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+ ./Tests/
+
+
+
+
+
+ ./
+
+ ./Tests
+ ./vendor
+
+
+
+
diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/.gitattributes b/src/Symfony/Component/Messenger/Bridge/Redis/.gitattributes
new file mode 100644
index 0000000000..ebb9287043
--- /dev/null
+++ b/src/Symfony/Component/Messenger/Bridge/Redis/.gitattributes
@@ -0,0 +1,3 @@
+/Tests export-ignore
+/phpunit.xml.dist export-ignore
+/.gitignore export-ignore
diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/.gitignore b/src/Symfony/Component/Messenger/Bridge/Redis/.gitignore
new file mode 100644
index 0000000000..c49a5d8df5
--- /dev/null
+++ b/src/Symfony/Component/Messenger/Bridge/Redis/.gitignore
@@ -0,0 +1,3 @@
+vendor/
+composer.lock
+phpunit.xml
diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/CHANGELOG.md b/src/Symfony/Component/Messenger/Bridge/Redis/CHANGELOG.md
new file mode 100644
index 0000000000..4ebe764927
--- /dev/null
+++ b/src/Symfony/Component/Messenger/Bridge/Redis/CHANGELOG.md
@@ -0,0 +1,7 @@
+CHANGELOG
+=========
+
+5.1.0
+-----
+
+ * Introduced the Redis bridge.
diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/LICENSE b/src/Symfony/Component/Messenger/Bridge/Redis/LICENSE
new file mode 100644
index 0000000000..69d925ba75
--- /dev/null
+++ b/src/Symfony/Component/Messenger/Bridge/Redis/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2018-2020 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/README.md b/src/Symfony/Component/Messenger/Bridge/Redis/README.md
new file mode 100644
index 0000000000..b363ce6198
--- /dev/null
+++ b/src/Symfony/Component/Messenger/Bridge/Redis/README.md
@@ -0,0 +1,12 @@
+Redis Messenger
+===============
+
+Provides Redis integration for Symfony Messenger.
+
+Resources
+---------
+
+ * [Contributing](https://symfony.com/doc/current/contributing/index.html)
+ * [Report issues](https://github.com/symfony/symfony/issues) and
+ [send Pull Requests](https://github.com/symfony/symfony/pulls)
+ in the [main Symfony repository](https://github.com/symfony/symfony)
diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Fixtures/DummyMessage.php b/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Fixtures/DummyMessage.php
new file mode 100644
index 0000000000..92f8a89c01
--- /dev/null
+++ b/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Fixtures/DummyMessage.php
@@ -0,0 +1,18 @@
+message = $message;
+ }
+
+ public function getMessage(): string
+ {
+ return $this->message;
+ }
+}
diff --git a/src/Symfony/Component/Messenger/Tests/Transport/RedisExt/ConnectionTest.php b/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/ConnectionTest.php
similarity index 98%
rename from src/Symfony/Component/Messenger/Tests/Transport/RedisExt/ConnectionTest.php
rename to src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/ConnectionTest.php
index 837abaec01..fd7ab71df8 100644
--- a/src/Symfony/Component/Messenger/Tests/Transport/RedisExt/ConnectionTest.php
+++ b/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/ConnectionTest.php
@@ -9,11 +9,11 @@
* file that was distributed with this source code.
*/
-namespace Symfony\Component\Messenger\Tests\Transport\RedisExt;
+namespace Symfony\Component\Messenger\Bridge\Redis\Tests\Transport;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Messenger\Exception\TransportException;
-use Symfony\Component\Messenger\Transport\RedisExt\Connection;
+use Symfony\Component\Messenger\Bridge\Redis\Transport\Connection;
/**
* @requires extension redis >= 4.3.0
diff --git a/src/Symfony/Component/Messenger/Tests/Transport/RedisExt/RedisExtIntegrationTest.php b/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RedisExtIntegrationTest.php
similarity index 94%
rename from src/Symfony/Component/Messenger/Tests/Transport/RedisExt/RedisExtIntegrationTest.php
rename to src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RedisExtIntegrationTest.php
index e2375511d6..cb65eddf03 100644
--- a/src/Symfony/Component/Messenger/Tests/Transport/RedisExt/RedisExtIntegrationTest.php
+++ b/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RedisExtIntegrationTest.php
@@ -9,11 +9,11 @@
* file that was distributed with this source code.
*/
-namespace Symfony\Component\Messenger\Tests\Transport\RedisExt;
+namespace Symfony\Component\Messenger\Bridge\Redis\Tests\Transport;
use PHPUnit\Framework\TestCase;
-use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
-use Symfony\Component\Messenger\Transport\RedisExt\Connection;
+use Symfony\Component\Messenger\Bridge\Redis\Tests\Fixtures\DummyMessage;
+use Symfony\Component\Messenger\Bridge\Redis\Transport\Connection;
/**
* @requires extension redis
diff --git a/src/Symfony/Component/Messenger/Tests/Transport/RedisExt/RedisReceiverTest.php b/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RedisReceiverTest.php
similarity index 89%
rename from src/Symfony/Component/Messenger/Tests/Transport/RedisExt/RedisReceiverTest.php
rename to src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RedisReceiverTest.php
index 0da0e78ff8..ec12e37d5f 100644
--- a/src/Symfony/Component/Messenger/Tests/Transport/RedisExt/RedisReceiverTest.php
+++ b/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RedisReceiverTest.php
@@ -9,13 +9,13 @@
* file that was distributed with this source code.
*/
-namespace Symfony\Component\Messenger\Tests\Transport\RedisExt;
+namespace Symfony\Component\Messenger\Bridge\Redis\Tests\Transport;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Messenger\Exception\MessageDecodingFailedException;
-use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
-use Symfony\Component\Messenger\Transport\RedisExt\Connection;
-use Symfony\Component\Messenger\Transport\RedisExt\RedisReceiver;
+use Symfony\Component\Messenger\Bridge\Redis\Tests\Fixtures\DummyMessage;
+use Symfony\Component\Messenger\Bridge\Redis\Transport\Connection;
+use Symfony\Component\Messenger\Bridge\Redis\Transport\RedisReceiver;
use Symfony\Component\Messenger\Transport\Serialization\PhpSerializer;
use Symfony\Component\Messenger\Transport\Serialization\Serializer;
use Symfony\Component\Serializer as SerializerComponent;
diff --git a/src/Symfony/Component/Messenger/Tests/Transport/RedisExt/RedisSenderTest.php b/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RedisSenderTest.php
similarity index 80%
rename from src/Symfony/Component/Messenger/Tests/Transport/RedisExt/RedisSenderTest.php
rename to src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RedisSenderTest.php
index 5cbda34e10..26231a1c3e 100644
--- a/src/Symfony/Component/Messenger/Tests/Transport/RedisExt/RedisSenderTest.php
+++ b/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RedisSenderTest.php
@@ -9,13 +9,13 @@
* file that was distributed with this source code.
*/
-namespace Symfony\Component\Messenger\Tests\Transport\RedisExt;
+namespace Symfony\Component\Messenger\Bridge\Redis\Tests\Transport;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Messenger\Envelope;
-use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
-use Symfony\Component\Messenger\Transport\RedisExt\Connection;
-use Symfony\Component\Messenger\Transport\RedisExt\RedisSender;
+use Symfony\Component\Messenger\Bridge\Redis\Tests\Fixtures\DummyMessage;
+use Symfony\Component\Messenger\Bridge\Redis\Transport\Connection;
+use Symfony\Component\Messenger\Bridge\Redis\Transport\RedisSender;
use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
class RedisSenderTest extends TestCase
diff --git a/src/Symfony/Component/Messenger/Tests/Transport/RedisExt/RedisTransportFactoryTest.php b/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RedisTransportFactoryTest.php
similarity index 80%
rename from src/Symfony/Component/Messenger/Tests/Transport/RedisExt/RedisTransportFactoryTest.php
rename to src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RedisTransportFactoryTest.php
index 58b71536cf..07248e05ab 100644
--- a/src/Symfony/Component/Messenger/Tests/Transport/RedisExt/RedisTransportFactoryTest.php
+++ b/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RedisTransportFactoryTest.php
@@ -9,12 +9,12 @@
* file that was distributed with this source code.
*/
-namespace Symfony\Component\Messenger\Tests\Transport\RedisExt;
+namespace Symfony\Component\Messenger\Bridge\Redis\Tests\Transport;
use PHPUnit\Framework\TestCase;
-use Symfony\Component\Messenger\Transport\RedisExt\Connection;
-use Symfony\Component\Messenger\Transport\RedisExt\RedisTransport;
-use Symfony\Component\Messenger\Transport\RedisExt\RedisTransportFactory;
+use Symfony\Component\Messenger\Bridge\Redis\Transport\Connection;
+use Symfony\Component\Messenger\Bridge\Redis\Transport\RedisTransport;
+use Symfony\Component\Messenger\Bridge\Redis\Transport\RedisTransportFactory;
use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
/**
diff --git a/src/Symfony/Component/Messenger/Tests/Transport/RedisExt/RedisTransportTest.php b/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RedisTransportTest.php
similarity index 87%
rename from src/Symfony/Component/Messenger/Tests/Transport/RedisExt/RedisTransportTest.php
rename to src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RedisTransportTest.php
index 8ca97243ae..16e022f68c 100644
--- a/src/Symfony/Component/Messenger/Tests/Transport/RedisExt/RedisTransportTest.php
+++ b/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RedisTransportTest.php
@@ -9,13 +9,13 @@
* file that was distributed with this source code.
*/
-namespace Symfony\Component\Messenger\Tests\Transport\RedisExt;
+namespace Symfony\Component\Messenger\Bridge\Redis\Tests\Transport;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Messenger\Envelope;
-use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
-use Symfony\Component\Messenger\Transport\RedisExt\Connection;
-use Symfony\Component\Messenger\Transport\RedisExt\RedisTransport;
+use Symfony\Component\Messenger\Bridge\Redis\Tests\Fixtures\DummyMessage;
+use Symfony\Component\Messenger\Bridge\Redis\Transport\Connection;
+use Symfony\Component\Messenger\Bridge\Redis\Transport\RedisTransport;
use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
use Symfony\Component\Messenger\Transport\TransportInterface;
diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php
new file mode 100644
index 0000000000..d73dc5259a
--- /dev/null
+++ b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php
@@ -0,0 +1,329 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Bridge\Redis\Transport;
+
+use Symfony\Component\Messenger\Exception\InvalidArgumentException;
+use Symfony\Component\Messenger\Exception\LogicException;
+use Symfony\Component\Messenger\Exception\TransportException;
+
+/**
+ * A Redis connection.
+ *
+ * @author Alexander Schranz
+ * @author Antoine Bluchet
+ * @author Robin Chalas
+ *
+ * @internal
+ * @final
+ */
+class Connection
+{
+ private const DEFAULT_OPTIONS = [
+ 'stream' => 'messages',
+ 'group' => 'symfony',
+ 'consumer' => 'consumer',
+ 'auto_setup' => true,
+ 'stream_max_entries' => 0, // any value higher than 0 defines an approximate maximum number of stream entries
+ 'dbindex' => 0,
+ ];
+
+ private $connection;
+ private $stream;
+ private $queue;
+ private $group;
+ private $consumer;
+ private $autoSetup;
+ private $maxEntries;
+ private $couldHavePendingMessages = true;
+
+ public function __construct(array $configuration, array $connectionCredentials = [], array $redisOptions = [], \Redis $redis = null)
+ {
+ if (version_compare(phpversion('redis'), '4.3.0', '<')) {
+ throw new LogicException('The redis transport requires php-redis 4.3.0 or higher.');
+ }
+
+ $this->connection = $redis ?: new \Redis();
+ $this->connection->connect($connectionCredentials['host'] ?? '127.0.0.1', $connectionCredentials['port'] ?? 6379);
+ $this->connection->setOption(\Redis::OPT_SERIALIZER, $redisOptions['serializer'] ?? \Redis::SERIALIZER_PHP);
+
+ if (isset($connectionCredentials['auth']) && !$this->connection->auth($connectionCredentials['auth'])) {
+ throw new InvalidArgumentException(sprintf('Redis connection failed: %s', $redis->getLastError()));
+ }
+
+ if (($dbIndex = $configuration['dbindex'] ?? self::DEFAULT_OPTIONS['dbindex']) && !$this->connection->select($dbIndex)) {
+ throw new InvalidArgumentException(sprintf('Redis connection failed: %s', $redis->getLastError()));
+ }
+
+ $this->stream = $configuration['stream'] ?? self::DEFAULT_OPTIONS['stream'];
+ $this->group = $configuration['group'] ?? self::DEFAULT_OPTIONS['group'];
+ $this->consumer = $configuration['consumer'] ?? self::DEFAULT_OPTIONS['consumer'];
+ $this->queue = $this->stream.'__queue';
+ $this->autoSetup = $configuration['auto_setup'] ?? self::DEFAULT_OPTIONS['auto_setup'];
+ $this->maxEntries = $configuration['stream_max_entries'] ?? self::DEFAULT_OPTIONS['stream_max_entries'];
+ }
+
+ public static function fromDsn(string $dsn, array $redisOptions = [], \Redis $redis = null): self
+ {
+ $url = $dsn;
+
+ if (preg_match('#^redis:///([^:@])+$#', $dsn)) {
+ $url = str_replace('redis:', 'file:', $dsn);
+ }
+
+ if (false === $parsedUrl = parse_url($url)) {
+ throw new InvalidArgumentException(sprintf('The given Redis DSN "%s" is invalid.', $dsn));
+ }
+ if (isset($parsedUrl['query'])) {
+ parse_str($parsedUrl['query'], $redisOptions);
+ }
+
+ $autoSetup = null;
+ if (\array_key_exists('auto_setup', $redisOptions)) {
+ $autoSetup = filter_var($redisOptions['auto_setup'], FILTER_VALIDATE_BOOLEAN);
+ unset($redisOptions['auto_setup']);
+ }
+
+ $maxEntries = null;
+ if (\array_key_exists('stream_max_entries', $redisOptions)) {
+ $maxEntries = filter_var($redisOptions['stream_max_entries'], FILTER_VALIDATE_INT);
+ unset($redisOptions['stream_max_entries']);
+ }
+
+ $dbIndex = null;
+ if (\array_key_exists('dbindex', $redisOptions)) {
+ $dbIndex = filter_var($redisOptions['dbindex'], FILTER_VALIDATE_INT);
+ unset($redisOptions['dbindex']);
+ }
+
+ $configuration = [
+ 'stream' => $redisOptions['stream'] ?? null,
+ 'group' => $redisOptions['group'] ?? null,
+ 'consumer' => $redisOptions['consumer'] ?? null,
+ 'auto_setup' => $autoSetup,
+ 'stream_max_entries' => $maxEntries,
+ 'dbindex' => $dbIndex,
+ ];
+
+ if (isset($parsedUrl['host'])) {
+ $connectionCredentials = [
+ 'host' => $parsedUrl['host'] ?? '127.0.0.1',
+ 'port' => $parsedUrl['port'] ?? 6379,
+ 'auth' => $parsedUrl['pass'] ?? $parsedUrl['user'] ?? null,
+ ];
+
+ $pathParts = explode('/', $parsedUrl['path'] ?? '');
+
+ $configuration['stream'] = $pathParts[1] ?? $configuration['stream'];
+ $configuration['group'] = $pathParts[2] ?? $configuration['group'];
+ $configuration['consumer'] = $pathParts[3] ?? $configuration['consumer'];
+ } else {
+ $connectionCredentials = [
+ 'host' => $parsedUrl['path'],
+ 'port' => 0,
+ ];
+ }
+
+ return new self($configuration, $connectionCredentials, $redisOptions, $redis);
+ }
+
+ public function get(): ?array
+ {
+ if ($this->autoSetup) {
+ $this->setup();
+ }
+
+ try {
+ $queuedMessageCount = $this->connection->zcount($this->queue, 0, $this->getCurrentTimeInMilliseconds());
+ } catch (\RedisException $e) {
+ throw new TransportException($e->getMessage(), 0, $e);
+ }
+
+ if ($queuedMessageCount) {
+ for ($i = 0; $i < $queuedMessageCount; ++$i) {
+ try {
+ $queuedMessages = $this->connection->zpopmin($this->queue, 1);
+ } catch (\RedisException $e) {
+ throw new TransportException($e->getMessage(), 0, $e);
+ }
+
+ foreach ($queuedMessages as $queuedMessage => $time) {
+ $queuedMessage = json_decode($queuedMessage, true);
+ // if a futured placed message is actually popped because of a race condition with
+ // another running message consumer, the message is readded to the queue by add function
+ // else its just added stream and will be available for all stream consumers
+ $this->add(
+ $queuedMessage['body'],
+ $queuedMessage['headers'],
+ $time - $this->getCurrentTimeInMilliseconds()
+ );
+ }
+ }
+ }
+
+ $messageId = '>'; // will receive new messages
+
+ if ($this->couldHavePendingMessages) {
+ $messageId = '0'; // will receive consumers pending messages
+ }
+
+ try {
+ $messages = $this->connection->xreadgroup(
+ $this->group,
+ $this->consumer,
+ [$this->stream => $messageId],
+ 1
+ );
+ } catch (\RedisException $e) {
+ throw new TransportException($e->getMessage(), 0, $e);
+ }
+
+ if (false === $messages) {
+ if ($error = $this->connection->getLastError() ?: null) {
+ $this->connection->clearLastError();
+ }
+
+ throw new TransportException($error ?? 'Could not read messages from the redis stream.');
+ }
+
+ if ($this->couldHavePendingMessages && empty($messages[$this->stream])) {
+ $this->couldHavePendingMessages = false;
+
+ // No pending messages so get a new one
+ return $this->get();
+ }
+
+ foreach ($messages[$this->stream] ?? [] as $key => $message) {
+ $redisEnvelope = json_decode($message['message'], true);
+
+ return [
+ 'id' => $key,
+ 'body' => $redisEnvelope['body'],
+ 'headers' => $redisEnvelope['headers'],
+ ];
+ }
+
+ return null;
+ }
+
+ public function ack(string $id): void
+ {
+ try {
+ $acknowledged = $this->connection->xack($this->stream, $this->group, [$id]);
+ } catch (\RedisException $e) {
+ throw new TransportException($e->getMessage(), 0, $e);
+ }
+
+ if (!$acknowledged) {
+ if ($error = $this->connection->getLastError() ?: null) {
+ $this->connection->clearLastError();
+ }
+ throw new TransportException($error ?? sprintf('Could not acknowledge redis message "%s".', $id));
+ }
+ }
+
+ public function reject(string $id): void
+ {
+ try {
+ $deleted = $this->connection->xack($this->stream, $this->group, [$id]);
+ $deleted = $this->connection->xdel($this->stream, [$id]) && $deleted;
+ } catch (\RedisException $e) {
+ throw new TransportException($e->getMessage(), 0, $e);
+ }
+
+ if (!$deleted) {
+ if ($error = $this->connection->getLastError() ?: null) {
+ $this->connection->clearLastError();
+ }
+ throw new TransportException($error ?? sprintf('Could not delete message "%s" from the redis stream.', $id));
+ }
+ }
+
+ public function add(string $body, array $headers, int $delayInMs = 0): void
+ {
+ if ($this->autoSetup) {
+ $this->setup();
+ }
+
+ try {
+ if ($delayInMs > 0) { // the delay could be smaller 0 in a queued message
+ $message = json_encode([
+ 'body' => $body,
+ 'headers' => $headers,
+ // Entry need to be unique in the sorted set else it would only be added once to the delayed messages queue
+ 'uniqid' => uniqid('', true),
+ ]);
+
+ if (false === $message) {
+ throw new TransportException(json_last_error_msg());
+ }
+
+ $score = (int) ($this->getCurrentTimeInMilliseconds() + $delayInMs);
+ $added = $this->connection->zadd($this->queue, ['NX'], $score, $message);
+ } else {
+ $message = json_encode([
+ 'body' => $body,
+ 'headers' => $headers,
+ ]);
+
+ if (false === $message) {
+ throw new TransportException(json_last_error_msg());
+ }
+
+ if ($this->maxEntries) {
+ $added = $this->connection->xadd($this->stream, '*', ['message' => $message], $this->maxEntries, true);
+ } else {
+ $added = $this->connection->xadd($this->stream, '*', ['message' => $message]);
+ }
+ }
+ } catch (\RedisException $e) {
+ if ($error = $this->connection->getLastError() ?: null) {
+ $this->connection->clearLastError();
+ }
+ throw new TransportException($error ?? $e->getMessage(), 0, $e);
+ }
+
+ if (!$added) {
+ if ($error = $this->connection->getLastError() ?: null) {
+ $this->connection->clearLastError();
+ }
+ throw new TransportException($error ?? 'Could not add a message to the redis stream.');
+ }
+ }
+
+ public function setup(): void
+ {
+ try {
+ $this->connection->xgroup('CREATE', $this->stream, $this->group, 0, true);
+ } catch (\RedisException $e) {
+ throw new TransportException($e->getMessage(), 0, $e);
+ }
+
+ // group might already exist, ignore
+ if ($this->connection->getLastError()) {
+ $this->connection->clearLastError();
+ }
+
+ $this->autoSetup = false;
+ }
+
+ private function getCurrentTimeInMilliseconds(): int
+ {
+ return (int) (microtime(true) * 1000);
+ }
+
+ public function cleanup(): void
+ {
+ $this->connection->del($this->stream);
+ $this->connection->del($this->queue);
+ }
+}
+class_alias(Connection::class, \Symfony\Component\Messenger\Transport\RedisExt\Connection::class);
diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisReceivedStamp.php b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisReceivedStamp.php
new file mode 100644
index 0000000000..486aa58dfd
--- /dev/null
+++ b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisReceivedStamp.php
@@ -0,0 +1,33 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Bridge\Redis\Transport;
+
+use Symfony\Component\Messenger\Stamp\NonSendableStampInterface;
+
+/**
+ * @author Alexander Schranz
+ */
+class RedisReceivedStamp implements NonSendableStampInterface
+{
+ private $id;
+
+ public function __construct(string $id)
+ {
+ $this->id = $id;
+ }
+
+ public function getId(): string
+ {
+ return $this->id;
+ }
+}
+class_alias(RedisReceivedStamp::class, \Symfony\Component\Messenger\Transport\RedisExt\RedisReceivedStamp::class);
diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisReceiver.php b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisReceiver.php
new file mode 100644
index 0000000000..0c51d15163
--- /dev/null
+++ b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisReceiver.php
@@ -0,0 +1,89 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Bridge\Redis\Transport;
+
+use Symfony\Component\Messenger\Envelope;
+use Symfony\Component\Messenger\Exception\LogicException;
+use Symfony\Component\Messenger\Exception\MessageDecodingFailedException;
+use Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface;
+use Symfony\Component\Messenger\Transport\Serialization\PhpSerializer;
+use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
+
+/**
+ * @author Alexander Schranz
+ * @author Antoine Bluchet
+ */
+class RedisReceiver implements ReceiverInterface
+{
+ private $connection;
+ private $serializer;
+
+ public function __construct(Connection $connection, SerializerInterface $serializer = null)
+ {
+ $this->connection = $connection;
+ $this->serializer = $serializer ?? new PhpSerializer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get(): iterable
+ {
+ $redisEnvelope = $this->connection->get();
+
+ if (null === $redisEnvelope) {
+ return [];
+ }
+
+ try {
+ $envelope = $this->serializer->decode([
+ 'body' => $redisEnvelope['body'],
+ 'headers' => $redisEnvelope['headers'],
+ ]);
+ } catch (MessageDecodingFailedException $exception) {
+ $this->connection->reject($redisEnvelope['id']);
+
+ throw $exception;
+ }
+
+ return [$envelope->with(new RedisReceivedStamp($redisEnvelope['id']))];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function ack(Envelope $envelope): void
+ {
+ $this->connection->ack($this->findRedisReceivedStamp($envelope)->getId());
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reject(Envelope $envelope): void
+ {
+ $this->connection->reject($this->findRedisReceivedStamp($envelope)->getId());
+ }
+
+ private function findRedisReceivedStamp(Envelope $envelope): RedisReceivedStamp
+ {
+ /** @var RedisReceivedStamp|null $redisReceivedStamp */
+ $redisReceivedStamp = $envelope->last(RedisReceivedStamp::class);
+
+ if (null === $redisReceivedStamp) {
+ throw new LogicException('No RedisReceivedStamp found on the Envelope.');
+ }
+
+ return $redisReceivedStamp;
+ }
+}
+class_alias(RedisReceiver::class, \Symfony\Component\Messenger\Transport\RedisExt\RedisReceiver::class);
diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisSender.php b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisSender.php
new file mode 100644
index 0000000000..38b2e47515
--- /dev/null
+++ b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisSender.php
@@ -0,0 +1,50 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Bridge\Redis\Transport;
+
+use Symfony\Component\Messenger\Envelope;
+use Symfony\Component\Messenger\Stamp\DelayStamp;
+use Symfony\Component\Messenger\Transport\Sender\SenderInterface;
+use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
+
+/**
+ * @author Alexander Schranz
+ * @author Antoine Bluchet
+ */
+class RedisSender implements SenderInterface
+{
+ private $connection;
+ private $serializer;
+
+ public function __construct(Connection $connection, SerializerInterface $serializer)
+ {
+ $this->connection = $connection;
+ $this->serializer = $serializer;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function send(Envelope $envelope): Envelope
+ {
+ $encodedMessage = $this->serializer->encode($envelope);
+
+ /** @var DelayStamp|null $delayStamp */
+ $delayStamp = $envelope->last(DelayStamp::class);
+ $delayInMs = null !== $delayStamp ? $delayStamp->getDelay() : 0;
+
+ $this->connection->add($encodedMessage['body'], $encodedMessage['headers'] ?? [], $delayInMs);
+
+ return $envelope;
+ }
+}
+class_alias(RedisSender::class, \Symfony\Component\Messenger\Transport\RedisExt\RedisSender::class);
diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisTransport.php b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisTransport.php
new file mode 100644
index 0000000000..d92afdec66
--- /dev/null
+++ b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisTransport.php
@@ -0,0 +1,87 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Bridge\Redis\Transport;
+
+use Symfony\Component\Messenger\Envelope;
+use Symfony\Component\Messenger\Transport\Serialization\PhpSerializer;
+use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
+use Symfony\Component\Messenger\Transport\SetupableTransportInterface;
+use Symfony\Component\Messenger\Transport\TransportInterface;
+
+/**
+ * @author Alexander Schranz
+ * @author Antoine Bluchet
+ */
+class RedisTransport implements TransportInterface, SetupableTransportInterface
+{
+ private $serializer;
+ private $connection;
+ private $receiver;
+ private $sender;
+
+ public function __construct(Connection $connection, SerializerInterface $serializer = null)
+ {
+ $this->connection = $connection;
+ $this->serializer = $serializer ?? new PhpSerializer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get(): iterable
+ {
+ return ($this->receiver ?? $this->getReceiver())->get();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function ack(Envelope $envelope): void
+ {
+ ($this->receiver ?? $this->getReceiver())->ack($envelope);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reject(Envelope $envelope): void
+ {
+ ($this->receiver ?? $this->getReceiver())->reject($envelope);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function send(Envelope $envelope): Envelope
+ {
+ return ($this->sender ?? $this->getSender())->send($envelope);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setup(): void
+ {
+ $this->connection->setup();
+ }
+
+ private function getReceiver(): RedisReceiver
+ {
+ return $this->receiver = new RedisReceiver($this->connection, $this->serializer);
+ }
+
+ private function getSender(): RedisSender
+ {
+ return $this->sender = new RedisSender($this->connection, $this->serializer);
+ }
+}
+class_alias(RedisTransport::class, \Symfony\Component\Messenger\Transport\RedisExt\RedisTransport::class);
diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisTransportFactory.php b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisTransportFactory.php
new file mode 100644
index 0000000000..b8a340e84f
--- /dev/null
+++ b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/RedisTransportFactory.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Bridge\Redis\Transport;
+
+use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
+use Symfony\Component\Messenger\Transport\TransportFactoryInterface;
+use Symfony\Component\Messenger\Transport\TransportInterface;
+
+/**
+ * @author Alexander Schranz
+ * @author Antoine Bluchet
+ */
+class RedisTransportFactory implements TransportFactoryInterface
+{
+ public function createTransport(string $dsn, array $options, SerializerInterface $serializer): TransportInterface
+ {
+ unset($options['transport_name']);
+
+ return new RedisTransport(Connection::fromDsn($dsn, $options), $serializer);
+ }
+
+ public function supports(string $dsn, array $options): bool
+ {
+ return 0 === strpos($dsn, 'redis://');
+ }
+}
+class_alias(RedisTransportFactory::class, \Symfony\Component\Messenger\Transport\RedisExt\RedisTransportFactory::class);
diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/composer.json b/src/Symfony/Component/Messenger/Bridge/Redis/composer.json
new file mode 100644
index 0000000000..cb456ec44a
--- /dev/null
+++ b/src/Symfony/Component/Messenger/Bridge/Redis/composer.json
@@ -0,0 +1,38 @@
+{
+ "name": "symfony/redis-messenger",
+ "type": "symfony-bridge",
+ "description": "Symfony Redis extension Messenger Bridge",
+ "keywords": [],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": "^7.2.5",
+ "symfony/messenger": "^5.1"
+ },
+ "require-dev": {
+ "symfony/property-access": "^4.4|^5.0",
+ "symfony/serializer": "^4.4|^5.0"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Component\\Messenger\\Bridge\\Redis\\": "" },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.1-dev"
+ }
+ }
+}
diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/phpunit.xml.dist b/src/Symfony/Component/Messenger/Bridge/Redis/phpunit.xml.dist
new file mode 100644
index 0000000000..4a59a18553
--- /dev/null
+++ b/src/Symfony/Component/Messenger/Bridge/Redis/phpunit.xml.dist
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+ ./Tests/
+
+
+
+
+
+ ./
+
+ ./Tests
+ ./vendor
+
+
+
+
diff --git a/src/Symfony/Component/Messenger/CHANGELOG.md b/src/Symfony/Component/Messenger/CHANGELOG.md
index a62b72a5bc..aab9a173af 100644
--- a/src/Symfony/Component/Messenger/CHANGELOG.md
+++ b/src/Symfony/Component/Messenger/CHANGELOG.md
@@ -1,6 +1,13 @@
CHANGELOG
=========
+5.1.0
+-----
+
+* Moved AmqpExt transport to package `symfony/amqp-messenger`. All classes in `Symfony\Component\Messenger\Transport\AmqpExt` have been moved to `Symfony\Component\Messenger\Bridge\Amqp\Transport`
+* Moved Doctrine transport to package `symfony/doctrine-messenger`. All classes in `Symfony\Component\Messenger\Transport\Doctrine` have been moved to `Symfony\Component\Messenger\Bridge\Doctrine\Transport`
+* Moved RedisExt transport to package `symfony/redis-messenger`. All classes in `Symfony\Component\Messenger\Transport\RedisExt` have been moved to `Symfony\Component\Messenger\Bridge\Redis\Transport`
+
5.0.0
-----
diff --git a/src/Symfony/Component/Messenger/Middleware/RejectRedeliveredMessageMiddleware.php b/src/Symfony/Component/Messenger/Middleware/RejectRedeliveredMessageMiddleware.php
index d036790ddb..33e5d427fa 100644
--- a/src/Symfony/Component/Messenger/Middleware/RejectRedeliveredMessageMiddleware.php
+++ b/src/Symfony/Component/Messenger/Middleware/RejectRedeliveredMessageMiddleware.php
@@ -11,9 +11,11 @@
namespace Symfony\Component\Messenger\Middleware;
+
+use Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpReceivedStamp;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Exception\RejectRedeliveredMessageException;
-use Symfony\Component\Messenger\Transport\AmqpExt\AmqpReceivedStamp;
+use Symfony\Component\Messenger\Transport\AmqpExt\AmqpReceivedStamp as LegacyAmqpReceivedStamp;
/**
* Middleware that throws a RejectRedeliveredMessageException when a message is detected that has been redelivered by AMQP.
@@ -34,11 +36,16 @@ class RejectRedeliveredMessageMiddleware implements MiddlewareInterface
public function handle(Envelope $envelope, StackInterface $stack): Envelope
{
$amqpReceivedStamp = $envelope->last(AmqpReceivedStamp::class);
-
if ($amqpReceivedStamp instanceof AmqpReceivedStamp && $amqpReceivedStamp->getAmqpEnvelope()->isRedelivery()) {
throw new RejectRedeliveredMessageException('Redelivered message from AMQP detected that will be rejected and trigger the retry logic.');
}
+ // Legacy code to support symfony/messenger < 5.1
+ $amqpReceivedStamp = $envelope->last(LegacyAmqpReceivedStamp::class);
+ if ($amqpReceivedStamp instanceof LegacyAmqpReceivedStamp && $amqpReceivedStamp->getAmqpEnvelope()->isRedelivery()) {
+ throw new RejectRedeliveredMessageException('Redelivered message from AMQP detected that will be rejected and trigger the retry logic.');
+ }
+
return $stack->next()->handle($envelope, $stack);
}
}
diff --git a/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php b/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php
index e87cbf18b5..c127163994 100644
--- a/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php
+++ b/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php
@@ -38,7 +38,7 @@ use Symfony\Component\Messenger\Tests\Fixtures\DummyQueryHandler;
use Symfony\Component\Messenger\Tests\Fixtures\MultipleBusesMessage;
use Symfony\Component\Messenger\Tests\Fixtures\MultipleBusesMessageHandler;
use Symfony\Component\Messenger\Tests\Fixtures\SecondMessage;
-use Symfony\Component\Messenger\Transport\AmqpExt\AmqpReceiver;
+use Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpReceiver;
use Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface;
class MessengerPassTest extends TestCase
diff --git a/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpFactory.php b/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpFactory.php
index 5cbdbdd086..e201d2e80f 100644
--- a/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpFactory.php
+++ b/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpFactory.php
@@ -11,25 +11,17 @@
namespace Symfony\Component\Messenger\Transport\AmqpExt;
-class AmqpFactory
-{
- public function createConnection(array $credentials): \AMQPConnection
- {
- return new \AMQPConnection($credentials);
- }
+use Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpFactory as BridgeAmqpFactory;
- public function createChannel(\AMQPConnection $connection): \AMQPChannel
- {
- return new \AMQPChannel($connection);
- }
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 5.1, use "%s" instead. The AmqpExt transport has been moved to package "symfony/amqp-messenger" and will not be included by default in 6.0. Run "composer require symfony/amqp-messenger".', AmqpFactory::class, BridgeAmqpFactory::class), E_USER_DEPRECATED);
- public function createQueue(\AMQPChannel $channel): \AMQPQueue
- {
- return new \AMQPQueue($channel);
- }
+class_exists(BridgeAmqpFactory::class);
- public function createExchange(\AMQPChannel $channel): \AMQPExchange
+if (false) {
+ /**
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/amqp-messenger instead.
+ */
+ class AmqpFactory
{
- return new \AMQPExchange($channel);
}
}
diff --git a/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpReceivedStamp.php b/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpReceivedStamp.php
index e02ecbf3e8..cea910a2c6 100644
--- a/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpReceivedStamp.php
+++ b/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpReceivedStamp.php
@@ -11,29 +11,17 @@
namespace Symfony\Component\Messenger\Transport\AmqpExt;
-use Symfony\Component\Messenger\Stamp\NonSendableStampInterface;
+use Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpReceivedStamp as BridgeAmqpReceivedStamp;
-/**
- * Stamp applied when a message is received from Amqp.
- */
-class AmqpReceivedStamp implements NonSendableStampInterface
-{
- private $amqpEnvelope;
- private $queueName;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 5.1, use "%s" instead. The AmqpExt transport has been moved to package "symfony/amqp-messenger" and will not be included by default in 6.0. Run "composer require symfony/amqp-messenger".', AmqpReceivedStamp::class, BridgeAmqpReceivedStamp::class), E_USER_DEPRECATED);
- public function __construct(\AMQPEnvelope $amqpEnvelope, string $queueName)
+class_exists(BridgeAmqpReceivedStamp::class);
+
+if (false) {
+ /**
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/amqp-messenger instead.
+ */
+ class AmqpReceivedStamp
{
- $this->amqpEnvelope = $amqpEnvelope;
- $this->queueName = $queueName;
- }
-
- public function getAmqpEnvelope(): \AMQPEnvelope
- {
- return $this->amqpEnvelope;
- }
-
- public function getQueueName(): string
- {
- return $this->queueName;
}
}
diff --git a/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpReceiver.php b/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpReceiver.php
index 068b0cba83..bd11af6cba 100644
--- a/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpReceiver.php
+++ b/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpReceiver.php
@@ -11,128 +11,17 @@
namespace Symfony\Component\Messenger\Transport\AmqpExt;
-use Symfony\Component\Messenger\Envelope;
-use Symfony\Component\Messenger\Exception\LogicException;
-use Symfony\Component\Messenger\Exception\MessageDecodingFailedException;
-use Symfony\Component\Messenger\Exception\TransportException;
-use Symfony\Component\Messenger\Transport\Receiver\MessageCountAwareInterface;
-use Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface;
-use Symfony\Component\Messenger\Transport\Serialization\PhpSerializer;
-use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
+use Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpReceiver as BridgeAmqpReceiver;
-/**
- * Symfony Messenger receiver to get messages from AMQP brokers using PHP's AMQP extension.
- *
- * @author Samuel Roze
- */
-class AmqpReceiver implements ReceiverInterface, MessageCountAwareInterface
-{
- private $serializer;
- private $connection;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 5.1, use "%s" instead. The AmqpExt transport has been moved to package "symfony/amqp-messenger" and will not be included by default in 6.0. Run "composer require symfony/amqp-messenger".', AmqpReceiver::class, BridgeAmqpReceiver::class), E_USER_DEPRECATED);
- public function __construct(Connection $connection, SerializerInterface $serializer = null)
- {
- $this->connection = $connection;
- $this->serializer = $serializer ?? new PhpSerializer();
- }
+class_exists(BridgeAmqpReceiver::class);
+if (false) {
/**
- * {@inheritdoc}
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/amqp-messenger instead.
*/
- public function get(): iterable
+ class AmqpReceiver
{
- foreach ($this->connection->getQueueNames() as $queueName) {
- yield from $this->getEnvelope($queueName);
- }
- }
-
- private function getEnvelope(string $queueName): iterable
- {
- try {
- $amqpEnvelope = $this->connection->get($queueName);
- } catch (\AMQPException $exception) {
- throw new TransportException($exception->getMessage(), 0, $exception);
- }
-
- if (null === $amqpEnvelope) {
- return;
- }
-
- $body = $amqpEnvelope->getBody();
-
- try {
- $envelope = $this->serializer->decode([
- 'body' => false === $body ? '' : $body, // workaround https://github.com/pdezwart/php-amqp/issues/351
- 'headers' => $amqpEnvelope->getHeaders(),
- ]);
- } catch (MessageDecodingFailedException $exception) {
- // invalid message of some type
- $this->rejectAmqpEnvelope($amqpEnvelope, $queueName);
-
- throw $exception;
- }
-
- yield $envelope->with(new AmqpReceivedStamp($amqpEnvelope, $queueName));
- }
-
- /**
- * {@inheritdoc}
- */
- public function ack(Envelope $envelope): void
- {
- try {
- $stamp = $this->findAmqpStamp($envelope);
-
- $this->connection->ack(
- $stamp->getAmqpEnvelope(),
- $stamp->getQueueName()
- );
- } catch (\AMQPException $exception) {
- throw new TransportException($exception->getMessage(), 0, $exception);
- }
- }
-
- /**
- * {@inheritdoc}
- */
- public function reject(Envelope $envelope): void
- {
- $stamp = $this->findAmqpStamp($envelope);
-
- $this->rejectAmqpEnvelope(
- $stamp->getAmqpEnvelope(),
- $stamp->getQueueName()
- );
- }
-
- /**
- * {@inheritdoc}
- */
- public function getMessageCount(): int
- {
- try {
- return $this->connection->countMessagesInQueues();
- } catch (\AMQPException $exception) {
- throw new TransportException($exception->getMessage(), 0, $exception);
- }
- }
-
- private function rejectAmqpEnvelope(\AMQPEnvelope $amqpEnvelope, string $queueName): void
- {
- try {
- $this->connection->nack($amqpEnvelope, $queueName, AMQP_NOPARAM);
- } catch (\AMQPException $exception) {
- throw new TransportException($exception->getMessage(), 0, $exception);
- }
- }
-
- private function findAmqpStamp(Envelope $envelope): AmqpReceivedStamp
- {
- $amqpReceivedStamp = $envelope->last(AmqpReceivedStamp::class);
- if (null === $amqpReceivedStamp) {
- throw new LogicException('No "AmqpReceivedStamp" stamp found on the Envelope.');
- }
-
- return $amqpReceivedStamp;
}
}
diff --git a/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpSender.php b/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpSender.php
index ae99759c49..c247a67f91 100644
--- a/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpSender.php
+++ b/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpSender.php
@@ -11,67 +11,18 @@
namespace Symfony\Component\Messenger\Transport\AmqpExt;
-use Symfony\Component\Messenger\Envelope;
-use Symfony\Component\Messenger\Exception\TransportException;
-use Symfony\Component\Messenger\Stamp\DelayStamp;
-use Symfony\Component\Messenger\Transport\Sender\SenderInterface;
-use Symfony\Component\Messenger\Transport\Serialization\PhpSerializer;
-use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
+use Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpSender as BridgeAmqpSender;
-/**
- * Symfony Messenger sender to send messages to AMQP brokers using PHP's AMQP extension.
- *
- * @author Samuel Roze
- */
-class AmqpSender implements SenderInterface
-{
- private $serializer;
- private $connection;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 5.1, use "%s" instead. The AmqpExt transport has been moved to package "symfony/amqp-messenger" and will not be included by default in 6.0. Run "composer require symfony/amqp-messenger".', AmqpSender::class, BridgeAmqpSender::class), E_USER_DEPRECATED);
- public function __construct(Connection $connection, SerializerInterface $serializer = null)
- {
- $this->connection = $connection;
- $this->serializer = $serializer ?? new PhpSerializer();
- }
+class_exists(BridgeAmqpSender::class);
+if (false) {
/**
- * {@inheritdoc}
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/amqp-messenger instead.
*/
- public function send(Envelope $envelope): Envelope
+ class AmqpSender
{
- $encodedMessage = $this->serializer->encode($envelope);
-
- /** @var DelayStamp|null $delayStamp */
- $delayStamp = $envelope->last(DelayStamp::class);
- $delay = $delayStamp ? $delayStamp->getDelay() : 0;
-
- /** @var AmqpStamp|null $amqpStamp */
- $amqpStamp = $envelope->last(AmqpStamp::class);
- if (isset($encodedMessage['headers']['Content-Type'])) {
- $contentType = $encodedMessage['headers']['Content-Type'];
- unset($encodedMessage['headers']['Content-Type']);
-
- if (!$amqpStamp || !isset($amqpStamp->getAttributes()['content_type'])) {
- $amqpStamp = AmqpStamp::createWithAttributes(['content_type' => $contentType], $amqpStamp);
- }
- }
-
- $amqpReceivedStamp = $envelope->last(AmqpReceivedStamp::class);
- if ($amqpReceivedStamp instanceof AmqpReceivedStamp) {
- $amqpStamp = AmqpStamp::createFromAmqpEnvelope($amqpReceivedStamp->getAmqpEnvelope(), $amqpStamp);
- }
-
- try {
- $this->connection->publish(
- $encodedMessage['body'],
- $encodedMessage['headers'] ?? [],
- $delay,
- $amqpStamp
- );
- } catch (\AMQPException $e) {
- throw new TransportException($e->getMessage(), 0, $e);
- }
-
- return $envelope;
}
}
+
diff --git a/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpStamp.php b/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpStamp.php
index 0a4777ccff..242b6f6482 100644
--- a/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpStamp.php
+++ b/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpStamp.php
@@ -11,66 +11,17 @@
namespace Symfony\Component\Messenger\Transport\AmqpExt;
-use Symfony\Component\Messenger\Stamp\NonSendableStampInterface;
+use Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpStamp as BridgeAmqpStamp;
-/**
- * @author Guillaume Gammelin
- * @author Samuel Roze
- */
-final class AmqpStamp implements NonSendableStampInterface
-{
- private $routingKey;
- private $flags;
- private $attributes;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 5.1, use "%s" instead. The AmqpExt transport has been moved to package "symfony/amqp-messenger" and will not be included by default in 6.0. Run "composer require symfony/amqp-messenger".', AmqpStamp::class, BridgeAmqpStamp::class), E_USER_DEPRECATED);
- public function __construct(string $routingKey = null, int $flags = AMQP_NOPARAM, array $attributes = [])
+class_exists(BridgeAmqpStamp::class);
+
+if (false) {
+ /**
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/amqp-messenger instead.
+ */
+ class AmqpStamp
{
- $this->routingKey = $routingKey;
- $this->flags = $flags;
- $this->attributes = $attributes;
- }
-
- public function getRoutingKey(): ?string
- {
- return $this->routingKey;
- }
-
- public function getFlags(): int
- {
- return $this->flags;
- }
-
- public function getAttributes(): array
- {
- return $this->attributes;
- }
-
- public static function createFromAmqpEnvelope(\AMQPEnvelope $amqpEnvelope, self $previousStamp = null): self
- {
- $attr = $previousStamp->attributes ?? [];
-
- $attr['headers'] = $attr['headers'] ?? $amqpEnvelope->getHeaders();
- $attr['content_type'] = $attr['content_type'] ?? $amqpEnvelope->getContentType();
- $attr['content_encoding'] = $attr['content_encoding'] ?? $amqpEnvelope->getContentEncoding();
- $attr['delivery_mode'] = $attr['delivery_mode'] ?? $amqpEnvelope->getDeliveryMode();
- $attr['priority'] = $attr['priority'] ?? $amqpEnvelope->getPriority();
- $attr['timestamp'] = $attr['timestamp'] ?? $amqpEnvelope->getTimestamp();
- $attr['app_id'] = $attr['app_id'] ?? $amqpEnvelope->getAppId();
- $attr['message_id'] = $attr['message_id'] ?? $amqpEnvelope->getMessageId();
- $attr['user_id'] = $attr['user_id'] ?? $amqpEnvelope->getUserId();
- $attr['expiration'] = $attr['expiration'] ?? $amqpEnvelope->getExpiration();
- $attr['type'] = $attr['type'] ?? $amqpEnvelope->getType();
- $attr['reply_to'] = $attr['reply_to'] ?? $amqpEnvelope->getReplyTo();
-
- return new self($previousStamp->routingKey ?? $amqpEnvelope->getRoutingKey(), $previousStamp->flags ?? AMQP_NOPARAM, $attr);
- }
-
- public static function createWithAttributes(array $attributes, self $previousStamp = null): self
- {
- return new self(
- $previousStamp->routingKey ?? null,
- $previousStamp->flags ?? AMQP_NOPARAM,
- array_merge($previousStamp->attributes ?? [], $attributes)
- );
}
}
diff --git a/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpTransport.php b/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpTransport.php
index bf536de8a1..653aac1c0e 100644
--- a/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpTransport.php
+++ b/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpTransport.php
@@ -11,84 +11,17 @@
namespace Symfony\Component\Messenger\Transport\AmqpExt;
-use Symfony\Component\Messenger\Envelope;
-use Symfony\Component\Messenger\Transport\Receiver\MessageCountAwareInterface;
-use Symfony\Component\Messenger\Transport\Serialization\PhpSerializer;
-use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
-use Symfony\Component\Messenger\Transport\SetupableTransportInterface;
-use Symfony\Component\Messenger\Transport\TransportInterface;
+use Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpTransport as BridgeAmqpTransport;
-/**
- * @author Nicolas Grekas
- */
-class AmqpTransport implements TransportInterface, SetupableTransportInterface, MessageCountAwareInterface
-{
- private $serializer;
- private $connection;
- private $receiver;
- private $sender;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 5.1, use "%s" instead. The AmqpExt transport has been moved to package "symfony/amqp-messenger" and will not be included by default in 6.0. Run "composer require symfony/amqp-messenger".', AmqpTransport::class, BridgeAmqpTransport::class), E_USER_DEPRECATED);
- public function __construct(Connection $connection, SerializerInterface $serializer = null)
- {
- $this->connection = $connection;
- $this->serializer = $serializer ?? new PhpSerializer();
- }
+class_exists(BridgeAmqpTransport::class);
+if (false) {
/**
- * {@inheritdoc}
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/amqp-messenger instead.
*/
- public function get(): iterable
+ class AmqpTransport
{
- return ($this->receiver ?? $this->getReceiver())->get();
- }
-
- /**
- * {@inheritdoc}
- */
- public function ack(Envelope $envelope): void
- {
- ($this->receiver ?? $this->getReceiver())->ack($envelope);
- }
-
- /**
- * {@inheritdoc}
- */
- public function reject(Envelope $envelope): void
- {
- ($this->receiver ?? $this->getReceiver())->reject($envelope);
- }
-
- /**
- * {@inheritdoc}
- */
- public function send(Envelope $envelope): Envelope
- {
- return ($this->sender ?? $this->getSender())->send($envelope);
- }
-
- /**
- * {@inheritdoc}
- */
- public function setup(): void
- {
- $this->connection->setup();
- }
-
- /**
- * {@inheritdoc}
- */
- public function getMessageCount(): int
- {
- return ($this->receiver ?? $this->getReceiver())->getMessageCount();
- }
-
- private function getReceiver(): AmqpReceiver
- {
- return $this->receiver = new AmqpReceiver($this->connection, $this->serializer);
- }
-
- private function getSender(): AmqpSender
- {
- return $this->sender = new AmqpSender($this->connection, $this->serializer);
}
}
diff --git a/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpTransportFactory.php b/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpTransportFactory.php
index 0a366d9a84..3d3dacc54e 100644
--- a/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpTransportFactory.php
+++ b/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpTransportFactory.php
@@ -11,24 +11,17 @@
namespace Symfony\Component\Messenger\Transport\AmqpExt;
-use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
-use Symfony\Component\Messenger\Transport\TransportFactoryInterface;
-use Symfony\Component\Messenger\Transport\TransportInterface;
+use Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpTransportFactory as BridgeAmqpTransportFactory;
-/**
- * @author Samuel Roze
- */
-class AmqpTransportFactory implements TransportFactoryInterface
-{
- public function createTransport(string $dsn, array $options, SerializerInterface $serializer): TransportInterface
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 5.1, use "%s" instead. The AmqpExt transport has been moved to package "symfony/amqp-messenger" and will not be included by default in 6.0. Run "composer require symfony/amqp-messenger".', AmqpTransportFactory::class, BridgeAmqpTransportFactory::class), E_USER_DEPRECATED);
+
+class_exists(BridgeAmqpTransportFactory::class);
+
+if (false) {
+ /**
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/amqp-messenger instead.
+ */
+ class AmqpTransportFactory
{
- unset($options['transport_name']);
-
- return new AmqpTransport(Connection::fromDsn($dsn, $options), $serializer);
- }
-
- public function supports(string $dsn, array $options): bool
- {
- return 0 === strpos($dsn, 'amqp://');
}
}
diff --git a/src/Symfony/Component/Messenger/Transport/AmqpExt/Connection.php b/src/Symfony/Component/Messenger/Transport/AmqpExt/Connection.php
index 2540b9d770..acb5f25168 100644
--- a/src/Symfony/Component/Messenger/Transport/AmqpExt/Connection.php
+++ b/src/Symfony/Component/Messenger/Transport/AmqpExt/Connection.php
@@ -11,463 +11,17 @@
namespace Symfony\Component\Messenger\Transport\AmqpExt;
-use Symfony\Component\Messenger\Exception\InvalidArgumentException;
-use Symfony\Component\Messenger\Exception\LogicException;
+use Symfony\Component\Messenger\Bridge\Amqp\Transport\Connection as BridgeConnection;
-/**
- * An AMQP connection.
- *
- * @author Samuel Roze
- *
- * @final
- */
-class Connection
-{
- private const ARGUMENTS_AS_INTEGER = [
- 'x-delay',
- 'x-expires',
- 'x-max-length',
- 'x-max-length-bytes',
- 'x-max-priority',
- 'x-message-ttl',
- ];
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 5.1, use "%s" instead. The AmqpExt transport has been moved to package "symfony/amqp-messenger" and will not be included by default in 6.0. Run "composer require symfony/amqp-messenger".', Connection::class, BridgeConnection::class), E_USER_DEPRECATED);
- private $connectionOptions;
- private $exchangeOptions;
- private $queuesOptions;
- private $amqpFactory;
+class_exists(BridgeConnection::class);
+if (false) {
/**
- * @var \AMQPChannel|null
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/amqp-messenger instead.
*/
- private $amqpChannel;
-
- /**
- * @var \AMQPExchange|null
- */
- private $amqpExchange;
-
- /**
- * @var \AMQPQueue[]|null
- */
- private $amqpQueues = [];
-
- /**
- * @var \AMQPExchange|null
- */
- private $amqpDelayExchange;
-
- public function __construct(array $connectionOptions, array $exchangeOptions, array $queuesOptions, AmqpFactory $amqpFactory = null)
+ class Connection
{
- if (!\extension_loaded('amqp')) {
- throw new LogicException(sprintf('You cannot use the "%s" as the "amqp" extension is not installed.', __CLASS__));
- }
-
- $this->connectionOptions = array_replace_recursive([
- 'delay' => [
- 'exchange_name' => 'delays',
- 'queue_name_pattern' => 'delay_%exchange_name%_%routing_key%_%delay%',
- ],
- ], $connectionOptions);
- $this->exchangeOptions = $exchangeOptions;
- $this->queuesOptions = $queuesOptions;
- $this->amqpFactory = $amqpFactory ?: new AmqpFactory();
- }
-
- /**
- * Creates a connection based on the DSN and options.
- *
- * 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: Password to use the connect to the AMQP service
- * * queues[name]: An array of queues, keyed by the name
- * * binding_keys: The binding keys (if any) to bind to this queue
- * * binding_arguments: Arguments to be used while binding the queue.
- * * flags: Queue flags (Default: AMQP_DURABLE)
- * * arguments: Extra arguments
- * * exchange:
- * * name: Name of the exchange
- * * type: Type of exchange (Default: fanout)
- * * default_publish_routing_key: Routing key to use when publishing, if none is specified on the message
- * * flags: Exchange flags (Default: AMQP_DURABLE)
- * * arguments: Extra arguments
- * * delay:
- * * queue_name_pattern: Pattern to use to create the queues (Default: "delay_%exchange_name%_%routing_key%_%delay%")
- * * exchange_name: Name of the exchange to be used for the delayed/retried messages (Default: "delays")
- * * auto_setup: Enable or not the auto-setup of queues and exchanges (Default: true)
- * * prefetch_count: set channel prefetch count
- */
- public static function fromDsn(string $dsn, array $options = [], AmqpFactory $amqpFactory = null): self
- {
- if (false === $parsedUrl = parse_url($dsn)) {
- // this is a valid URI that parse_url cannot handle when you want to pass all parameters as options
- if ('amqp://' !== $dsn) {
- throw new InvalidArgumentException(sprintf('The given AMQP DSN "%s" is invalid.', $dsn));
- }
-
- $parsedUrl = [];
- }
-
- $pathParts = isset($parsedUrl['path']) ? explode('/', trim($parsedUrl['path'], '/')) : [];
- $exchangeName = $pathParts[1] ?? 'messages';
- parse_str($parsedUrl['query'] ?? '', $parsedQuery);
-
- $amqpOptions = array_replace_recursive([
- 'host' => $parsedUrl['host'] ?? 'localhost',
- 'port' => $parsedUrl['port'] ?? 5672,
- 'vhost' => isset($pathParts[0]) ? urldecode($pathParts[0]) : '/',
- 'exchange' => [
- 'name' => $exchangeName,
- ],
- ], $options, $parsedQuery);
-
- if (isset($parsedUrl['user'])) {
- $amqpOptions['login'] = $parsedUrl['user'];
- }
-
- if (isset($parsedUrl['pass'])) {
- $amqpOptions['password'] = $parsedUrl['pass'];
- }
-
- if (!isset($amqpOptions['queues'])) {
- $amqpOptions['queues'][$exchangeName] = [];
- }
-
- $exchangeOptions = $amqpOptions['exchange'];
- $queuesOptions = $amqpOptions['queues'];
- unset($amqpOptions['queues'], $amqpOptions['exchange']);
-
- $queuesOptions = array_map(function ($queueOptions) {
- if (!\is_array($queueOptions)) {
- $queueOptions = [];
- }
- if (\is_array($queueOptions['arguments'] ?? false)) {
- $queueOptions['arguments'] = self::normalizeQueueArguments($queueOptions['arguments']);
- }
-
- return $queueOptions;
- }, $queuesOptions);
-
- return new self($amqpOptions, $exchangeOptions, $queuesOptions, $amqpFactory);
- }
-
- private static function normalizeQueueArguments(array $arguments): array
- {
- foreach (self::ARGUMENTS_AS_INTEGER as $key) {
- if (!\array_key_exists($key, $arguments)) {
- continue;
- }
-
- if (!is_numeric($arguments[$key])) {
- throw new InvalidArgumentException(sprintf('Integer expected for queue argument "%s", %s given.', $key, \gettype($arguments[$key])));
- }
-
- $arguments[$key] = (int) $arguments[$key];
- }
-
- return $arguments;
- }
-
- /**
- * @throws \AMQPException
- */
- public function publish(string $body, array $headers = [], int $delayInMs = 0, AmqpStamp $amqpStamp = null): void
- {
- $this->clearWhenDisconnected();
-
- if (0 !== $delayInMs) {
- $this->publishWithDelay($body, $headers, $delayInMs, $amqpStamp);
-
- return;
- }
-
- if ($this->shouldSetup()) {
- $this->setupExchangeAndQueues();
- }
-
- $this->publishOnExchange(
- $this->exchange(),
- $body,
- $this->getRoutingKeyForMessage($amqpStamp),
- $headers,
- $amqpStamp
- );
- }
-
- /**
- * Returns an approximate count of the messages in defined queues.
- */
- public function countMessagesInQueues(): int
- {
- return array_sum(array_map(function ($queueName) {
- return $this->queue($queueName)->declareQueue();
- }, $this->getQueueNames()));
- }
-
- /**
- * @throws \AMQPException
- */
- private function publishWithDelay(string $body, array $headers, int $delay, AmqpStamp $amqpStamp = null)
- {
- $routingKey = $this->getRoutingKeyForMessage($amqpStamp);
-
- $this->setupDelay($delay, $routingKey);
-
- $this->publishOnExchange(
- $this->getDelayExchange(),
- $body,
- $this->getRoutingKeyForDelay($delay, $routingKey),
- $headers,
- $amqpStamp
- );
- }
-
- private function publishOnExchange(\AMQPExchange $exchange, string $body, string $routingKey = null, array $headers = [], AmqpStamp $amqpStamp = null)
- {
- $attributes = $amqpStamp ? $amqpStamp->getAttributes() : [];
- $attributes['headers'] = array_merge($attributes['headers'] ?? [], $headers);
- $attributes['delivery_mode'] = $attributes['delivery_mode'] ?? 2;
-
- $exchange->publish(
- $body,
- $routingKey,
- $amqpStamp ? $amqpStamp->getFlags() : AMQP_NOPARAM,
- $attributes
- );
- }
-
- private function setupDelay(int $delay, ?string $routingKey)
- {
- if ($this->shouldSetup()) {
- $this->setup(); // setup delay exchange and normal exchange for delay queue to DLX messages to
- }
-
- $queue = $this->createDelayQueue($delay, $routingKey);
- $queue->declareQueue(); // the delay queue always need to be declared because the name is dynamic and cannot be declared in advance
- $queue->bind($this->connectionOptions['delay']['exchange_name'], $this->getRoutingKeyForDelay($delay, $routingKey));
- }
-
- private function getDelayExchange(): \AMQPExchange
- {
- if (null === $this->amqpDelayExchange) {
- $this->amqpDelayExchange = $this->amqpFactory->createExchange($this->channel());
- $this->amqpDelayExchange->setName($this->connectionOptions['delay']['exchange_name']);
- $this->amqpDelayExchange->setType(AMQP_EX_TYPE_DIRECT);
- $this->amqpDelayExchange->setFlags(AMQP_DURABLE);
- }
-
- return $this->amqpDelayExchange;
- }
-
- /**
- * Creates a delay queue that will delay for a certain amount of time.
- *
- * This works by setting message TTL for the delay and pointing
- * the dead letter exchange to the original exchange. The result
- * is that after the TTL, the message is sent to the dead-letter-exchange,
- * which is the original exchange, resulting on it being put back into
- * the original queue.
- */
- private function createDelayQueue(int $delay, ?string $routingKey): \AMQPQueue
- {
- $queue = $this->amqpFactory->createQueue($this->channel());
- $queue->setName(str_replace(
- ['%delay%', '%exchange_name%', '%routing_key%'],
- [$delay, $this->exchangeOptions['name'], $routingKey ?? ''],
- $this->connectionOptions['delay']['queue_name_pattern']
- ));
- $queue->setFlags(AMQP_DURABLE);
- $queue->setArguments([
- 'x-message-ttl' => $delay,
- // delete the delay queue 10 seconds after the message expires
- // publishing another message redeclares the queue which renews the lease
- 'x-expires' => $delay + 10000,
- 'x-dead-letter-exchange' => $this->exchangeOptions['name'],
- // after being released from to DLX, make sure the original routing key will be used
- // we must use an empty string instead of null for the argument to be picked up
- 'x-dead-letter-routing-key' => $routingKey ?? '',
- ]);
-
- return $queue;
- }
-
- private function getRoutingKeyForDelay(int $delay, ?string $finalRoutingKey): string
- {
- return str_replace(
- ['%delay%', '%exchange_name%', '%routing_key%'],
- [$delay, $this->exchangeOptions['name'], $finalRoutingKey ?? ''],
- $this->connectionOptions['delay']['queue_name_pattern']
- );
- }
-
- /**
- * Gets a message from the specified queue.
- *
- * @throws \AMQPException
- */
- public function get(string $queueName): ?\AMQPEnvelope
- {
- $this->clearWhenDisconnected();
-
- if ($this->shouldSetup()) {
- $this->setupExchangeAndQueues();
- }
-
- try {
- if (false !== $message = $this->queue($queueName)->get()) {
- return $message;
- }
- } catch (\AMQPQueueException $e) {
- if (404 === $e->getCode() && $this->shouldSetup()) {
- // If we get a 404 for the queue, it means we need to set up the exchange & queue.
- $this->setupExchangeAndQueues();
-
- return $this->get();
- }
-
- throw $e;
- }
-
- return null;
- }
-
- public function ack(\AMQPEnvelope $message, string $queueName): bool
- {
- return $this->queue($queueName)->ack($message->getDeliveryTag());
- }
-
- public function nack(\AMQPEnvelope $message, string $queueName, int $flags = AMQP_NOPARAM): bool
- {
- return $this->queue($queueName)->nack($message->getDeliveryTag(), $flags);
- }
-
- public function setup(): void
- {
- $this->setupExchangeAndQueues();
- $this->getDelayExchange()->declareExchange();
- }
-
- private function setupExchangeAndQueues(): void
- {
- $this->exchange()->declareExchange();
-
- foreach ($this->queuesOptions as $queueName => $queueConfig) {
- $this->queue($queueName)->declareQueue();
- foreach ($queueConfig['binding_keys'] ?? [null] as $bindingKey) {
- $this->queue($queueName)->bind($this->exchangeOptions['name'], $bindingKey, $queueConfig['binding_arguments'] ?? []);
- }
- }
- }
-
- /**
- * @return string[]
- */
- public function getQueueNames(): array
- {
- return array_keys($this->queuesOptions);
- }
-
- public function channel(): \AMQPChannel
- {
- if (null === $this->amqpChannel) {
- $connection = $this->amqpFactory->createConnection($this->connectionOptions);
- $connectMethod = 'true' === ($this->connectionOptions['persistent'] ?? 'false') ? 'pconnect' : 'connect';
-
- try {
- $connection->{$connectMethod}();
- } catch (\AMQPConnectionException $e) {
- $credentials = $this->connectionOptions;
- $credentials['password'] = '********';
- unset($credentials['delay']);
-
- throw new \AMQPException(sprintf('Could not connect to the AMQP server. Please verify the provided DSN. (%s)', json_encode($credentials)), 0, $e);
- }
- $this->amqpChannel = $this->amqpFactory->createChannel($connection);
-
- if (isset($this->connectionOptions['prefetch_count'])) {
- $this->amqpChannel->setPrefetchCount($this->connectionOptions['prefetch_count']);
- }
- }
-
- return $this->amqpChannel;
- }
-
- public function queue(string $queueName): \AMQPQueue
- {
- if (!isset($this->amqpQueues[$queueName])) {
- $queueConfig = $this->queuesOptions[$queueName];
-
- $amqpQueue = $this->amqpFactory->createQueue($this->channel());
- $amqpQueue->setName($queueName);
- $amqpQueue->setFlags($queueConfig['flags'] ?? AMQP_DURABLE);
-
- if (isset($queueConfig['arguments'])) {
- $amqpQueue->setArguments($queueConfig['arguments']);
- }
-
- $this->amqpQueues[$queueName] = $amqpQueue;
- }
-
- return $this->amqpQueues[$queueName];
- }
-
- public function exchange(): \AMQPExchange
- {
- if (null === $this->amqpExchange) {
- $this->amqpExchange = $this->amqpFactory->createExchange($this->channel());
- $this->amqpExchange->setName($this->exchangeOptions['name']);
- $this->amqpExchange->setType($this->exchangeOptions['type'] ?? AMQP_EX_TYPE_FANOUT);
- $this->amqpExchange->setFlags($this->exchangeOptions['flags'] ?? AMQP_DURABLE);
-
- if (isset($this->exchangeOptions['arguments'])) {
- $this->amqpExchange->setArguments($this->exchangeOptions['arguments']);
- }
- }
-
- return $this->amqpExchange;
- }
-
- private function clearWhenDisconnected(): void
- {
- if (!$this->channel()->isConnected()) {
- $this->amqpChannel = null;
- $this->amqpQueues = [];
- $this->amqpExchange = null;
- $this->amqpDelayExchange = null;
- }
- }
-
- private function shouldSetup(): bool
- {
- if (!\array_key_exists('auto_setup', $this->connectionOptions)) {
- return true;
- }
-
- if (\in_array($this->connectionOptions['auto_setup'], [false, 'false'], true)) {
- return false;
- }
-
- return true;
- }
-
- private function getDefaultPublishRoutingKey(): ?string
- {
- return $this->exchangeOptions['default_publish_routing_key'] ?? null;
- }
-
- public function purgeQueues()
- {
- foreach ($this->getQueueNames() as $queueName) {
- $this->queue($queueName)->purge();
- }
- }
-
- private function getRoutingKeyForMessage(?AmqpStamp $amqpStamp): ?string
- {
- return (null !== $amqpStamp ? $amqpStamp->getRoutingKey() : null) ?? $this->getDefaultPublishRoutingKey();
}
}
diff --git a/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php b/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php
index d5d4d74031..30c33bac55 100644
--- a/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php
+++ b/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php
@@ -11,336 +11,17 @@
namespace Symfony\Component\Messenger\Transport\Doctrine;
-use Doctrine\DBAL\Connection as DBALConnection;
-use Doctrine\DBAL\DBALException;
-use Doctrine\DBAL\Driver\ResultStatement;
-use Doctrine\DBAL\Exception\TableNotFoundException;
-use Doctrine\DBAL\Query\QueryBuilder;
-use Doctrine\DBAL\Schema\Schema;
-use Doctrine\DBAL\Schema\Synchronizer\SchemaSynchronizer;
-use Doctrine\DBAL\Schema\Synchronizer\SingleDatabaseSynchronizer;
-use Doctrine\DBAL\Types\Type;
-use Symfony\Component\Messenger\Exception\InvalidArgumentException;
-use Symfony\Component\Messenger\Exception\TransportException;
+use Symfony\Component\Messenger\Bridge\Doctrine\Transport\Connection as BridgeConnection;
-/**
- * @author Vincent Touzet
- *
- * @final
- */
-class Connection
-{
- private const DEFAULT_OPTIONS = [
- 'table_name' => 'messenger_messages',
- 'queue_name' => 'default',
- 'redeliver_timeout' => 3600,
- 'auto_setup' => true,
- ];
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 5.1, use "%s" instead. The Doctrine transport has been moved to package "symfony/doctrine-messenger" and will not be included by default in 6.0. Run "composer require symfony/doctrine-messenger".', Connection::class, BridgeConnection::class), E_USER_DEPRECATED);
+class_exists(BridgeConnection::class);
+
+if (false) {
/**
- * Configuration of the connection.
- *
- * Available options:
- *
- * * table_name: name of the table
- * * connection: name of the Doctrine's entity manager
- * * queue_name: name of the queue
- * * redeliver_timeout: Timeout before redeliver messages still in handling state (i.e: delivered_at is not null and message is still in table). Default 3600
- * * auto_setup: Whether the table should be created automatically during send / get. Default : true
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/doctrine-messenger instead.
*/
- private $configuration = [];
- private $driverConnection;
- private $schemaSynchronizer;
- private $autoSetup;
-
- public function __construct(array $configuration, DBALConnection $driverConnection, SchemaSynchronizer $schemaSynchronizer = null)
+ class Connection
{
- $this->configuration = array_replace_recursive(self::DEFAULT_OPTIONS, $configuration);
- $this->driverConnection = $driverConnection;
- $this->schemaSynchronizer = $schemaSynchronizer ?? new SingleDatabaseSynchronizer($this->driverConnection);
- $this->autoSetup = $this->configuration['auto_setup'];
- }
-
- public function getConfiguration(): array
- {
- return $this->configuration;
- }
-
- public static function buildConfiguration(string $dsn, array $options = []): array
- {
- if (false === $components = parse_url($dsn)) {
- throw new InvalidArgumentException(sprintf('The given Doctrine Messenger DSN "%s" is invalid.', $dsn));
- }
-
- $query = [];
- if (isset($components['query'])) {
- parse_str($components['query'], $query);
- }
-
- $configuration = ['connection' => $components['host']];
- $configuration += $options + $query + self::DEFAULT_OPTIONS;
-
- $configuration['auto_setup'] = filter_var($configuration['auto_setup'], FILTER_VALIDATE_BOOLEAN);
-
- // check for extra keys in options
- $optionsExtraKeys = array_diff(array_keys($options), array_keys(self::DEFAULT_OPTIONS));
- if (0 < \count($optionsExtraKeys)) {
- throw new InvalidArgumentException(sprintf('Unknown option found : [%s]. Allowed options are [%s]', implode(', ', $optionsExtraKeys), implode(', ', self::DEFAULT_OPTIONS)));
- }
-
- // check for extra keys in options
- $queryExtraKeys = array_diff(array_keys($query), array_keys(self::DEFAULT_OPTIONS));
- if (0 < \count($queryExtraKeys)) {
- throw new InvalidArgumentException(sprintf('Unknown option found in DSN: [%s]. Allowed options are [%s]', implode(', ', $queryExtraKeys), implode(', ', self::DEFAULT_OPTIONS)));
- }
-
- return $configuration;
- }
-
- /**
- * @param int $delay The delay in milliseconds
- *
- * @return string The inserted id
- *
- * @throws \Doctrine\DBAL\DBALException
- */
- public function send(string $body, array $headers, int $delay = 0): string
- {
- $now = new \DateTime();
- $availableAt = (clone $now)->modify(sprintf('+%d seconds', $delay / 1000));
-
- $queryBuilder = $this->driverConnection->createQueryBuilder()
- ->insert($this->configuration['table_name'])
- ->values([
- 'body' => '?',
- 'headers' => '?',
- 'queue_name' => '?',
- 'created_at' => '?',
- 'available_at' => '?',
- ]);
-
- $this->executeQuery($queryBuilder->getSQL(), [
- $body,
- json_encode($headers),
- $this->configuration['queue_name'],
- $now,
- $availableAt,
- ], [
- null,
- null,
- null,
- Type::DATETIME,
- Type::DATETIME,
- ]);
-
- return $this->driverConnection->lastInsertId();
- }
-
- public function get(): ?array
- {
- get:
- $this->driverConnection->beginTransaction();
- try {
- $query = $this->createAvailableMessagesQueryBuilder()
- ->orderBy('available_at', 'ASC')
- ->setMaxResults(1);
-
- // use SELECT ... FOR UPDATE to lock table
- $doctrineEnvelope = $this->executeQuery(
- $query->getSQL().' '.$this->driverConnection->getDatabasePlatform()->getWriteLockSQL(),
- $query->getParameters(),
- $query->getParameterTypes()
- )->fetch();
-
- if (false === $doctrineEnvelope) {
- $this->driverConnection->commit();
-
- return null;
- }
-
- $doctrineEnvelope = $this->decodeEnvelopeHeaders($doctrineEnvelope);
-
- $queryBuilder = $this->driverConnection->createQueryBuilder()
- ->update($this->configuration['table_name'])
- ->set('delivered_at', '?')
- ->where('id = ?');
- $now = new \DateTime();
- $this->executeQuery($queryBuilder->getSQL(), [
- $now,
- $doctrineEnvelope['id'],
- ], [
- Type::DATETIME,
- ]);
-
- $this->driverConnection->commit();
-
- return $doctrineEnvelope;
- } catch (\Throwable $e) {
- $this->driverConnection->rollBack();
-
- if ($this->autoSetup && $e instanceof TableNotFoundException) {
- $this->setup();
- goto get;
- }
-
- throw $e;
- }
- }
-
- public function ack(string $id): bool
- {
- try {
- return $this->driverConnection->delete($this->configuration['table_name'], ['id' => $id]) > 0;
- } catch (DBALException $exception) {
- throw new TransportException($exception->getMessage(), 0, $exception);
- }
- }
-
- public function reject(string $id): bool
- {
- try {
- return $this->driverConnection->delete($this->configuration['table_name'], ['id' => $id]) > 0;
- } catch (DBALException $exception) {
- throw new TransportException($exception->getMessage(), 0, $exception);
- }
- }
-
- public function setup(): void
- {
- $configuration = $this->driverConnection->getConfiguration();
- // Since Doctrine 2.9 the getFilterSchemaAssetsExpression is deprecated
- $hasFilterCallback = method_exists($configuration, 'getSchemaAssetsFilter');
-
- if ($hasFilterCallback) {
- $assetFilter = $this->driverConnection->getConfiguration()->getSchemaAssetsFilter();
- $this->driverConnection->getConfiguration()->setSchemaAssetsFilter(null);
- } else {
- $assetFilter = $this->driverConnection->getConfiguration()->getFilterSchemaAssetsExpression();
- $this->driverConnection->getConfiguration()->setFilterSchemaAssetsExpression(null);
- }
-
- $this->schemaSynchronizer->updateSchema($this->getSchema(), true);
-
- if ($hasFilterCallback) {
- $this->driverConnection->getConfiguration()->setSchemaAssetsFilter($assetFilter);
- } else {
- $this->driverConnection->getConfiguration()->setFilterSchemaAssetsExpression($assetFilter);
- }
-
- $this->autoSetup = false;
- }
-
- public function getMessageCount(): int
- {
- $queryBuilder = $this->createAvailableMessagesQueryBuilder()
- ->select('COUNT(m.id) as message_count')
- ->setMaxResults(1);
-
- return $this->executeQuery($queryBuilder->getSQL(), $queryBuilder->getParameters(), $queryBuilder->getParameterTypes())->fetchColumn();
- }
-
- public function findAll(int $limit = null): array
- {
- $queryBuilder = $this->createAvailableMessagesQueryBuilder();
- if (null !== $limit) {
- $queryBuilder->setMaxResults($limit);
- }
-
- $data = $this->executeQuery($queryBuilder->getSQL(), $queryBuilder->getParameters(), $queryBuilder->getParameterTypes())->fetchAll();
-
- return array_map(function ($doctrineEnvelope) {
- return $this->decodeEnvelopeHeaders($doctrineEnvelope);
- }, $data);
- }
-
- public function find($id): ?array
- {
- $queryBuilder = $this->createQueryBuilder()
- ->where('m.id = ?');
-
- $data = $this->executeQuery($queryBuilder->getSQL(), [
- $id,
- ])->fetch();
-
- return false === $data ? null : $this->decodeEnvelopeHeaders($data);
- }
-
- private function createAvailableMessagesQueryBuilder(): QueryBuilder
- {
- $now = new \DateTime();
- $redeliverLimit = (clone $now)->modify(sprintf('-%d seconds', $this->configuration['redeliver_timeout']));
-
- return $this->createQueryBuilder()
- ->where('m.delivered_at is null OR m.delivered_at < ?')
- ->andWhere('m.available_at <= ?')
- ->andWhere('m.queue_name = ?')
- ->setParameters([
- $redeliverLimit,
- $now,
- $this->configuration['queue_name'],
- ], [
- Type::DATETIME,
- Type::DATETIME,
- ]);
- }
-
- private function createQueryBuilder(): QueryBuilder
- {
- return $this->driverConnection->createQueryBuilder()
- ->select('m.*')
- ->from($this->configuration['table_name'], 'm');
- }
-
- private function executeQuery(string $sql, array $parameters = [], array $types = []): ResultStatement
- {
- try {
- $stmt = $this->driverConnection->executeQuery($sql, $parameters, $types);
- } catch (TableNotFoundException $e) {
- if ($this->driverConnection->isTransactionActive()) {
- throw $e;
- }
-
- // create table
- if ($this->autoSetup) {
- $this->setup();
- }
- $stmt = $this->driverConnection->executeQuery($sql, $parameters, $types);
- }
-
- return $stmt;
- }
-
- private function getSchema(): Schema
- {
- $schema = new Schema([], [], $this->driverConnection->getSchemaManager()->createSchemaConfig());
- $table = $schema->createTable($this->configuration['table_name']);
- $table->addColumn('id', Type::BIGINT)
- ->setAutoincrement(true)
- ->setNotnull(true);
- $table->addColumn('body', Type::TEXT)
- ->setNotnull(true);
- $table->addColumn('headers', Type::TEXT)
- ->setNotnull(true);
- $table->addColumn('queue_name', Type::STRING)
- ->setNotnull(true);
- $table->addColumn('created_at', Type::DATETIME)
- ->setNotnull(true);
- $table->addColumn('available_at', Type::DATETIME)
- ->setNotnull(true);
- $table->addColumn('delivered_at', Type::DATETIME)
- ->setNotnull(false);
- $table->setPrimaryKey(['id']);
- $table->addIndex(['queue_name']);
- $table->addIndex(['available_at']);
- $table->addIndex(['delivered_at']);
-
- return $schema;
- }
-
- private function decodeEnvelopeHeaders(array $doctrineEnvelope): array
- {
- $doctrineEnvelope['headers'] = json_decode($doctrineEnvelope['headers'], true);
-
- return $doctrineEnvelope;
}
}
diff --git a/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineReceivedStamp.php b/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineReceivedStamp.php
index 96cd3eb3f9..f754115b50 100644
--- a/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineReceivedStamp.php
+++ b/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineReceivedStamp.php
@@ -11,22 +11,17 @@
namespace Symfony\Component\Messenger\Transport\Doctrine;
-use Symfony\Component\Messenger\Stamp\NonSendableStampInterface;
+use Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineReceivedStamp as BridgeDoctrineReceivedStamp;
-/**
- * @author Vincent Touzet
- */
-class DoctrineReceivedStamp implements NonSendableStampInterface
-{
- private $id;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 5.1, use "%s" instead. The Doctrine transport has been moved to package "symfony/doctrine-messenger" and will not be included by default in 6.0. Run "composer require symfony/doctrine-messenger".', DoctrineReceivedStamp::class, BridgeDoctrineReceivedStamp::class), E_USER_DEPRECATED);
- public function __construct(string $id)
+class_exists(BridgeDoctrineReceivedStamp::class);
+
+if (false) {
+ /**
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/doctrine-messenger instead.
+ */
+ class DoctrineReceivedStamp
{
- $this->id = $id;
- }
-
- public function getId(): string
- {
- return $this->id;
}
}
diff --git a/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineReceiver.php b/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineReceiver.php
index 071cf2812a..2d36044841 100644
--- a/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineReceiver.php
+++ b/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineReceiver.php
@@ -11,162 +11,17 @@
namespace Symfony\Component\Messenger\Transport\Doctrine;
-use Doctrine\DBAL\DBALException;
-use Doctrine\DBAL\Exception\RetryableException;
-use Symfony\Component\Messenger\Envelope;
-use Symfony\Component\Messenger\Exception\LogicException;
-use Symfony\Component\Messenger\Exception\MessageDecodingFailedException;
-use Symfony\Component\Messenger\Exception\TransportException;
-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\Messenger\Transport\Serialization\SerializerInterface;
+use Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineReceiver as BridgeDoctrineReceiver;
-/**
- * @author Vincent Touzet
- */
-class DoctrineReceiver implements ReceiverInterface, MessageCountAwareInterface, ListableReceiverInterface
-{
- private const MAX_RETRIES = 3;
- private $retryingSafetyCounter = 0;
- private $connection;
- private $serializer;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 5.1, use "%s" instead. The Doctrine transport has been moved to package "symfony/doctrine-messenger" and will not be included by default in 6.0. Run "composer require symfony/doctrine-messenger".', DoctrineReceiver::class, BridgeDoctrineReceiver::class), E_USER_DEPRECATED);
- public function __construct(Connection $connection, SerializerInterface $serializer = null)
- {
- $this->connection = $connection;
- $this->serializer = $serializer ?? new PhpSerializer();
- }
+class_exists(BridgeDoctrineReceiver::class);
+if (false) {
/**
- * {@inheritdoc}
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/doctrine-messenger instead.
*/
- public function get(): iterable
+ class DoctrineReceiver
{
- try {
- $doctrineEnvelope = $this->connection->get();
- $this->retryingSafetyCounter = 0; // reset counter
- } catch (RetryableException $exception) {
- // Do nothing when RetryableException occurs less than "MAX_RETRIES"
- // as it will likely be resolved on the next call to get()
- // Problem with concurrent consumers and database deadlocks
- if (++$this->retryingSafetyCounter >= self::MAX_RETRIES) {
- $this->retryingSafetyCounter = 0; // reset counter
- throw new TransportException($exception->getMessage(), 0, $exception);
- }
-
- return [];
- } catch (DBALException $exception) {
- throw new TransportException($exception->getMessage(), 0, $exception);
- }
-
- if (null === $doctrineEnvelope) {
- return [];
- }
-
- return [$this->createEnvelopeFromData($doctrineEnvelope)];
- }
-
- /**
- * {@inheritdoc}
- */
- public function ack(Envelope $envelope): void
- {
- try {
- $this->connection->ack($this->findDoctrineReceivedStamp($envelope)->getId());
- } catch (DBALException $exception) {
- throw new TransportException($exception->getMessage(), 0, $exception);
- }
- }
-
- /**
- * {@inheritdoc}
- */
- public function reject(Envelope $envelope): void
- {
- try {
- $this->connection->reject($this->findDoctrineReceivedStamp($envelope)->getId());
- } catch (DBALException $exception) {
- throw new TransportException($exception->getMessage(), 0, $exception);
- }
- }
-
- /**
- * {@inheritdoc}
- */
- public function getMessageCount(): int
- {
- try {
- return $this->connection->getMessageCount();
- } catch (DBALException $exception) {
- throw new TransportException($exception->getMessage(), 0, $exception);
- }
- }
-
- /**
- * {@inheritdoc}
- */
- public function all(int $limit = null): iterable
- {
- try {
- $doctrineEnvelopes = $this->connection->findAll($limit);
- } catch (DBALException $exception) {
- throw new TransportException($exception->getMessage(), 0, $exception);
- }
-
- foreach ($doctrineEnvelopes as $doctrineEnvelope) {
- yield $this->createEnvelopeFromData($doctrineEnvelope);
- }
- }
-
- /**
- * {@inheritdoc}
- */
- public function find($id): ?Envelope
- {
- try {
- $doctrineEnvelope = $this->connection->find($id);
- } catch (DBALException $exception) {
- throw new TransportException($exception->getMessage(), 0, $exception);
- }
-
- if (null === $doctrineEnvelope) {
- return null;
- }
-
- return $this->createEnvelopeFromData($doctrineEnvelope);
- }
-
- private function findDoctrineReceivedStamp(Envelope $envelope): DoctrineReceivedStamp
- {
- /** @var DoctrineReceivedStamp|null $doctrineReceivedStamp */
- $doctrineReceivedStamp = $envelope->last(DoctrineReceivedStamp::class);
-
- if (null === $doctrineReceivedStamp) {
- throw new LogicException('No DoctrineReceivedStamp found on the Envelope.');
- }
-
- return $doctrineReceivedStamp;
- }
-
- private function createEnvelopeFromData(array $data): Envelope
- {
- try {
- $envelope = $this->serializer->decode([
- 'body' => $data['body'],
- 'headers' => $data['headers'],
- ]);
- } catch (MessageDecodingFailedException $exception) {
- $this->connection->reject($data['id']);
-
- throw $exception;
- }
-
- return $envelope->with(
- new DoctrineReceivedStamp($data['id']),
- new TransportMessageIdStamp($data['id'])
- );
}
}
diff --git a/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineSender.php b/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineSender.php
index ecfb5113e0..b0a645d855 100644
--- a/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineSender.php
+++ b/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineSender.php
@@ -11,46 +11,17 @@
namespace Symfony\Component\Messenger\Transport\Doctrine;
-use Doctrine\DBAL\DBALException;
-use Symfony\Component\Messenger\Envelope;
-use Symfony\Component\Messenger\Exception\TransportException;
-use Symfony\Component\Messenger\Stamp\DelayStamp;
-use Symfony\Component\Messenger\Stamp\TransportMessageIdStamp;
-use Symfony\Component\Messenger\Transport\Sender\SenderInterface;
-use Symfony\Component\Messenger\Transport\Serialization\PhpSerializer;
-use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
+use Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineSender as BridgeDoctrineSender;
-/**
- * @author Vincent Touzet
- */
-class DoctrineSender implements SenderInterface
-{
- private $connection;
- private $serializer;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 5.1, use "%s" instead. The Doctrine transport has been moved to package "symfony/doctrine-messenger" and will not be included by default in 6.0. Run "composer require symfony/doctrine-messenger".', DoctrineSender::class, BridgeDoctrineSender::class), E_USER_DEPRECATED);
- public function __construct(Connection $connection, SerializerInterface $serializer = null)
- {
- $this->connection = $connection;
- $this->serializer = $serializer ?? new PhpSerializer();
- }
+class_exists(BridgeDoctrineSender::class);
+if (false) {
/**
- * {@inheritdoc}
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/doctrine-messenger instead.
*/
- public function send(Envelope $envelope): Envelope
+ class DoctrineSender
{
- $encodedMessage = $this->serializer->encode($envelope);
-
- /** @var DelayStamp|null $delayStamp */
- $delayStamp = $envelope->last(DelayStamp::class);
- $delay = null !== $delayStamp ? $delayStamp->getDelay() : 0;
-
- try {
- $id = $this->connection->send($encodedMessage['body'], $encodedMessage['headers'] ?? [], $delay);
- } catch (DBALException $exception) {
- throw new TransportException($exception->getMessage(), 0, $exception);
- }
-
- return $envelope->with(new TransportMessageIdStamp($id));
}
}
diff --git a/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineTransport.php b/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineTransport.php
index 6ed54e590f..416edb0e81 100644
--- a/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineTransport.php
+++ b/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineTransport.php
@@ -11,100 +11,17 @@
namespace Symfony\Component\Messenger\Transport\Doctrine;
-use Symfony\Component\Messenger\Envelope;
-use Symfony\Component\Messenger\Transport\Receiver\ListableReceiverInterface;
-use Symfony\Component\Messenger\Transport\Receiver\MessageCountAwareInterface;
-use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
-use Symfony\Component\Messenger\Transport\SetupableTransportInterface;
-use Symfony\Component\Messenger\Transport\TransportInterface;
+use Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineTransport as BridgeDoctrineTransport;
-/**
- * @author Vincent Touzet
- */
-class DoctrineTransport implements TransportInterface, SetupableTransportInterface, MessageCountAwareInterface, ListableReceiverInterface
-{
- private $connection;
- private $serializer;
- private $receiver;
- private $sender;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 5.1, use "%s" instead. The Doctrine transport has been moved to package "symfony/doctrine-messenger" and will not be included by default in 6.0. Run "composer require symfony/doctrine-messenger".', DoctrineTransport::class, BridgeDoctrineTransport::class), E_USER_DEPRECATED);
- public function __construct(Connection $connection, SerializerInterface $serializer)
- {
- $this->connection = $connection;
- $this->serializer = $serializer;
- }
+class_exists(BridgeDoctrineTransport::class);
+if (false) {
/**
- * {@inheritdoc}
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/doctrine-messenger instead.
*/
- public function get(): iterable
+ class DoctrineTransport
{
- return ($this->receiver ?? $this->getReceiver())->get();
- }
-
- /**
- * {@inheritdoc}
- */
- public function ack(Envelope $envelope): void
- {
- ($this->receiver ?? $this->getReceiver())->ack($envelope);
- }
-
- /**
- * {@inheritdoc}
- */
- public function reject(Envelope $envelope): void
- {
- ($this->receiver ?? $this->getReceiver())->reject($envelope);
- }
-
- /**
- * {@inheritdoc}
- */
- public function getMessageCount(): int
- {
- return ($this->receiver ?? $this->getReceiver())->getMessageCount();
- }
-
- /**
- * {@inheritdoc}
- */
- public function all(int $limit = null): iterable
- {
- return ($this->receiver ?? $this->getReceiver())->all($limit);
- }
-
- /**
- * {@inheritdoc}
- */
- public function find($id): ?Envelope
- {
- return ($this->receiver ?? $this->getReceiver())->find($id);
- }
-
- /**
- * {@inheritdoc}
- */
- public function send(Envelope $envelope): Envelope
- {
- return ($this->sender ?? $this->getSender())->send($envelope);
- }
-
- /**
- * {@inheritdoc}
- */
- public function setup(): void
- {
- $this->connection->setup();
- }
-
- private function getReceiver(): DoctrineReceiver
- {
- return $this->receiver = new DoctrineReceiver($this->connection, $this->serializer);
- }
-
- private function getSender(): DoctrineSender
- {
- return $this->sender = new DoctrineSender($this->connection, $this->serializer);
}
}
diff --git a/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineTransportFactory.php b/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineTransportFactory.php
index b4455e04f1..29df160f2a 100644
--- a/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineTransportFactory.php
+++ b/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineTransportFactory.php
@@ -11,47 +11,17 @@
namespace Symfony\Component\Messenger\Transport\Doctrine;
-use Doctrine\Persistence\ConnectionRegistry;
-use Symfony\Bridge\Doctrine\RegistryInterface;
-use Symfony\Component\Messenger\Exception\TransportException;
-use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
-use Symfony\Component\Messenger\Transport\TransportFactoryInterface;
-use Symfony\Component\Messenger\Transport\TransportInterface;
+use Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineTransportFactory as BridgeDoctrineTransportFactory;
-/**
- * @author Vincent Touzet
- */
-class DoctrineTransportFactory implements TransportFactoryInterface
-{
- private $registry;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 5.1, use "%s" instead. The Doctrine transport has been moved to package "symfony/doctrine-messenger" and will not be included by default in 6.0. Run "composer require symfony/doctrine-messenger".', DoctrineTransportFactory::class, BridgeDoctrineTransportFactory::class), E_USER_DEPRECATED);
- public function __construct($registry)
+class_exists(BridgeDoctrineTransportFactory::class);
+
+if (false) {
+ /**
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/doctrine-messenger instead.
+ */
+ class DoctrineTransportFactory
{
- if (!$registry instanceof RegistryInterface && !$registry instanceof ConnectionRegistry) {
- throw new \TypeError(sprintf('Expected an instance of %s or %s, but got %s.', RegistryInterface::class, ConnectionRegistry::class, \is_object($registry) ? \get_class($registry) : \gettype($registry)));
- }
-
- $this->registry = $registry;
- }
-
- public function createTransport(string $dsn, array $options, SerializerInterface $serializer): TransportInterface
- {
- unset($options['transport_name']);
- $configuration = Connection::buildConfiguration($dsn, $options);
-
- try {
- $driverConnection = $this->registry->getConnection($configuration['connection']);
- } catch (\InvalidArgumentException $e) {
- throw new TransportException(sprintf('Could not find Doctrine connection from Messenger DSN "%s".', $dsn), 0, $e);
- }
-
- $connection = new Connection($configuration, $driverConnection);
-
- return new DoctrineTransport($connection, $serializer);
- }
-
- public function supports(string $dsn, array $options): bool
- {
- return 0 === strpos($dsn, 'doctrine://');
}
}
diff --git a/src/Symfony/Component/Messenger/Transport/RedisExt/Connection.php b/src/Symfony/Component/Messenger/Transport/RedisExt/Connection.php
index f4cc3a158e..070ac7e5c9 100644
--- a/src/Symfony/Component/Messenger/Transport/RedisExt/Connection.php
+++ b/src/Symfony/Component/Messenger/Transport/RedisExt/Connection.php
@@ -11,318 +11,17 @@
namespace Symfony\Component\Messenger\Transport\RedisExt;
-use Symfony\Component\Messenger\Exception\InvalidArgumentException;
-use Symfony\Component\Messenger\Exception\LogicException;
-use Symfony\Component\Messenger\Exception\TransportException;
+use Symfony\Component\Messenger\Bridge\Redis\Transport\Connection as BridgeConnection;
-/**
- * A Redis connection.
- *
- * @author Alexander Schranz
- * @author Antoine Bluchet
- * @author Robin Chalas
- *
- * @internal
- * @final
- */
-class Connection
-{
- private const DEFAULT_OPTIONS = [
- 'stream' => 'messages',
- 'group' => 'symfony',
- 'consumer' => 'consumer',
- 'auto_setup' => true,
- 'stream_max_entries' => 0, // any value higher than 0 defines an approximate maximum number of stream entries
- 'dbindex' => 0,
- ];
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 5.1, use "%s" instead. The RedisExt transport has been moved to package "symfony/redis-messenger" and will not be included by default in 6.0. Run "composer require symfony/redis-messenger".', Connection::class, BridgeConnection::class), E_USER_DEPRECATED);
- private $connection;
- private $stream;
- private $queue;
- private $group;
- private $consumer;
- private $autoSetup;
- private $maxEntries;
- private $couldHavePendingMessages = true;
+class_exists(BridgeConnection::class);
- public function __construct(array $configuration, array $connectionCredentials = [], array $redisOptions = [], \Redis $redis = null)
+if (false) {
+ /**
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/redis-messenger instead.
+ */
+ class Connection
{
- if (version_compare(phpversion('redis'), '4.3.0', '<')) {
- throw new LogicException('The redis transport requires php-redis 4.3.0 or higher.');
- }
-
- $this->connection = $redis ?: new \Redis();
- $this->connection->connect($connectionCredentials['host'] ?? '127.0.0.1', $connectionCredentials['port'] ?? 6379);
- $this->connection->setOption(\Redis::OPT_SERIALIZER, $redisOptions['serializer'] ?? \Redis::SERIALIZER_PHP);
-
- if (isset($connectionCredentials['auth']) && !$this->connection->auth($connectionCredentials['auth'])) {
- throw new InvalidArgumentException(sprintf('Redis connection failed: %s', $redis->getLastError()));
- }
-
- if (($dbIndex = $configuration['dbindex'] ?? self::DEFAULT_OPTIONS['dbindex']) && !$this->connection->select($dbIndex)) {
- throw new InvalidArgumentException(sprintf('Redis connection failed: %s', $redis->getLastError()));
- }
-
- $this->stream = $configuration['stream'] ?? self::DEFAULT_OPTIONS['stream'];
- $this->group = $configuration['group'] ?? self::DEFAULT_OPTIONS['group'];
- $this->consumer = $configuration['consumer'] ?? self::DEFAULT_OPTIONS['consumer'];
- $this->queue = $this->stream.'__queue';
- $this->autoSetup = $configuration['auto_setup'] ?? self::DEFAULT_OPTIONS['auto_setup'];
- $this->maxEntries = $configuration['stream_max_entries'] ?? self::DEFAULT_OPTIONS['stream_max_entries'];
- }
-
- public static function fromDsn(string $dsn, array $redisOptions = [], \Redis $redis = null): self
- {
- $url = $dsn;
-
- if (preg_match('#^redis:///([^:@])+$#', $dsn)) {
- $url = str_replace('redis:', 'file:', $dsn);
- }
-
- if (false === $parsedUrl = parse_url($url)) {
- throw new InvalidArgumentException(sprintf('The given Redis DSN "%s" is invalid.', $dsn));
- }
- if (isset($parsedUrl['query'])) {
- parse_str($parsedUrl['query'], $redisOptions);
- }
-
- $autoSetup = null;
- if (\array_key_exists('auto_setup', $redisOptions)) {
- $autoSetup = filter_var($redisOptions['auto_setup'], FILTER_VALIDATE_BOOLEAN);
- unset($redisOptions['auto_setup']);
- }
-
- $maxEntries = null;
- if (\array_key_exists('stream_max_entries', $redisOptions)) {
- $maxEntries = filter_var($redisOptions['stream_max_entries'], FILTER_VALIDATE_INT);
- unset($redisOptions['stream_max_entries']);
- }
-
- $dbIndex = null;
- if (\array_key_exists('dbindex', $redisOptions)) {
- $dbIndex = filter_var($redisOptions['dbindex'], FILTER_VALIDATE_INT);
- unset($redisOptions['dbindex']);
- }
-
- $configuration = [
- 'stream' => $redisOptions['stream'] ?? null,
- 'group' => $redisOptions['group'] ?? null,
- 'consumer' => $redisOptions['consumer'] ?? null,
- 'auto_setup' => $autoSetup,
- 'stream_max_entries' => $maxEntries,
- 'dbindex' => $dbIndex,
- ];
-
- if (isset($parsedUrl['host'])) {
- $connectionCredentials = [
- 'host' => $parsedUrl['host'] ?? '127.0.0.1',
- 'port' => $parsedUrl['port'] ?? 6379,
- 'auth' => $parsedUrl['pass'] ?? $parsedUrl['user'] ?? null,
- ];
-
- $pathParts = explode('/', $parsedUrl['path'] ?? '');
-
- $configuration['stream'] = $pathParts[1] ?? $configuration['stream'];
- $configuration['group'] = $pathParts[2] ?? $configuration['group'];
- $configuration['consumer'] = $pathParts[3] ?? $configuration['consumer'];
- } else {
- $connectionCredentials = [
- 'host' => $parsedUrl['path'],
- 'port' => 0,
- ];
- }
-
- return new self($configuration, $connectionCredentials, $redisOptions, $redis);
- }
-
- public function get(): ?array
- {
- if ($this->autoSetup) {
- $this->setup();
- }
-
- try {
- $queuedMessageCount = $this->connection->zcount($this->queue, 0, $this->getCurrentTimeInMilliseconds());
- } catch (\RedisException $e) {
- throw new TransportException($e->getMessage(), 0, $e);
- }
-
- if ($queuedMessageCount) {
- for ($i = 0; $i < $queuedMessageCount; ++$i) {
- try {
- $queuedMessages = $this->connection->zpopmin($this->queue, 1);
- } catch (\RedisException $e) {
- throw new TransportException($e->getMessage(), 0, $e);
- }
-
- foreach ($queuedMessages as $queuedMessage => $time) {
- $queuedMessage = json_decode($queuedMessage, true);
- // if a futured placed message is actually popped because of a race condition with
- // another running message consumer, the message is readded to the queue by add function
- // else its just added stream and will be available for all stream consumers
- $this->add(
- $queuedMessage['body'],
- $queuedMessage['headers'],
- $time - $this->getCurrentTimeInMilliseconds()
- );
- }
- }
- }
-
- $messageId = '>'; // will receive new messages
-
- if ($this->couldHavePendingMessages) {
- $messageId = '0'; // will receive consumers pending messages
- }
-
- try {
- $messages = $this->connection->xreadgroup(
- $this->group,
- $this->consumer,
- [$this->stream => $messageId],
- 1
- );
- } catch (\RedisException $e) {
- throw new TransportException($e->getMessage(), 0, $e);
- }
-
- if (false === $messages) {
- if ($error = $this->connection->getLastError() ?: null) {
- $this->connection->clearLastError();
- }
-
- throw new TransportException($error ?? 'Could not read messages from the redis stream.');
- }
-
- if ($this->couldHavePendingMessages && empty($messages[$this->stream])) {
- $this->couldHavePendingMessages = false;
-
- // No pending messages so get a new one
- return $this->get();
- }
-
- foreach ($messages[$this->stream] ?? [] as $key => $message) {
- $redisEnvelope = json_decode($message['message'], true);
-
- return [
- 'id' => $key,
- 'body' => $redisEnvelope['body'],
- 'headers' => $redisEnvelope['headers'],
- ];
- }
-
- return null;
- }
-
- public function ack(string $id): void
- {
- try {
- $acknowledged = $this->connection->xack($this->stream, $this->group, [$id]);
- } catch (\RedisException $e) {
- throw new TransportException($e->getMessage(), 0, $e);
- }
-
- if (!$acknowledged) {
- if ($error = $this->connection->getLastError() ?: null) {
- $this->connection->clearLastError();
- }
- throw new TransportException($error ?? sprintf('Could not acknowledge redis message "%s".', $id));
- }
- }
-
- public function reject(string $id): void
- {
- try {
- $deleted = $this->connection->xack($this->stream, $this->group, [$id]);
- $deleted = $this->connection->xdel($this->stream, [$id]) && $deleted;
- } catch (\RedisException $e) {
- throw new TransportException($e->getMessage(), 0, $e);
- }
-
- if (!$deleted) {
- if ($error = $this->connection->getLastError() ?: null) {
- $this->connection->clearLastError();
- }
- throw new TransportException($error ?? sprintf('Could not delete message "%s" from the redis stream.', $id));
- }
- }
-
- public function add(string $body, array $headers, int $delayInMs = 0): void
- {
- if ($this->autoSetup) {
- $this->setup();
- }
-
- try {
- if ($delayInMs > 0) { // the delay could be smaller 0 in a queued message
- $message = json_encode([
- 'body' => $body,
- 'headers' => $headers,
- // Entry need to be unique in the sorted set else it would only be added once to the delayed messages queue
- 'uniqid' => uniqid('', true),
- ]);
-
- if (false === $message) {
- throw new TransportException(json_last_error_msg());
- }
-
- $score = (int) ($this->getCurrentTimeInMilliseconds() + $delayInMs);
- $added = $this->connection->zadd($this->queue, ['NX'], $score, $message);
- } else {
- $message = json_encode([
- 'body' => $body,
- 'headers' => $headers,
- ]);
-
- if (false === $message) {
- throw new TransportException(json_last_error_msg());
- }
-
- if ($this->maxEntries) {
- $added = $this->connection->xadd($this->stream, '*', ['message' => $message], $this->maxEntries, true);
- } else {
- $added = $this->connection->xadd($this->stream, '*', ['message' => $message]);
- }
- }
- } catch (\RedisException $e) {
- if ($error = $this->connection->getLastError() ?: null) {
- $this->connection->clearLastError();
- }
- throw new TransportException($error ?? $e->getMessage(), 0, $e);
- }
-
- if (!$added) {
- if ($error = $this->connection->getLastError() ?: null) {
- $this->connection->clearLastError();
- }
- throw new TransportException($error ?? 'Could not add a message to the redis stream.');
- }
- }
-
- public function setup(): void
- {
- try {
- $this->connection->xgroup('CREATE', $this->stream, $this->group, 0, true);
- } catch (\RedisException $e) {
- throw new TransportException($e->getMessage(), 0, $e);
- }
-
- // group might already exist, ignore
- if ($this->connection->getLastError()) {
- $this->connection->clearLastError();
- }
-
- $this->autoSetup = false;
- }
-
- private function getCurrentTimeInMilliseconds(): int
- {
- return (int) (microtime(true) * 1000);
- }
-
- public function cleanup(): void
- {
- $this->connection->del($this->stream);
- $this->connection->del($this->queue);
}
}
diff --git a/src/Symfony/Component/Messenger/Transport/RedisExt/RedisReceivedStamp.php b/src/Symfony/Component/Messenger/Transport/RedisExt/RedisReceivedStamp.php
index 1f7803394c..3a81152bcc 100644
--- a/src/Symfony/Component/Messenger/Transport/RedisExt/RedisReceivedStamp.php
+++ b/src/Symfony/Component/Messenger/Transport/RedisExt/RedisReceivedStamp.php
@@ -11,22 +11,17 @@
namespace Symfony\Component\Messenger\Transport\RedisExt;
-use Symfony\Component\Messenger\Stamp\NonSendableStampInterface;
+use Symfony\Component\Messenger\Bridge\Redis\Transport\RedisReceivedStamp as BridgeRedisReceivedStamp;
-/**
- * @author Alexander Schranz
- */
-class RedisReceivedStamp implements NonSendableStampInterface
-{
- private $id;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 5.1, use "%s" instead. The RedisExt transport has been moved to package "symfony/redis-messenger" and will not be included by default in 6.0. Run "composer require symfony/redis-messenger".', RedisReceivedStamp::class, BridgeRedisReceivedStamp::class), E_USER_DEPRECATED);
- public function __construct(string $id)
+class_exists(BridgeRedisReceivedStamp::class);
+
+if (false) {
+ /**
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/redis-messenger instead.
+ */
+ class RedisReceivedStamp
{
- $this->id = $id;
- }
-
- public function getId(): string
- {
- return $this->id;
}
}
diff --git a/src/Symfony/Component/Messenger/Transport/RedisExt/RedisReceiver.php b/src/Symfony/Component/Messenger/Transport/RedisExt/RedisReceiver.php
index 5425812de7..bfc1eaf560 100644
--- a/src/Symfony/Component/Messenger/Transport/RedisExt/RedisReceiver.php
+++ b/src/Symfony/Component/Messenger/Transport/RedisExt/RedisReceiver.php
@@ -11,78 +11,17 @@
namespace Symfony\Component\Messenger\Transport\RedisExt;
-use Symfony\Component\Messenger\Envelope;
-use Symfony\Component\Messenger\Exception\LogicException;
-use Symfony\Component\Messenger\Exception\MessageDecodingFailedException;
-use Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface;
-use Symfony\Component\Messenger\Transport\Serialization\PhpSerializer;
-use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
+use Symfony\Component\Messenger\Bridge\Redis\Transport\RedisReceiver as BridgeRedisReceiver;
-/**
- * @author Alexander Schranz
- * @author Antoine Bluchet
- */
-class RedisReceiver implements ReceiverInterface
-{
- private $connection;
- private $serializer;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 5.1, use "%s" instead. The RedisExt transport has been moved to package "symfony/redis-messenger" and will not be included by default in 6.0. Run "composer require symfony/redis-messenger".', RedisReceiver::class, BridgeRedisReceiver::class), E_USER_DEPRECATED);
- public function __construct(Connection $connection, SerializerInterface $serializer = null)
- {
- $this->connection = $connection;
- $this->serializer = $serializer ?? new PhpSerializer();
- }
+class_exists(BridgeRedisReceiver::class);
+if (false) {
/**
- * {@inheritdoc}
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/redis-messenger instead.
*/
- public function get(): iterable
+ class RedisReceiver
{
- $redisEnvelope = $this->connection->get();
-
- if (null === $redisEnvelope) {
- return [];
- }
-
- try {
- $envelope = $this->serializer->decode([
- 'body' => $redisEnvelope['body'],
- 'headers' => $redisEnvelope['headers'],
- ]);
- } catch (MessageDecodingFailedException $exception) {
- $this->connection->reject($redisEnvelope['id']);
-
- throw $exception;
- }
-
- return [$envelope->with(new RedisReceivedStamp($redisEnvelope['id']))];
- }
-
- /**
- * {@inheritdoc}
- */
- public function ack(Envelope $envelope): void
- {
- $this->connection->ack($this->findRedisReceivedStamp($envelope)->getId());
- }
-
- /**
- * {@inheritdoc}
- */
- public function reject(Envelope $envelope): void
- {
- $this->connection->reject($this->findRedisReceivedStamp($envelope)->getId());
- }
-
- private function findRedisReceivedStamp(Envelope $envelope): RedisReceivedStamp
- {
- /** @var RedisReceivedStamp|null $redisReceivedStamp */
- $redisReceivedStamp = $envelope->last(RedisReceivedStamp::class);
-
- if (null === $redisReceivedStamp) {
- throw new LogicException('No RedisReceivedStamp found on the Envelope.');
- }
-
- return $redisReceivedStamp;
}
}
diff --git a/src/Symfony/Component/Messenger/Transport/RedisExt/RedisSender.php b/src/Symfony/Component/Messenger/Transport/RedisExt/RedisSender.php
index beda996870..e5954c9a21 100644
--- a/src/Symfony/Component/Messenger/Transport/RedisExt/RedisSender.php
+++ b/src/Symfony/Component/Messenger/Transport/RedisExt/RedisSender.php
@@ -11,39 +11,17 @@
namespace Symfony\Component\Messenger\Transport\RedisExt;
-use Symfony\Component\Messenger\Envelope;
-use Symfony\Component\Messenger\Stamp\DelayStamp;
-use Symfony\Component\Messenger\Transport\Sender\SenderInterface;
-use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
+use Symfony\Component\Messenger\Bridge\Redis\Transport\RedisSender as BridgeRedisSender;
-/**
- * @author Alexander Schranz
- * @author Antoine Bluchet
- */
-class RedisSender implements SenderInterface
-{
- private $connection;
- private $serializer;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 5.1, use "%s" instead. The RedisExt transport has been moved to package "symfony/redis-messenger" and will not be included by default in 6.0. Run "composer require symfony/redis-messenger".', RedisSender::class, BridgeRedisSender::class), E_USER_DEPRECATED);
- public function __construct(Connection $connection, SerializerInterface $serializer)
- {
- $this->connection = $connection;
- $this->serializer = $serializer;
- }
+class_exists(BridgeRedisSender::class);
+if (false) {
/**
- * {@inheritdoc}
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/redis-messenger instead.
*/
- public function send(Envelope $envelope): Envelope
+ class RedisSender
{
- $encodedMessage = $this->serializer->encode($envelope);
-
- /** @var DelayStamp|null $delayStamp */
- $delayStamp = $envelope->last(DelayStamp::class);
- $delayInMs = null !== $delayStamp ? $delayStamp->getDelay() : 0;
-
- $this->connection->add($encodedMessage['body'], $encodedMessage['headers'] ?? [], $delayInMs);
-
- return $envelope;
}
}
diff --git a/src/Symfony/Component/Messenger/Transport/RedisExt/RedisTransport.php b/src/Symfony/Component/Messenger/Transport/RedisExt/RedisTransport.php
index 61e14822f2..911e905947 100644
--- a/src/Symfony/Component/Messenger/Transport/RedisExt/RedisTransport.php
+++ b/src/Symfony/Component/Messenger/Transport/RedisExt/RedisTransport.php
@@ -11,76 +11,17 @@
namespace Symfony\Component\Messenger\Transport\RedisExt;
-use Symfony\Component\Messenger\Envelope;
-use Symfony\Component\Messenger\Transport\Serialization\PhpSerializer;
-use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
-use Symfony\Component\Messenger\Transport\SetupableTransportInterface;
-use Symfony\Component\Messenger\Transport\TransportInterface;
+use Symfony\Component\Messenger\Bridge\Redis\Transport\RedisTransport as BridgeRedisTransport;
-/**
- * @author Alexander Schranz
- * @author Antoine Bluchet
- */
-class RedisTransport implements TransportInterface, SetupableTransportInterface
-{
- private $serializer;
- private $connection;
- private $receiver;
- private $sender;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 5.1, use "%s" instead. The RedisExt transport has been moved to package "symfony/redis-messenger" and will not be included by default in 6.0. Run "composer require symfony/redis-messenger".', RedisTransport::class, BridgeRedisTransport::class), E_USER_DEPRECATED);
- public function __construct(Connection $connection, SerializerInterface $serializer = null)
- {
- $this->connection = $connection;
- $this->serializer = $serializer ?? new PhpSerializer();
- }
+class_exists(BridgeRedisTransport::class);
+if (false) {
/**
- * {@inheritdoc}
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/redis-messenger instead.
*/
- public function get(): iterable
+ class RedisTransport
{
- return ($this->receiver ?? $this->getReceiver())->get();
- }
-
- /**
- * {@inheritdoc}
- */
- public function ack(Envelope $envelope): void
- {
- ($this->receiver ?? $this->getReceiver())->ack($envelope);
- }
-
- /**
- * {@inheritdoc}
- */
- public function reject(Envelope $envelope): void
- {
- ($this->receiver ?? $this->getReceiver())->reject($envelope);
- }
-
- /**
- * {@inheritdoc}
- */
- public function send(Envelope $envelope): Envelope
- {
- return ($this->sender ?? $this->getSender())->send($envelope);
- }
-
- /**
- * {@inheritdoc}
- */
- public function setup(): void
- {
- $this->connection->setup();
- }
-
- private function getReceiver(): RedisReceiver
- {
- return $this->receiver = new RedisReceiver($this->connection, $this->serializer);
- }
-
- private function getSender(): RedisSender
- {
- return $this->sender = new RedisSender($this->connection, $this->serializer);
}
}
diff --git a/src/Symfony/Component/Messenger/Transport/RedisExt/RedisTransportFactory.php b/src/Symfony/Component/Messenger/Transport/RedisExt/RedisTransportFactory.php
index 60ea10dca7..a2897c1561 100644
--- a/src/Symfony/Component/Messenger/Transport/RedisExt/RedisTransportFactory.php
+++ b/src/Symfony/Component/Messenger/Transport/RedisExt/RedisTransportFactory.php
@@ -11,25 +11,17 @@
namespace Symfony\Component\Messenger\Transport\RedisExt;
-use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
-use Symfony\Component\Messenger\Transport\TransportFactoryInterface;
-use Symfony\Component\Messenger\Transport\TransportInterface;
+use Symfony\Component\Messenger\Bridge\Redis\Transport\RedisTransportFactory as BridgeRedisTransportFactory;
-/**
- * @author Alexander Schranz
- * @author Antoine Bluchet
- */
-class RedisTransportFactory implements TransportFactoryInterface
-{
- public function createTransport(string $dsn, array $options, SerializerInterface $serializer): TransportInterface
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 5.1, use "%s" instead. The RedisExt transport has been moved to package "symfony/redis-messenger" and will not be included by default in 6.0. Run "composer require symfony/redis-messenger".', RedisTransportFactory::class, BridgeRedisTransportFactory::class), E_USER_DEPRECATED);
+
+class_exists(BridgeRedisTransportFactory::class);
+
+if (false) {
+ /**
+ * @deprecated since Symfony 5.1, to be removed in 6.0. Use symfony/redis-messenger instead.
+ */
+ class RedisTransportFactory
{
- unset($options['transport_name']);
-
- return new RedisTransport(Connection::fromDsn($dsn, $options), $serializer);
- }
-
- public function supports(string $dsn, array $options): bool
- {
- return 0 === strpos($dsn, 'redis://');
}
}
diff --git a/src/Symfony/Component/Messenger/Transport/TransportFactory.php b/src/Symfony/Component/Messenger/Transport/TransportFactory.php
index 4565efe41b..ae62f7ab9b 100644
--- a/src/Symfony/Component/Messenger/Transport/TransportFactory.php
+++ b/src/Symfony/Component/Messenger/Transport/TransportFactory.php
@@ -37,7 +37,17 @@ class TransportFactory implements TransportFactoryInterface
}
}
- throw new InvalidArgumentException(sprintf('No transport supports the given Messenger DSN "%s".', $dsn));
+ // Help the user to select Symfony packages based on protocol.
+ $packageSuggestion = '';
+ if (substr($dsn, 0, 7) === 'amqp://') {
+ $packageSuggestion = ' Run "composer require symfony/amqp-messenger" to install AMQP transport.';
+ } elseif (substr($dsn, 0, 11) === 'doctrine://') {
+ $packageSuggestion = ' Run "composer require symfony/doctrine-messenger" to install Doctrine transport.';
+ } elseif (substr($dsn, 0, 8) === 'redis://') {
+ $packageSuggestion = ' Run "composer require symfony/redis-messenger" to install Redis transport.';
+ }
+
+ throw new InvalidArgumentException(sprintf('No transport supports the given Messenger DSN "%s".%s', $dsn, $packageSuggestion));
}
public function supports(string $dsn, array $options): bool
diff --git a/src/Symfony/Component/Messenger/composer.json b/src/Symfony/Component/Messenger/composer.json
index 510ac0f68c..054836940b 100644
--- a/src/Symfony/Component/Messenger/composer.json
+++ b/src/Symfony/Component/Messenger/composer.json
@@ -17,12 +17,13 @@
],
"require": {
"php": "^7.2.5",
- "psr/log": "~1.0"
+ "psr/log": "~1.0",
+ "symfony/amqp-messenger": "^5.1",
+ "symfony/doctrine-messenger": "^5.1",
+ "symfony/redis-messenger": "^5.1"
},
"require-dev": {
- "doctrine/dbal": "^2.6",
"psr/cache": "~1.0",
- "doctrine/persistence": "^1.3",
"symfony/console": "^4.4|^5.0",
"symfony/dependency-injection": "^4.4|^5.0",
"symfony/event-dispatcher": "^4.4|^5.0",
@@ -35,7 +36,6 @@
"symfony/validator": "^4.4|^5.0"
},
"conflict": {
- "doctrine/persistence": "<1.3",
"symfony/event-dispatcher": "<4.4",
"symfony/framework-bundle": "<4.4",
"symfony/http-kernel": "<4.4"