From 8e0605ac1878433bcbbebe7f9bc0236dce9dbee9 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 25 Sep 2018 18:01:05 +0200 Subject: [PATCH] [Cache] support configuring multiple Memcached servers in one DSN --- .../Compiler/CachePoolPass.php | 2 +- .../Cache/Adapter/AbstractAdapter.php | 2 +- src/Symfony/Component/Cache/CHANGELOG.md | 1 + .../Tests/Adapter/MemcachedAdapterTest.php | 42 ++++++++++++++++++ .../Component/Cache/Traits/MemcachedTrait.php | 43 +++++++++++++++---- 5 files changed, 80 insertions(+), 10 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolPass.php index 583c3b80ea34..5ba6615f0717 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolPass.php @@ -133,7 +133,7 @@ public static function getServiceProvider(ContainerBuilder $container, $name) { $container->resolveEnvPlaceholders($name, null, $usedEnvs); - if ($usedEnvs || preg_match('#^[a-z]++://#', $name)) { + if ($usedEnvs || preg_match('#^[a-z]++:#', $name)) { $dsn = $name; if (!$container->hasDefinition($name = '.cache_connection.'.ContainerBuilder::hash($dsn))) { diff --git a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php index c7ee3bcddfb2..8c755e306690 100644 --- a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php @@ -151,7 +151,7 @@ public static function createConnection($dsn, array $options = array()) if (0 === strpos($dsn, 'redis://')) { return RedisAdapter::createConnection($dsn, $options); } - if (0 === strpos($dsn, 'memcached://')) { + if (0 === strpos($dsn, 'memcached:')) { return MemcachedAdapter::createConnection($dsn, $options); } diff --git a/src/Symfony/Component/Cache/CHANGELOG.md b/src/Symfony/Component/Cache/CHANGELOG.md index 846b5308ee74..105f14643a21 100644 --- a/src/Symfony/Component/Cache/CHANGELOG.md +++ b/src/Symfony/Component/Cache/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 4.2.0 ----- + * added support for configuring multiple Memcached servers in one DSN * added `MarshallerInterface` and `DefaultMarshaller` to allow changing the serializer and provide one that automatically uses igbinary when available * added `CacheInterface`, which provides stampede protection via probabilistic early expiration and should become the preferred way to use a cache * added sub-second expiry accuracy for backends that support it diff --git a/src/Symfony/Component/Cache/Tests/Adapter/MemcachedAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/MemcachedAdapterTest.php index d1f87903406f..46e9f4e5d5c7 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/MemcachedAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/MemcachedAdapterTest.php @@ -192,4 +192,46 @@ public function provideDsnWithOptions() array(\Memcached::OPT_SOCKET_RECV_SIZE => 1, \Memcached::OPT_SOCKET_SEND_SIZE => 2, \Memcached::OPT_RETRY_TIMEOUT => 8), ); } + + public function testMultiServerDsn() + { + $dsn = 'memcached:?host[localhost]&host[localhost:12345]&host[/some/memcached.sock:]=3'; + $client = MemcachedAdapter::createConnection($dsn); + + $expected = array( + 0 => array( + 'host' => 'localhost', + 'port' => 11211, + 'type' => 'TCP', + ), + 1 => array( + 'host' => 'localhost', + 'port' => 12345, + 'type' => 'TCP', + ), + 2 => array( + 'host' => '/some/memcached.sock', + 'port' => 0, + 'type' => 'SOCKET', + ), + ); + $this->assertSame($expected, $client->getServerList()); + + $dsn = 'memcached://localhost?host[foo.bar]=3'; + $client = MemcachedAdapter::createConnection($dsn); + + $expected = array( + 0 => array( + 'host' => 'localhost', + 'port' => 11211, + 'type' => 'TCP', + ), + 1 => array( + 'host' => 'foo.bar', + 'port' => 11211, + 'type' => 'TCP', + ), + ); + $this->assertSame($expected, $client->getServerList()); + } } diff --git a/src/Symfony/Component/Cache/Traits/MemcachedTrait.php b/src/Symfony/Component/Cache/Traits/MemcachedTrait.php index 521fe74186b3..df34850ba51e 100644 --- a/src/Symfony/Component/Cache/Traits/MemcachedTrait.php +++ b/src/Symfony/Component/Cache/Traits/MemcachedTrait.php @@ -99,19 +99,43 @@ public static function createConnection($servers, array $options = array()) if (\is_array($dsn)) { continue; } - if (0 !== strpos($dsn, 'memcached://')) { - throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s does not start with "memcached://"', $dsn)); + if (0 !== strpos($dsn, 'memcached:')) { + throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s does not start with "memcached:"', $dsn)); } - $params = preg_replace_callback('#^memcached://(?:([^@]*+)@)?#', function ($m) use (&$username, &$password) { - if (!empty($m[1])) { - list($username, $password) = explode(':', $m[1], 2) + array(1 => null); + $params = preg_replace_callback('#^memcached:(//)?(?:([^@]*+)@)?#', function ($m) use (&$username, &$password) { + if (!empty($m[2])) { + list($username, $password) = explode(':', $m[2], 2) + array(1 => null); } - return 'file://'; + return 'file:'.($m[1] ?? ''); }, $dsn); if (false === $params = parse_url($params)) { throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s', $dsn)); } + $query = $hosts = array(); + if (isset($params['query'])) { + parse_str($params['query'], $query); + + if (isset($query['host'])) { + if (!\is_array($hosts = $query['host'])) { + throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s', $dsn)); + } + foreach ($hosts as $host => $weight) { + if (false === $port = strrpos($host, ':')) { + $hosts[$host] = array($host, 11211, (int) $weight); + } else { + $hosts[$host] = array(substr($host, 0, $port), (int) substr($host, 1 + $port), (int) $weight); + } + } + $hosts = array_values($hosts); + unset($query['host']); + } + if ($hosts && !isset($params['host']) && !isset($params['path'])) { + unset($servers[$i]); + $servers = array_merge($servers, $hosts); + continue; + } + } if (!isset($params['host']) && !isset($params['path'])) { throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s', $dsn)); } @@ -124,13 +148,16 @@ public static function createConnection($servers, array $options = array()) 'port' => isset($params['host']) ? 11211 : null, 'weight' => 0, ); - if (isset($params['query'])) { - parse_str($params['query'], $query); + if ($query) { $params += $query; $options = $query + $options; } $servers[$i] = array($params['host'], $params['port'], $params['weight']); + + if ($hosts) { + $servers = array_merge($servers, $hosts); + } } // set client's options