vendor/symfony/redis-messenger/Transport/Connection.php line 169

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Component\Messenger\Bridge\Redis\Transport;
  11. use Symfony\Component\Messenger\Exception\InvalidArgumentException;
  12. use Symfony\Component\Messenger\Exception\LogicException;
  13. use Symfony\Component\Messenger\Exception\TransportException;
  14. /**
  15.  * A Redis connection.
  16.  *
  17.  * @author Alexander Schranz <alexander@sulu.io>
  18.  * @author Antoine Bluchet <soyuka@gmail.com>
  19.  * @author Robin Chalas <robin.chalas@gmail.com>
  20.  *
  21.  * @internal
  22.  *
  23.  * @final
  24.  */
  25. class Connection
  26. {
  27.     private const DEFAULT_OPTIONS = [
  28.         'stream' => 'messages',
  29.         'group' => 'symfony',
  30.         'consumer' => 'consumer',
  31.         'auto_setup' => true,
  32.         'delete_after_ack' => false,
  33.         'delete_after_reject' => true,
  34.         'stream_max_entries' => 0// any value higher than 0 defines an approximate maximum number of stream entries
  35.         'dbindex' => 0,
  36.         'tls' => false,
  37.         'redeliver_timeout' => 3600// Timeout before redeliver messages still in pending state (seconds)
  38.         'claim_interval' => 60000// Interval by which pending/abandoned messages should be checked
  39.         'lazy' => false,
  40.         'auth' => null,
  41.         'serializer' => \Redis::SERIALIZER_PHP,
  42.     ];
  43.     private $connection;
  44.     private $stream;
  45.     private $queue;
  46.     private $group;
  47.     private $consumer;
  48.     private $autoSetup;
  49.     private $maxEntries;
  50.     private $redeliverTimeout;
  51.     private $nextClaim 0.0;
  52.     private $claimInterval;
  53.     private $deleteAfterAck;
  54.     private $deleteAfterReject;
  55.     private $couldHavePendingMessages true;
  56.     /**
  57.      * @param \Redis|\RedisCluster|null $redis
  58.      */
  59.     public function __construct(array $configuration, array $connectionCredentials = [], array $redisOptions = [], $redis null)
  60.     {
  61.         if (version_compare(phpversion('redis'), '4.3.0''<')) {
  62.             throw new LogicException('The redis transport requires php-redis 4.3.0 or higher.');
  63.         }
  64.         $host $connectionCredentials['host'] ?? '127.0.0.1';
  65.         $port $connectionCredentials['port'] ?? 6379;
  66.         $serializer $redisOptions['serializer'] ?? \Redis::SERIALIZER_PHP;
  67.         $dbIndex $configuration['dbindex'] ?? self::DEFAULT_OPTIONS['dbindex'];
  68.         $auth $connectionCredentials['auth'] ?? null;
  69.         if ('' === $auth) {
  70.             $auth null;
  71.         }
  72.         $lazy $configuration['lazy'] ?? self::DEFAULT_OPTIONS['lazy'];
  73.         if (\is_array($host) || $redis instanceof \RedisCluster) {
  74.             $hosts = \is_string($host) ? [$host.':'.$port] : $host// Always ensure we have an array
  75.             $initializer = static function ($redis) use ($hosts$auth$serializer) {
  76.                 return self::initializeRedisCluster($redis$hosts$auth$serializer);
  77.             };
  78.             $redis $lazy ? new RedisClusterProxy($redis$initializer) : $initializer($redis);
  79.         } else {
  80.             $redis $redis ?? new \Redis();
  81.             $initializer = static function ($redis) use ($host$port$auth$serializer$dbIndex) {
  82.                 return self::initializeRedis($redis$host$port$auth$serializer$dbIndex);
  83.             };
  84.             $redis $lazy ? new RedisProxy($redis$initializer) : $initializer($redis);
  85.         }
  86.         $this->connection $redis;
  87.         foreach (['stream''group''consumer'] as $key) {
  88.             if (isset($configuration[$key]) && '' === $configuration[$key]) {
  89.                 throw new InvalidArgumentException(sprintf('"%s" should be configured, got an empty string.'$key));
  90.             }
  91.         }
  92.         $this->stream $configuration['stream'] ?? self::DEFAULT_OPTIONS['stream'];
  93.         $this->group $configuration['group'] ?? self::DEFAULT_OPTIONS['group'];
  94.         $this->consumer $configuration['consumer'] ?? self::DEFAULT_OPTIONS['consumer'];
  95.         $this->queue $this->stream.'__queue';
  96.         $this->autoSetup $configuration['auto_setup'] ?? self::DEFAULT_OPTIONS['auto_setup'];
  97.         $this->maxEntries $configuration['stream_max_entries'] ?? self::DEFAULT_OPTIONS['stream_max_entries'];
  98.         $this->deleteAfterAck $configuration['delete_after_ack'] ?? self::DEFAULT_OPTIONS['delete_after_ack'];
  99.         $this->deleteAfterReject $configuration['delete_after_reject'] ?? self::DEFAULT_OPTIONS['delete_after_reject'];
  100.         $this->redeliverTimeout = ($configuration['redeliver_timeout'] ?? self::DEFAULT_OPTIONS['redeliver_timeout']) * 1000;
  101.         $this->claimInterval = ($configuration['claim_interval'] ?? self::DEFAULT_OPTIONS['claim_interval']) / 1000;
  102.     }
  103.     /**
  104.      * @param string|string[]|null $auth
  105.      */
  106.     private static function initializeRedis(\Redis $redisstring $hostint $port$authint $serializerint $dbIndex): \Redis
  107.     {
  108.         if ($redis->isConnected()) {
  109.             return $redis;
  110.         }
  111.         @$redis->connect($host$port);
  112.         $error null;
  113.         set_error_handler(function ($type$msg) use (&$error) { $error $msg; });
  114.         try {
  115.             $isConnected $redis->isConnected();
  116.         } finally {
  117.             restore_error_handler();
  118.         }
  119.         if (!$isConnected) {
  120.             throw new InvalidArgumentException('Redis connection failed: '.(preg_match('/^Redis::p?connect\(\): (.*)/'$error ?? $redis->getLastError() ?? ''$matches) ? \sprintf(' (%s)'$matches[1]) : ''));
  121.         }
  122.         $redis->setOption(\Redis::OPT_SERIALIZER$serializer);
  123.         if (null !== $auth && !$redis->auth($auth)) {
  124.             throw new InvalidArgumentException('Redis connection failed: '.$redis->getLastError());
  125.         }
  126.         if ($dbIndex && !$redis->select($dbIndex)) {
  127.             throw new InvalidArgumentException('Redis connection failed: '.$redis->getLastError());
  128.         }
  129.         return $redis;
  130.     }
  131.     /**
  132.      * @param string|string[]|null $auth
  133.      */
  134.     private static function initializeRedisCluster(?\RedisCluster $redis, array $hosts$authint $serializer): \RedisCluster
  135.     {
  136.         if (null === $redis) {
  137.             $redis = new \RedisCluster(null$hosts0.00.0false$auth);
  138.         }
  139.         $redis->setOption(\Redis::OPT_SERIALIZER$serializer);
  140.         return $redis;
  141.     }
  142.     /**
  143.      * @param \Redis|\RedisCluster|null $redis
  144.      */
  145.     public static function fromDsn(string $dsn, array $redisOptions = [], $redis null): self
  146.     {
  147.         if (false === strpos($dsn',')) {
  148.             $params self::parseDsn($dsn$redisOptions);
  149.         } else {
  150.             $dsns explode(','$dsn);
  151.             $parsedUrls array_map(function ($dsn) use (&$redisOptions) {
  152.                 return self::parseDsn($dsn$redisOptions);
  153.             }, $dsns);
  154.             // Merge all the URLs, the last one overrides the previous ones
  155.             $params array_merge(...$parsedUrls);
  156.             // Regroup all the hosts in an array interpretable by RedisCluster
  157.             $params['host'] = array_map(function ($parsedUrl) {
  158.                 if (!isset($parsedUrl['host'])) {
  159.                     throw new InvalidArgumentException('Missing host in DSN, it must be defined when using Redis Cluster.');
  160.                 }
  161.                 return $parsedUrl['host'].':'.($parsedUrl['port'] ?? 6379);
  162.             }, $parsedUrls$dsns);
  163.         }
  164.         self::validateOptions($redisOptions);
  165.         $autoSetup null;
  166.         if (\array_key_exists('auto_setup'$redisOptions)) {
  167.             $autoSetup filter_var($redisOptions['auto_setup'], \FILTER_VALIDATE_BOOLEAN);
  168.             unset($redisOptions['auto_setup']);
  169.         }
  170.         $maxEntries null;
  171.         if (\array_key_exists('stream_max_entries'$redisOptions)) {
  172.             $maxEntries filter_var($redisOptions['stream_max_entries'], \FILTER_VALIDATE_INT);
  173.             unset($redisOptions['stream_max_entries']);
  174.         }
  175.         $deleteAfterAck null;
  176.         if (\array_key_exists('delete_after_ack'$redisOptions)) {
  177.             $deleteAfterAck filter_var($redisOptions['delete_after_ack'], \FILTER_VALIDATE_BOOLEAN);
  178.             unset($redisOptions['delete_after_ack']);
  179.         } else {
  180.             trigger_deprecation('symfony/redis-messenger''5.4''Not setting the "delete_after_ack" boolean option explicitly is deprecated, its default value will change to true in 6.0.');
  181.         }
  182.         $deleteAfterReject null;
  183.         if (\array_key_exists('delete_after_reject'$redisOptions)) {
  184.             $deleteAfterReject filter_var($redisOptions['delete_after_reject'], \FILTER_VALIDATE_BOOLEAN);
  185.             unset($redisOptions['delete_after_reject']);
  186.         }
  187.         $dbIndex null;
  188.         if (\array_key_exists('dbindex'$redisOptions)) {
  189.             $dbIndex filter_var($redisOptions['dbindex'], \FILTER_VALIDATE_INT);
  190.             unset($redisOptions['dbindex']);
  191.         }
  192.         $tls 'rediss' === $params['scheme'];
  193.         if (\array_key_exists('tls'$redisOptions)) {
  194.             trigger_deprecation('symfony/redis-messenger''5.3''Providing "tls" parameter is deprecated, use "rediss://" DSN scheme instead');
  195.             $tls filter_var($redisOptions['tls'], \FILTER_VALIDATE_BOOLEAN);
  196.             unset($redisOptions['tls']);
  197.         }
  198.         $redeliverTimeout null;
  199.         if (\array_key_exists('redeliver_timeout'$redisOptions)) {
  200.             $redeliverTimeout filter_var($redisOptions['redeliver_timeout'], \FILTER_VALIDATE_INT);
  201.             unset($redisOptions['redeliver_timeout']);
  202.         }
  203.         $claimInterval null;
  204.         if (\array_key_exists('claim_interval'$redisOptions)) {
  205.             $claimInterval filter_var($redisOptions['claim_interval'], \FILTER_VALIDATE_INT);
  206.             unset($redisOptions['claim_interval']);
  207.         }
  208.         $configuration = [
  209.             'stream' => $redisOptions['stream'] ?? null,
  210.             'group' => $redisOptions['group'] ?? null,
  211.             'consumer' => $redisOptions['consumer'] ?? null,
  212.             'lazy' => $redisOptions['lazy'] ?? self::DEFAULT_OPTIONS['lazy'],
  213.             'auto_setup' => $autoSetup,
  214.             'stream_max_entries' => $maxEntries,
  215.             'delete_after_ack' => $deleteAfterAck,
  216.             'delete_after_reject' => $deleteAfterReject,
  217.             'dbindex' => $dbIndex,
  218.             'redeliver_timeout' => $redeliverTimeout,
  219.             'claim_interval' => $claimInterval,
  220.         ];
  221.         if (isset($params['host'])) {
  222.             $user = isset($params['user']) && '' !== $params['user'] ? rawurldecode($params['user']) : null;
  223.             $pass = isset($params['pass']) && '' !== $params['pass'] ? rawurldecode($params['pass']) : null;
  224.             $connectionCredentials = [
  225.                 'host' => $params['host'],
  226.                 'port' => $params['port'] ?? 6379,
  227.                 // See: https://github.com/phpredis/phpredis/#auth
  228.                 'auth' => $redisOptions['auth'] ?? (null !== $pass && null !== $user ? [$user$pass] : ($pass ?? $user)),
  229.             ];
  230.             $pathParts explode('/'rtrim($params['path'] ?? '''/'));
  231.             $configuration['stream'] = $pathParts[1] ?? $configuration['stream'];
  232.             $configuration['group'] = $pathParts[2] ?? $configuration['group'];
  233.             $configuration['consumer'] = $pathParts[3] ?? $configuration['consumer'];
  234.             if ($tls) {
  235.                 $connectionCredentials['host'] = 'tls://'.$connectionCredentials['host'];
  236.             }
  237.         } else {
  238.             $connectionCredentials = [
  239.                 'host' => $params['path'],
  240.                 'port' => 0,
  241.             ];
  242.         }
  243.         return new self($configuration$connectionCredentials$redisOptions$redis);
  244.     }
  245.     private static function parseDsn(string $dsn, array &$redisOptions): array
  246.     {
  247.         $url $dsn;
  248.         $scheme === strpos($dsn'rediss:') ? 'rediss' 'redis';
  249.         if (preg_match('#^'.$scheme.':///([^:@])+$#'$dsn)) {
  250.             $url str_replace($scheme.':''file:'$dsn);
  251.         }
  252.         if (false === $params parse_url($url)) {
  253.             throw new InvalidArgumentException('The given Redis DSN is invalid.');
  254.         }
  255.         if (isset($params['query'])) {
  256.             parse_str($params['query'], $dsnOptions);
  257.             $redisOptions array_merge($redisOptions$dsnOptions);
  258.         }
  259.         return $params;
  260.     }
  261.     private static function validateOptions(array $options): void
  262.     {
  263.         $availableOptions array_keys(self::DEFAULT_OPTIONS);
  264.         if (< \count($invalidOptions array_diff(array_keys($options), $availableOptions))) {
  265.             trigger_deprecation('symfony/messenger''5.1''Invalid option(s) "%s" passed to the Redis Messenger transport. Passing invalid options is deprecated.'implode('", "'$invalidOptions));
  266.         }
  267.     }
  268.     private function claimOldPendingMessages()
  269.     {
  270.         try {
  271.             // This could soon be optimized with https://github.com/antirez/redis/issues/5212 or
  272.             // https://github.com/antirez/redis/issues/6256
  273.             $pendingMessages $this->connection->xpending($this->stream$this->group'-''+'1) ?: [];
  274.         } catch (\RedisException $e) {
  275.             throw new TransportException($e->getMessage(), 0$e);
  276.         }
  277.         $claimableIds = [];
  278.         foreach ($pendingMessages as $pendingMessage) {
  279.             if ($pendingMessage[1] === $this->consumer) {
  280.                 $this->couldHavePendingMessages true;
  281.                 return;
  282.             }
  283.             if ($pendingMessage[2] >= $this->redeliverTimeout) {
  284.                 $claimableIds[] = $pendingMessage[0];
  285.             }
  286.         }
  287.         if (\count($claimableIds) > 0) {
  288.             try {
  289.                 $this->connection->xclaim(
  290.                     $this->stream,
  291.                     $this->group,
  292.                     $this->consumer,
  293.                     $this->redeliverTimeout,
  294.                     $claimableIds,
  295.                     ['JUSTID']
  296.                 );
  297.                 $this->couldHavePendingMessages true;
  298.             } catch (\RedisException $e) {
  299.                 throw new TransportException($e->getMessage(), 0$e);
  300.             }
  301.         }
  302.         $this->nextClaim microtime(true) + $this->claimInterval;
  303.     }
  304.     public function get(): ?array
  305.     {
  306.         if ($this->autoSetup) {
  307.             $this->setup();
  308.         }
  309.         $now microtime();
  310.         $now substr($now11).substr($now23);
  311.         $queuedMessageCount $this->rawCommand('ZCOUNT'0$now) ?? 0;
  312.         while ($queuedMessageCount--) {
  313.             if (!$message $this->rawCommand('ZPOPMIN'1)) {
  314.                 break;
  315.             }
  316.             [$queuedMessage$expiry] = $message;
  317.             if (\strlen($expiry) === \strlen($now) ? $expiry $now : \strlen($expiry) < \strlen($now)) {
  318.                 // if a future-placed message is popped because of a race condition with
  319.                 // another running consumer, the message is readded to the queue
  320.                 if (!$this->rawCommand('ZADD''NX'$expiry$queuedMessage)) {
  321.                     throw new TransportException('Could not add a message to the redis stream.');
  322.                 }
  323.                 break;
  324.             }
  325.             $decodedQueuedMessage json_decode($queuedMessagetrue);
  326.             $this->add(\array_key_exists('body'$decodedQueuedMessage) ? $decodedQueuedMessage['body'] : $queuedMessage$decodedQueuedMessage['headers'] ?? [], 0);
  327.         }
  328.         if (!$this->couldHavePendingMessages && $this->nextClaim <= microtime(true)) {
  329.             $this->claimOldPendingMessages();
  330.         }
  331.         $messageId '>'// will receive new messages
  332.         if ($this->couldHavePendingMessages) {
  333.             $messageId '0'// will receive consumers pending messages
  334.         }
  335.         try {
  336.             $messages $this->connection->xreadgroup(
  337.                 $this->group,
  338.                 $this->consumer,
  339.                 [$this->stream => $messageId],
  340.                 1,
  341.                 1
  342.             );
  343.         } catch (\RedisException $e) {
  344.             throw new TransportException($e->getMessage(), 0$e);
  345.         }
  346.         if (false === $messages) {
  347.             if ($error $this->connection->getLastError() ?: null) {
  348.                 $this->connection->clearLastError();
  349.             }
  350.             throw new TransportException($error ?? 'Could not read messages from the redis stream.');
  351.         }
  352.         if ($this->couldHavePendingMessages && empty($messages[$this->stream])) {
  353.             $this->couldHavePendingMessages false;
  354.             // No pending messages so get a new one
  355.             return $this->get();
  356.         }
  357.         foreach ($messages[$this->stream] ?? [] as $key => $message) {
  358.             return [
  359.                 'id' => $key,
  360.                 'data' => $message,
  361.             ];
  362.         }
  363.         return null;
  364.     }
  365.     public function ack(string $id): void
  366.     {
  367.         try {
  368.             $acknowledged $this->connection->xack($this->stream$this->group, [$id]);
  369.             if ($this->deleteAfterAck) {
  370.                 $acknowledged $this->connection->xdel($this->stream, [$id]);
  371.             }
  372.         } catch (\RedisException $e) {
  373.             throw new TransportException($e->getMessage(), 0$e);
  374.         }
  375.         if (!$acknowledged) {
  376.             if ($error $this->connection->getLastError() ?: null) {
  377.                 $this->connection->clearLastError();
  378.             }
  379.             throw new TransportException($error ?? sprintf('Could not acknowledge redis message "%s".'$id));
  380.         }
  381.     }
  382.     public function reject(string $id): void
  383.     {
  384.         try {
  385.             $deleted $this->connection->xack($this->stream$this->group, [$id]);
  386.             if ($this->deleteAfterReject) {
  387.                 $deleted $this->connection->xdel($this->stream, [$id]) && $deleted;
  388.             }
  389.         } catch (\RedisException $e) {
  390.             throw new TransportException($e->getMessage(), 0$e);
  391.         }
  392.         if (!$deleted) {
  393.             if ($error $this->connection->getLastError() ?: null) {
  394.                 $this->connection->clearLastError();
  395.             }
  396.             throw new TransportException($error ?? sprintf('Could not delete message "%s" from the redis stream.'$id));
  397.         }
  398.     }
  399.     public function add(string $body, array $headersint $delayInMs 0): void
  400.     {
  401.         if ($this->autoSetup) {
  402.             $this->setup();
  403.         }
  404.         try {
  405.             if ($delayInMs 0) { // the delay is <= 0 for queued messages
  406.                 $message json_encode([
  407.                     'body' => $body,
  408.                     'headers' => $headers,
  409.                     // Entry need to be unique in the sorted set else it would only be added once to the delayed messages queue
  410.                     'uniqid' => uniqid(''true),
  411.                 ]);
  412.                 if (false === $message) {
  413.                     throw new TransportException(json_last_error_msg());
  414.                 }
  415.                 $now explode(' 'microtime(), 2);
  416.                 $now[0] = str_pad($delayInMs substr($now[0], 23), 3'0', \STR_PAD_LEFT);
  417.                 if (< \strlen($now[0])) {
  418.                     $now[1] += substr($now[0], 0, -3);
  419.                     $now[0] = substr($now[0], -3);
  420.                     if (\is_float($now[1])) {
  421.                         throw new TransportException("Message delay is too big: {$delayInMs}ms.");
  422.                     }
  423.                 }
  424.                 $added $this->rawCommand('ZADD''NX'$now[1].$now[0], $message);
  425.             } else {
  426.                 $message json_encode([
  427.                     'body' => $body,
  428.                     'headers' => $headers,
  429.                 ]);
  430.                 if (false === $message) {
  431.                     throw new TransportException(json_last_error_msg());
  432.                 }
  433.                 if ($this->maxEntries) {
  434.                     $added $this->connection->xadd($this->stream'*', ['message' => $message], $this->maxEntriestrue);
  435.                 } else {
  436.                     $added $this->connection->xadd($this->stream'*', ['message' => $message]);
  437.                 }
  438.             }
  439.         } catch (\RedisException $e) {
  440.             if ($error $this->connection->getLastError() ?: null) {
  441.                 $this->connection->clearLastError();
  442.             }
  443.             throw new TransportException($error ?? $e->getMessage(), 0$e);
  444.         }
  445.         if (!$added) {
  446.             if ($error $this->connection->getLastError() ?: null) {
  447.                 $this->connection->clearLastError();
  448.             }
  449.             throw new TransportException($error ?? 'Could not add a message to the redis stream.');
  450.         }
  451.     }
  452.     public function setup(): void
  453.     {
  454.         try {
  455.             $this->connection->xgroup('CREATE'$this->stream$this->group0true);
  456.         } catch (\RedisException $e) {
  457.             throw new TransportException($e->getMessage(), 0$e);
  458.         }
  459.         // group might already exist, ignore
  460.         if ($this->connection->getLastError()) {
  461.             $this->connection->clearLastError();
  462.         }
  463.         if ($this->deleteAfterAck || $this->deleteAfterReject) {
  464.             $groups $this->connection->xinfo('GROUPS'$this->stream);
  465.             if (
  466.                 // support for Redis extension version 5+
  467.                 (\is_array($groups) && < \count($groups))
  468.                 // support for Redis extension version 4.x
  469.                 || (\is_string($groups) && substr_count($groups'"name"'))
  470.             ) {
  471.                 throw new LogicException(sprintf('More than one group exists for stream "%s", delete_after_ack and delete_after_reject cannot be enabled as it risks deleting messages before all groups could consume them.'$this->stream));
  472.             }
  473.         }
  474.         $this->autoSetup false;
  475.     }
  476.     private function getCurrentTimeInMilliseconds(): int
  477.     {
  478.         return (int) (microtime(true) * 1000);
  479.     }
  480.     public function cleanup(): void
  481.     {
  482.         static $unlink true;
  483.         if ($unlink) {
  484.             try {
  485.                 $unlink false !== $this->connection->unlink($this->stream$this->queue);
  486.             } catch (\Throwable $e) {
  487.                 $unlink false;
  488.             }
  489.         }
  490.         if (!$unlink) {
  491.             $this->connection->del($this->stream$this->queue);
  492.         }
  493.     }
  494.     /**
  495.      * @return mixed
  496.      */
  497.     private function rawCommand(string $command, ...$arguments)
  498.     {
  499.         try {
  500.             if ($this->connection instanceof \RedisCluster || $this->connection instanceof RedisClusterProxy) {
  501.                 $result $this->connection->rawCommand($this->queue$command$this->queue, ...$arguments);
  502.             } else {
  503.                 $result $this->connection->rawCommand($command$this->queue, ...$arguments);
  504.             }
  505.         } catch (\RedisException $e) {
  506.             throw new TransportException($e->getMessage(), 0$e);
  507.         }
  508.         if (false === $result) {
  509.             if ($error $this->connection->getLastError() ?: null) {
  510.                 $this->connection->clearLastError();
  511.             }
  512.             throw new TransportException($error ?? sprintf('Could not run "%s" on Redis queue.'$command));
  513.         }
  514.         return $result;
  515.     }
  516. }
  517. if (!class_exists(\Symfony\Component\Messenger\Transport\RedisExt\Connection::class, false)) {
  518.     class_alias(Connection::class, \Symfony\Component\Messenger\Transport\RedisExt\Connection::class);
  519. }