vendor/symfony/cache/Traits/RedisTrait.php line 354

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\Cache\Traits;
  11. use Predis\Connection\Aggregate\ClusterInterface;
  12. use Predis\Connection\Aggregate\RedisCluster;
  13. use Predis\Connection\Aggregate\ReplicationInterface;
  14. use Predis\Response\ErrorInterface;
  15. use Predis\Response\Status;
  16. use Symfony\Component\Cache\Exception\CacheException;
  17. use Symfony\Component\Cache\Exception\InvalidArgumentException;
  18. use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
  19. use Symfony\Component\Cache\Marshaller\MarshallerInterface;
  20. /**
  21.  * @author Aurimas Niekis <aurimas@niekis.lt>
  22.  * @author Nicolas Grekas <p@tchwork.com>
  23.  *
  24.  * @internal
  25.  */
  26. trait RedisTrait
  27. {
  28.     private static $defaultConnectionOptions = [
  29.         'class' => null,
  30.         'persistent' => 0,
  31.         'persistent_id' => null,
  32.         'timeout' => 30,
  33.         'read_timeout' => 0,
  34.         'retry_interval' => 0,
  35.         'tcp_keepalive' => 0,
  36.         'lazy' => null,
  37.         'redis_cluster' => false,
  38.         'redis_sentinel' => null,
  39.         'dbindex' => 0,
  40.         'failover' => 'none',
  41.         'ssl' => null// see https://php.net/context.ssl
  42.     ];
  43.     private $redis;
  44.     private $marshaller;
  45.     /**
  46.      * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy|RedisClusterProxy $redis
  47.      */
  48.     private function init($redisstring $namespaceint $defaultLifetime, ?MarshallerInterface $marshaller)
  49.     {
  50.         parent::__construct($namespace$defaultLifetime);
  51.         if (preg_match('#[^-+_.A-Za-z0-9]#'$namespace$match)) {
  52.             throw new InvalidArgumentException(sprintf('RedisAdapter namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed.'$match[0]));
  53.         }
  54.         if (!$redis instanceof \Redis && !$redis instanceof \RedisArray && !$redis instanceof \RedisCluster && !$redis instanceof \Predis\ClientInterface && !$redis instanceof RedisProxy && !$redis instanceof RedisClusterProxy) {
  55.             throw new InvalidArgumentException(sprintf('"%s()" expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\ClientInterface, "%s" given.'__METHOD__, \is_object($redis) ? \get_class($redis) : \gettype($redis)));
  56.         }
  57.         if ($redis instanceof \Predis\ClientInterface && $redis->getOptions()->exceptions) {
  58.             $options = clone $redis->getOptions();
  59.             \Closure::bind(function () { $this->options['exceptions'] = false; }, $options$options)();
  60.             $redis = new $redis($redis->getConnection(), $options);
  61.         }
  62.         $this->redis $redis;
  63.         $this->marshaller $marshaller ?? new DefaultMarshaller();
  64.     }
  65.     /**
  66.      * Creates a Redis connection using a DSN configuration.
  67.      *
  68.      * Example DSN:
  69.      *   - redis://localhost
  70.      *   - redis://example.com:1234
  71.      *   - redis://secret@example.com/13
  72.      *   - redis:///var/run/redis.sock
  73.      *   - redis://secret@/var/run/redis.sock/13
  74.      *
  75.      * @param string $dsn
  76.      * @param array  $options See self::$defaultConnectionOptions
  77.      *
  78.      * @throws InvalidArgumentException when the DSN is invalid
  79.      *
  80.      * @return \Redis|\RedisCluster|RedisClusterProxy|RedisProxy|\Predis\ClientInterface According to the "class" option
  81.      */
  82.     public static function createConnection($dsn, array $options = [])
  83.     {
  84.         if (str_starts_with($dsn'redis:')) {
  85.             $scheme 'redis';
  86.         } elseif (str_starts_with($dsn'rediss:')) {
  87.             $scheme 'rediss';
  88.         } else {
  89.             throw new InvalidArgumentException(sprintf('Invalid Redis DSN: "%s" does not start with "redis:" or "rediss".'$dsn));
  90.         }
  91.         if (!\extension_loaded('redis') && !class_exists(\Predis\Client::class)) {
  92.             throw new CacheException(sprintf('Cannot find the "redis" extension nor the "predis/predis" package: "%s".'$dsn));
  93.         }
  94.         $params preg_replace_callback('#^'.$scheme.':(//)?(?:(?:[^:@]*+:)?([^@]*+)@)?#', function ($m) use (&$auth) {
  95.             if (isset($m[2])) {
  96.                 $auth $m[2];
  97.                 if ('' === $auth) {
  98.                     $auth null;
  99.                 }
  100.             }
  101.             return 'file:'.($m[1] ?? '');
  102.         }, $dsn);
  103.         if (false === $params parse_url($params)) {
  104.             throw new InvalidArgumentException(sprintf('Invalid Redis DSN: "%s".'$dsn));
  105.         }
  106.         $query $hosts = [];
  107.         $tls 'rediss' === $scheme;
  108.         $tcpScheme $tls 'tls' 'tcp';
  109.         if (isset($params['query'])) {
  110.             parse_str($params['query'], $query);
  111.             if (isset($query['host'])) {
  112.                 if (!\is_array($hosts $query['host'])) {
  113.                     throw new InvalidArgumentException(sprintf('Invalid Redis DSN: "%s".'$dsn));
  114.                 }
  115.                 foreach ($hosts as $host => $parameters) {
  116.                     if (\is_string($parameters)) {
  117.                         parse_str($parameters$parameters);
  118.                     }
  119.                     if (false === $i strrpos($host':')) {
  120.                         $hosts[$host] = ['scheme' => $tcpScheme'host' => $host'port' => 6379] + $parameters;
  121.                     } elseif ($port = (int) substr($host$i)) {
  122.                         $hosts[$host] = ['scheme' => $tcpScheme'host' => substr($host0$i), 'port' => $port] + $parameters;
  123.                     } else {
  124.                         $hosts[$host] = ['scheme' => 'unix''path' => substr($host0$i)] + $parameters;
  125.                     }
  126.                 }
  127.                 $hosts array_values($hosts);
  128.             }
  129.         }
  130.         if (isset($params['host']) || isset($params['path'])) {
  131.             if (!isset($params['dbindex']) && isset($params['path'])) {
  132.                 if (preg_match('#/(\d+)$#'$params['path'], $m)) {
  133.                     $params['dbindex'] = $m[1];
  134.                     $params['path'] = substr($params['path'], 0, -\strlen($m[0]));
  135.                 } elseif (isset($params['host'])) {
  136.                     throw new InvalidArgumentException(sprintf('Invalid Redis DSN: "%s", the "dbindex" parameter must be a number.'$dsn));
  137.                 }
  138.             }
  139.             if (isset($params['host'])) {
  140.                 array_unshift($hosts, ['scheme' => $tcpScheme'host' => $params['host'], 'port' => $params['port'] ?? 6379]);
  141.             } else {
  142.                 array_unshift($hosts, ['scheme' => 'unix''path' => $params['path']]);
  143.             }
  144.         }
  145.         if (!$hosts) {
  146.             throw new InvalidArgumentException(sprintf('Invalid Redis DSN: "%s".'$dsn));
  147.         }
  148.         $params += $query $options self::$defaultConnectionOptions;
  149.         if (isset($params['redis_sentinel']) && !class_exists(\Predis\Client::class)) {
  150.             throw new CacheException(sprintf('Redis Sentinel support requires the "predis/predis" package: "%s".'$dsn));
  151.         }
  152.         if (null === $params['class'] && !isset($params['redis_sentinel']) && \extension_loaded('redis')) {
  153.             $class $params['redis_cluster'] ? \RedisCluster::class : (< \count($hosts) ? \RedisArray::class : \Redis::class);
  154.         } else {
  155.             $class $params['class'] ?? \Predis\Client::class;
  156.             if (isset($params['redis_sentinel']) && !is_a($class, \Predis\Client::class, true)) {
  157.                 throw new CacheException(sprintf('Cannot use Redis Sentinel: class "%s" does not extend "Predis\Client": "%s".'$class$dsn));
  158.             }
  159.         }
  160.         if (is_a($class, \Redis::class, true)) {
  161.             $connect $params['persistent'] || $params['persistent_id'] ? 'pconnect' 'connect';
  162.             $redis = new $class();
  163.             $initializer = static function ($redis) use ($connect$params$dsn$auth$hosts$tls) {
  164.                 $host $hosts[0]['host'] ?? $hosts[0]['path'];
  165.                 $port $hosts[0]['port'] ?? 0;
  166.                 if (isset($hosts[0]['host']) && $tls) {
  167.                     $host 'tls://'.$host;
  168.                 }
  169.                 try {
  170.                     @$redis->{$connect}($host$port$params['timeout'], (string) $params['persistent_id'], $params['retry_interval'], $params['read_timeout'], ...\defined('Redis::SCAN_PREFIX') ? [['stream' => $params['ssl'] ?? null]] : []);
  171.                     set_error_handler(function ($type$msg) use (&$error) { $error $msg; });
  172.                     $isConnected $redis->isConnected();
  173.                     restore_error_handler();
  174.                     if (!$isConnected) {
  175.                         $error preg_match('/^Redis::p?connect\(\): (.*)/'$error$error) ? sprintf(' (%s)'$error[1]) : '';
  176.                         throw new InvalidArgumentException(sprintf('Redis connection "%s" failed: '$dsn).$error.'.');
  177.                     }
  178.                     if ((null !== $auth && !$redis->auth($auth))
  179.                         || ($params['dbindex'] && !$redis->select($params['dbindex']))
  180.                     ) {
  181.                         $e preg_replace('/^ERR /'''$redis->getLastError());
  182.                         throw new InvalidArgumentException(sprintf('Redis connection "%s" failed: '$dsn).$e.'.');
  183.                     }
  184.                     if ($params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) {
  185.                         $redis->setOption(\Redis::OPT_TCP_KEEPALIVE$params['tcp_keepalive']);
  186.                     }
  187.                 } catch (\RedisException $e) {
  188.                     throw new InvalidArgumentException(sprintf('Redis connection "%s" failed: '$dsn).$e->getMessage());
  189.                 }
  190.                 return true;
  191.             };
  192.             if ($params['lazy']) {
  193.                 $redis = new RedisProxy($redis$initializer);
  194.             } else {
  195.                 $initializer($redis);
  196.             }
  197.         } elseif (is_a($class, \RedisArray::class, true)) {
  198.             foreach ($hosts as $i => $host) {
  199.                 switch ($host['scheme']) {
  200.                     case 'tcp'$hosts[$i] = $host['host'].':'.$host['port']; break;
  201.                     case 'tls'$hosts[$i] = 'tls://'.$host['host'].':'.$host['port']; break;
  202.                     default: $hosts[$i] = $host['path'];
  203.                 }
  204.             }
  205.             $params['lazy_connect'] = $params['lazy'] ?? true;
  206.             $params['connect_timeout'] = $params['timeout'];
  207.             try {
  208.                 $redis = new $class($hosts$params);
  209.             } catch (\RedisClusterException $e) {
  210.                 throw new InvalidArgumentException(sprintf('Redis connection "%s" failed: '$dsn).$e->getMessage());
  211.             }
  212.             if ($params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) {
  213.                 $redis->setOption(\Redis::OPT_TCP_KEEPALIVE$params['tcp_keepalive']);
  214.             }
  215.         } elseif (is_a($class, \RedisCluster::class, true)) {
  216.             $initializer = static function () use ($class$params$dsn$hosts) {
  217.                 foreach ($hosts as $i => $host) {
  218.                     switch ($host['scheme']) {
  219.                         case 'tcp'$hosts[$i] = $host['host'].':'.$host['port']; break;
  220.                         case 'tls'$hosts[$i] = 'tls://'.$host['host'].':'.$host['port']; break;
  221.                         default: $hosts[$i] = $host['path'];
  222.                     }
  223.                 }
  224.                 try {
  225.                     $redis = new $class(null$hosts$params['timeout'], $params['read_timeout'], (bool) $params['persistent'], $params['auth'] ?? '', ...\defined('Redis::SCAN_PREFIX') ? [$params['ssl'] ?? null] : []);
  226.                 } catch (\RedisClusterException $e) {
  227.                     throw new InvalidArgumentException(sprintf('Redis connection "%s" failed: '$dsn).$e->getMessage());
  228.                 }
  229.                 if ($params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) {
  230.                     $redis->setOption(\Redis::OPT_TCP_KEEPALIVE$params['tcp_keepalive']);
  231.                 }
  232.                 switch ($params['failover']) {
  233.                     case 'error'$redis->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, \RedisCluster::FAILOVER_ERROR); break;
  234.                     case 'distribute'$redis->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, \RedisCluster::FAILOVER_DISTRIBUTE); break;
  235.                     case 'slaves'$redis->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, \RedisCluster::FAILOVER_DISTRIBUTE_SLAVES); break;
  236.                 }
  237.                 return $redis;
  238.             };
  239.             $redis $params['lazy'] ? new RedisClusterProxy($initializer) : $initializer();
  240.         } elseif (is_a($class, \Predis\ClientInterface::class, true)) {
  241.             if ($params['redis_cluster']) {
  242.                 $params['cluster'] = 'redis';
  243.                 if (isset($params['redis_sentinel'])) {
  244.                     throw new InvalidArgumentException(sprintf('Cannot use both "redis_cluster" and "redis_sentinel" at the same time: "%s".'$dsn));
  245.                 }
  246.             } elseif (isset($params['redis_sentinel'])) {
  247.                 $params['replication'] = 'sentinel';
  248.                 $params['service'] = $params['redis_sentinel'];
  249.             }
  250.             $params += ['parameters' => []];
  251.             $params['parameters'] += [
  252.                 'persistent' => $params['persistent'],
  253.                 'timeout' => $params['timeout'],
  254.                 'read_write_timeout' => $params['read_timeout'],
  255.                 'tcp_nodelay' => true,
  256.             ];
  257.             if ($params['dbindex']) {
  258.                 $params['parameters']['database'] = $params['dbindex'];
  259.             }
  260.             if (null !== $auth) {
  261.                 $params['parameters']['password'] = $auth;
  262.             }
  263.             if (=== \count($hosts) && !($params['redis_cluster'] || $params['redis_sentinel'])) {
  264.                 $hosts $hosts[0];
  265.             } elseif (\in_array($params['failover'], ['slaves''distribute'], true) && !isset($params['replication'])) {
  266.                 $params['replication'] = true;
  267.                 $hosts[0] += ['alias' => 'master'];
  268.             }
  269.             $params['exceptions'] = false;
  270.             $redis = new $class($hostsarray_diff_key($paramsarray_diff_key(self::$defaultConnectionOptions, ['ssl' => null])));
  271.             if (isset($params['redis_sentinel'])) {
  272.                 $redis->getConnection()->setSentinelTimeout($params['timeout']);
  273.             }
  274.         } elseif (class_exists($classfalse)) {
  275.             throw new InvalidArgumentException(sprintf('"%s" is not a subclass of "Redis", "RedisArray", "RedisCluster" nor "Predis\ClientInterface".'$class));
  276.         } else {
  277.             throw new InvalidArgumentException(sprintf('Class "%s" does not exist.'$class));
  278.         }
  279.         return $redis;
  280.     }
  281.     /**
  282.      * {@inheritdoc}
  283.      */
  284.     protected function doFetch(array $ids)
  285.     {
  286.         if (!$ids) {
  287.             return [];
  288.         }
  289.         $result = [];
  290.         if ($this->redis instanceof \Predis\ClientInterface && $this->redis->getConnection() instanceof ClusterInterface) {
  291.             $values $this->pipeline(function () use ($ids) {
  292.                 foreach ($ids as $id) {
  293.                     yield 'get' => [$id];
  294.                 }
  295.             });
  296.         } else {
  297.             $values $this->redis->mget($ids);
  298.             if (!\is_array($values) || \count($values) !== \count($ids)) {
  299.                 return [];
  300.             }
  301.             $values array_combine($ids$values);
  302.         }
  303.         foreach ($values as $id => $v) {
  304.             if ($v) {
  305.                 $result[$id] = $this->marshaller->unmarshall($v);
  306.             }
  307.         }
  308.         return $result;
  309.     }
  310.     /**
  311.      * {@inheritdoc}
  312.      */
  313.     protected function doHave($id)
  314.     {
  315.         return (bool) $this->redis->exists($id);
  316.     }
  317.     /**
  318.      * {@inheritdoc}
  319.      */
  320.     protected function doClear($namespace)
  321.     {
  322.         if ($this->redis instanceof \Predis\ClientInterface) {
  323.             $prefix $this->redis->getOptions()->prefix $this->redis->getOptions()->prefix->getPrefix() : '';
  324.             $prefixLen = \strlen($prefix ?? '');
  325.         }
  326.         $cleared true;
  327.         $hosts $this->getHosts();
  328.         $host reset($hosts);
  329.         if ($host instanceof \Predis\Client && $host->getConnection() instanceof ReplicationInterface) {
  330.             // Predis supports info command only on the master in replication environments
  331.             $hosts = [$host->getClientFor('master')];
  332.         }
  333.         foreach ($hosts as $host) {
  334.             if (!isset($namespace[0])) {
  335.                 $cleared $host->flushDb() && $cleared;
  336.                 continue;
  337.             }
  338.             $info $host->info('Server');
  339.             $info = !$info instanceof ErrorInterface $info['Server'] ?? $info : ['redis_version' => '2.0'];
  340.             if (!$host instanceof \Predis\ClientInterface) {
  341.                 $prefix = \defined('Redis::SCAN_PREFIX') && (\Redis::SCAN_PREFIX $host->getOption(\Redis::OPT_SCAN)) ? '' $host->getOption(\Redis::OPT_PREFIX);
  342.                 $prefixLen = \strlen($host->getOption(\Redis::OPT_PREFIX) ?? '');
  343.             }
  344.             $pattern $prefix.$namespace.'*';
  345.             if (!version_compare($info['redis_version'], '2.8''>=')) {
  346.                 // As documented in Redis documentation (http://redis.io/commands/keys) using KEYS
  347.                 // can hang your server when it is executed against large databases (millions of items).
  348.                 // Whenever you hit this scale, you should really consider upgrading to Redis 2.8 or above.
  349.                 $args $this->redis instanceof \Predis\ClientInterface ? [0$pattern] : [[$pattern], 0];
  350.                 $cleared $host->eval("local keys=redis.call('KEYS',ARGV[1]) for i=1,#keys,5000 do redis.call('DEL',unpack(keys,i,math.min(i+4999,#keys))) end return 1"$args[0], $args[1]) && $cleared;
  351.                 continue;
  352.             }
  353.             $cursor null;
  354.             do {
  355.                 $keys $host instanceof \Predis\ClientInterface $host->scan($cursor'MATCH'$pattern'COUNT'1000) : $host->scan($cursor$pattern1000);
  356.                 if (isset($keys[1]) && \is_array($keys[1])) {
  357.                     $cursor $keys[0];
  358.                     $keys $keys[1];
  359.                 }
  360.                 if ($keys) {
  361.                     if ($prefixLen) {
  362.                         foreach ($keys as $i => $key) {
  363.                             $keys[$i] = substr($key$prefixLen);
  364.                         }
  365.                     }
  366.                     $this->doDelete($keys);
  367.                 }
  368.             } while ($cursor = (int) $cursor);
  369.         }
  370.         return $cleared;
  371.     }
  372.     /**
  373.      * {@inheritdoc}
  374.      */
  375.     protected function doDelete(array $ids)
  376.     {
  377.         if (!$ids) {
  378.             return true;
  379.         }
  380.         if ($this->redis instanceof \Predis\ClientInterface && $this->redis->getConnection() instanceof ClusterInterface) {
  381.             $this->pipeline(function () use ($ids) {
  382.                 foreach ($ids as $id) {
  383.                     yield 'del' => [$id];
  384.                 }
  385.             })->rewind();
  386.         } else {
  387.             $this->redis->del($ids);
  388.         }
  389.         return true;
  390.     }
  391.     /**
  392.      * {@inheritdoc}
  393.      */
  394.     protected function doSave(array $valuesint $lifetime)
  395.     {
  396.         if (!$values $this->marshaller->marshall($values$failed)) {
  397.             return $failed;
  398.         }
  399.         $results $this->pipeline(function () use ($values$lifetime) {
  400.             foreach ($values as $id => $value) {
  401.                 if (>= $lifetime) {
  402.                     yield 'set' => [$id$value];
  403.                 } else {
  404.                     yield 'setEx' => [$id$lifetime$value];
  405.                 }
  406.             }
  407.         });
  408.         foreach ($results as $id => $result) {
  409.             if (true !== $result && (!$result instanceof Status || Status::get('OK') !== $result)) {
  410.                 $failed[] = $id;
  411.             }
  412.         }
  413.         return $failed;
  414.     }
  415.     private function pipeline(\Closure $generator$redis null): \Generator
  416.     {
  417.         $ids = [];
  418.         $redis $redis ?? $this->redis;
  419.         if ($redis instanceof RedisClusterProxy || $redis instanceof \RedisCluster || ($redis instanceof \Predis\ClientInterface && $redis->getConnection() instanceof RedisCluster)) {
  420.             // phpredis & predis don't support pipelining with RedisCluster
  421.             // see https://github.com/phpredis/phpredis/blob/develop/cluster.markdown#pipelining
  422.             // see https://github.com/nrk/predis/issues/267#issuecomment-123781423
  423.             $results = [];
  424.             foreach ($generator() as $command => $args) {
  425.                 $results[] = $redis->{$command}(...$args);
  426.                 $ids[] = 'eval' === $command ? ($redis instanceof \Predis\ClientInterface $args[2] : $args[1][0]) : $args[0];
  427.             }
  428.         } elseif ($redis instanceof \Predis\ClientInterface) {
  429.             $results $redis->pipeline(static function ($redis) use ($generator, &$ids) {
  430.                 foreach ($generator() as $command => $args) {
  431.                     $redis->{$command}(...$args);
  432.                     $ids[] = 'eval' === $command $args[2] : $args[0];
  433.                 }
  434.             });
  435.         } elseif ($redis instanceof \RedisArray) {
  436.             $connections $results $ids = [];
  437.             foreach ($generator() as $command => $args) {
  438.                 $id 'eval' === $command $args[1][0] : $args[0];
  439.                 if (!isset($connections[$h $redis->_target($id)])) {
  440.                     $connections[$h] = [$redis->_instance($h), -1];
  441.                     $connections[$h][0]->multi(\Redis::PIPELINE);
  442.                 }
  443.                 $connections[$h][0]->{$command}(...$args);
  444.                 $results[] = [$h, ++$connections[$h][1]];
  445.                 $ids[] = $id;
  446.             }
  447.             foreach ($connections as $h => $c) {
  448.                 $connections[$h] = $c[0]->exec();
  449.             }
  450.             foreach ($results as $k => [$h$c]) {
  451.                 $results[$k] = $connections[$h][$c];
  452.             }
  453.         } else {
  454.             $redis->multi(\Redis::PIPELINE);
  455.             foreach ($generator() as $command => $args) {
  456.                 $redis->{$command}(...$args);
  457.                 $ids[] = 'eval' === $command $args[1][0] : $args[0];
  458.             }
  459.             $results $redis->exec();
  460.         }
  461.         if (!$redis instanceof \Predis\ClientInterface && 'eval' === $command && $redis->getLastError()) {
  462.             $e = new \RedisException($redis->getLastError());
  463.             $results array_map(function ($v) use ($e) { return false === $v $e $v; }, $results);
  464.         }
  465.         foreach ($ids as $k => $id) {
  466.             yield $id => $results[$k];
  467.         }
  468.     }
  469.     private function getHosts(): array
  470.     {
  471.         $hosts = [$this->redis];
  472.         if ($this->redis instanceof \Predis\ClientInterface) {
  473.             $connection $this->redis->getConnection();
  474.             if ($connection instanceof ClusterInterface && $connection instanceof \Traversable) {
  475.                 $hosts = [];
  476.                 foreach ($connection as $c) {
  477.                     $hosts[] = new \Predis\Client($c);
  478.                 }
  479.             }
  480.         } elseif ($this->redis instanceof \RedisArray) {
  481.             $hosts = [];
  482.             foreach ($this->redis->_hosts() as $host) {
  483.                 $hosts[] = $this->redis->_instance($host);
  484.             }
  485.         } elseif ($this->redis instanceof RedisClusterProxy || $this->redis instanceof \RedisCluster) {
  486.             $hosts = [];
  487.             foreach ($this->redis->_masters() as $host) {
  488.                 $hosts[] = new RedisClusterNodeProxy($host$this->redis);
  489.             }
  490.         }
  491.         return $hosts;
  492.     }
  493. }