minor #24366 [Lock] Use cache connection factories in lock (jderusse)

This PR was merged into the 3.4 branch.

Discussion
----------

[Lock] Use cache connection factories in lock

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | no
| New feature?  | no (feature removal)
| BC breaks?    | no (if merged in 3.4)
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets |
| License       | MIT
| Doc PR        |

An alternative to https://github.com/symfony/symfony/pull/24267 to share code between cache and lock.

Commits
-------

95358ac98f Share connection factories between cache and lock
This commit is contained in:
Fabien Potencier 2017-09-28 16:19:46 -07:00
commit 537c496dfe
5 changed files with 2 additions and 316 deletions

View File

@ -23,6 +23,7 @@ use Symfony\Bundle\FrameworkBundle\Command\YamlLintCommand;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Bundle\FrameworkBundle\Routing\AnnotatedRouteControllerLoader;
use Symfony\Component\Cache\Adapter\AbstractAdapter;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\ResettableInterface;
@ -1706,7 +1707,7 @@ class FrameworkExtension extends Extension
if (!$container->hasDefinition($connectionDefinitionId = $container->hash($storeDsn))) {
$connectionDefinition = new Definition(\stdClass::class);
$connectionDefinition->setPublic(false);
$connectionDefinition->setFactory(array(StoreFactory::class, 'createConnection'));
$connectionDefinition->setFactory(array(AbstractAdapter::class, 'createConnection'));
$connectionDefinition->setArguments(array($storeDsn));
$container->setDefinition($connectionDefinitionId, $connectionDefinition);
}

View File

@ -58,128 +58,6 @@ class MemcachedStore implements StoreInterface
$this->initialTtl = $initialTtl;
}
/**
* Creates a Memcached instance.
*
* By default, the binary protocol, block, and libketama compatible options are enabled.
*
* Example DSN:
* - 'memcached://user:pass@localhost?weight=33'
* - array(array('localhost', 11211, 33))
*
* @param string $dsn A server or A DSN
* @param array $options An array of options
*
* @return \Memcached
*
* @throws \ErrorEception When invalid options or server are provided
*/
public static function createConnection($server, array $options = array())
{
if (!static::isSupported()) {
throw new InvalidArgumentException('Memcached extension is required');
}
set_error_handler(function ($type, $msg, $file, $line) { throw new \ErrorException($msg, 0, $type, $file, $line); });
try {
$options += static::$defaultClientOptions;
$client = new \Memcached($options['persistent_id']);
$username = $options['username'];
$password = $options['password'];
// parse any DSN in $server
if (is_string($server)) {
if (0 !== strpos($server, 'memcached://')) {
throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s does not start with "memcached://"', $server));
}
$params = preg_replace_callback('#^memcached://(?:([^@]*+)@)?#', function ($m) use (&$username, &$password) {
if (!empty($m[1])) {
list($username, $password) = explode(':', $m[1], 2) + array(1 => null);
}
return 'file://';
}, $server);
if (false === $params = parse_url($params)) {
throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s', $server));
}
if (!isset($params['host']) && !isset($params['path'])) {
throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s', $server));
}
if (isset($params['path']) && preg_match('#/(\d+)$#', $params['path'], $m)) {
$params['weight'] = $m[1];
$params['path'] = substr($params['path'], 0, -strlen($m[0]));
}
$params += array(
'host' => isset($params['host']) ? $params['host'] : $params['path'],
'port' => isset($params['host']) ? 11211 : null,
'weight' => 0,
);
if (isset($params['query'])) {
parse_str($params['query'], $query);
$params += $query;
$options = $query + $options;
}
$server = array($params['host'], $params['port'], $params['weight']);
}
// set client's options
unset($options['persistent_id'], $options['username'], $options['password'], $options['weight']);
$options = array_change_key_case($options, CASE_UPPER);
$client->setOption(\Memcached::OPT_BINARY_PROTOCOL, true);
$client->setOption(\Memcached::OPT_NO_BLOCK, false);
if (!array_key_exists('LIBKETAMA_COMPATIBLE', $options) && !array_key_exists(\Memcached::OPT_LIBKETAMA_COMPATIBLE, $options)) {
$client->setOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE, true);
}
foreach ($options as $name => $value) {
if (is_int($name)) {
continue;
}
if ('HASH' === $name || 'SERIALIZER' === $name || 'DISTRIBUTION' === $name) {
$value = constant('Memcached::'.$name.'_'.strtoupper($value));
}
$opt = constant('Memcached::OPT_'.$name);
unset($options[$name]);
$options[$opt] = $value;
}
$client->setOptions($options);
// set client's servers, taking care of persistent connections
if (!$client->isPristine()) {
$oldServers = array();
foreach ($client->getServerList() as $server) {
$oldServers[] = array($server['host'], $server['port']);
}
$newServers = array();
if (1 < count($server)) {
$server = array_values($server);
unset($server[2]);
$server[1] = (int) $server[1];
}
$newServers[] = $server;
if ($oldServers !== $newServers) {
// before resetting, ensure $servers is valid
$client->addServers(array($server));
$client->resetServerList();
}
}
$client->addServers(array($server));
if (null !== $username || null !== $password) {
if (!method_exists($client, 'setSaslAuthData')) {
trigger_error('Missing SASL support: the memcached extension must be compiled with --enable-memcached-sasl.');
}
$client->setSaslAuthData($username, $password);
}
return $client;
} finally {
restore_error_handler();
}
}
/**
* {@inheritdoc}
*/

View File

@ -53,88 +53,6 @@ class RedisStore implements StoreInterface
$this->initialTtl = $initialTtl;
}
/**
* Creates a Redis connection using a DSN configuration.
*
* Example DSN:
* - redis://localhost
* - redis://example.com:1234
* - redis://secret@example.com/13
* - redis:///var/run/redis.sock
* - redis://secret@/var/run/redis.sock/13
*
* @param string $dsn
* @param array $options See self::$defaultConnectionOptions
*
* @throws InvalidArgumentException When the DSN is invalid
*
* @return \Redis|\Predis\Client According to the "class" option
*/
public static function createConnection($dsn, array $options = array())
{
if (0 !== strpos($dsn, 'redis://')) {
throw new InvalidArgumentException(sprintf('Invalid Redis DSN: %s does not start with "redis://"', $dsn));
}
$params = preg_replace_callback('#^redis://(?:(?:[^:@]*+:)?([^@]*+)@)?#', function ($m) use (&$auth) {
if (isset($m[1])) {
$auth = $m[1];
}
return 'file://';
}, $dsn);
if (false === $params = parse_url($params)) {
throw new InvalidArgumentException(sprintf('Invalid Redis DSN: %s', $dsn));
}
if (!isset($params['host']) && !isset($params['path'])) {
throw new InvalidArgumentException(sprintf('Invalid Redis DSN: %s', $dsn));
}
if (isset($params['path']) && preg_match('#/(\d+)$#', $params['path'], $m)) {
$params['dbindex'] = $m[1];
$params['path'] = substr($params['path'], 0, -strlen($m[0]));
}
$params += array(
'host' => isset($params['host']) ? $params['host'] : $params['path'],
'port' => isset($params['host']) ? 6379 : null,
'dbindex' => 0,
);
if (isset($params['query'])) {
parse_str($params['query'], $query);
$params += $query;
}
$params += $options + self::$defaultConnectionOptions;
$class = null === $params['class'] ? (extension_loaded('redis') ? \Redis::class : \Predis\Client::class) : $params['class'];
if (is_a($class, \Redis::class, true)) {
$connect = $params['persistent'] || $params['persistent_id'] ? 'pconnect' : 'connect';
$redis = new $class();
@$redis->{$connect}($params['host'], $params['port'], $params['timeout'], $params['persistent_id'], $params['retry_interval']);
if (@!$redis->isConnected()) {
$e = ($e = error_get_last()) && preg_match('/^Redis::p?connect\(\): (.*)/', $e['message'], $e) ? sprintf(' (%s)', $e[1]) : '';
throw new InvalidArgumentException(sprintf('Redis connection failed%s: %s', $e, $dsn));
}
if ((null !== $auth && !$redis->auth($auth))
|| ($params['dbindex'] && !$redis->select($params['dbindex']))
|| ($params['read_timeout'] && !$redis->setOption(\Redis::OPT_READ_TIMEOUT, $params['read_timeout']))
) {
$e = preg_replace('/^ERR /', '', $redis->getLastError());
throw new InvalidArgumentException(sprintf('Redis connection failed (%s): %s', $e, $dsn));
}
} elseif (is_a($class, \Predis\Client::class, true)) {
$params['scheme'] = isset($params['host']) ? 'tcp' : 'unix';
$params['database'] = $params['dbindex'] ?: null;
$params['password'] = $auth;
$redis = new $class((new Factory())->create($params));
} elseif (class_exists($class, false)) {
throw new InvalidArgumentException(sprintf('"%s" is not a subclass of "Redis" or "Predis\Client"', $class));
} else {
throw new InvalidArgumentException(sprintf('Class "%s" does not exist', $class));
}
return $redis;
}
/**
* {@inheritdoc}
*/

View File

@ -20,21 +20,6 @@ use Symfony\Component\Lock\Exception\InvalidArgumentException;
*/
class StoreFactory
{
public static function createConnection($dsn, array $options = array())
{
if (!is_string($dsn)) {
throw new InvalidArgumentException(sprintf('The %s() method expects argument #1 to be string, %s given.', __METHOD__, gettype($dsn)));
}
if (0 === strpos($dsn, 'redis://')) {
return RedisStore::createConnection($dsn, $options);
}
if (0 === strpos($dsn, 'memcached://')) {
return MemcachedStore::createConnection($dsn, $options);
}
throw new InvalidArgumentException(sprintf('Unsupported DSN: %s.', $dsn));
}
/**
* @param \Redis|\RedisArray|\RedisCluster|\Predis\Client|\Memcached $connection
*

View File

@ -54,100 +54,4 @@ class MemcachedStoreTest extends AbstractStoreTest
{
$this->markTestSkipped('Memcached expects a TTL greater than 1 sec. Simulating a slow network is too hard');
}
public function testDefaultOptions()
{
$this->assertTrue(MemcachedStore::isSupported());
$client = MemcachedStore::createConnection('memcached://127.0.0.1');
$this->assertTrue($client->getOption(\Memcached::OPT_COMPRESSION));
$this->assertSame(1, $client->getOption(\Memcached::OPT_BINARY_PROTOCOL));
$this->assertSame(1, $client->getOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE));
}
/**
* @dataProvider provideServersSetting
*/
public function testServersSetting($dsn, $host, $port)
{
$client1 = MemcachedStore::createConnection($dsn);
$client3 = MemcachedStore::createConnection(array($host, $port));
$expect = array(
'host' => $host,
'port' => $port,
);
$f = function ($s) { return array('host' => $s['host'], 'port' => $s['port']); };
$this->assertSame(array($expect), array_map($f, $client1->getServerList()));
$this->assertSame(array($expect), array_map($f, $client3->getServerList()));
}
public function provideServersSetting()
{
yield array(
'memcached://127.0.0.1/50',
'127.0.0.1',
11211,
);
yield array(
'memcached://localhost:11222?weight=25',
'localhost',
11222,
);
if (ini_get('memcached.use_sasl')) {
yield array(
'memcached://user:password@127.0.0.1?weight=50',
'127.0.0.1',
11211,
);
}
yield array(
'memcached:///var/run/memcached.sock?weight=25',
'/var/run/memcached.sock',
0,
);
yield array(
'memcached:///var/local/run/memcached.socket?weight=25',
'/var/local/run/memcached.socket',
0,
);
if (ini_get('memcached.use_sasl')) {
yield array(
'memcached://user:password@/var/local/run/memcached.socket?weight=25',
'/var/local/run/memcached.socket',
0,
);
}
}
/**
* @dataProvider provideDsnWithOptions
*/
public function testDsnWithOptions($dsn, array $options, array $expectedOptions)
{
$client = MemcachedStore::createConnection($dsn, $options);
foreach ($expectedOptions as $option => $expect) {
$this->assertSame($expect, $client->getOption($option));
}
}
public function provideDsnWithOptions()
{
if (!class_exists('\Memcached')) {
self::markTestSkipped('Extension memcached required.');
}
yield array(
'memcached://localhost:11222?retry_timeout=10',
array(\Memcached::OPT_RETRY_TIMEOUT => 8),
array(\Memcached::OPT_RETRY_TIMEOUT => 10),
);
yield array(
'memcached://localhost:11222?socket_recv_size=1&socket_send_size=2',
array(\Memcached::OPT_RETRY_TIMEOUT => 8),
array(\Memcached::OPT_SOCKET_RECV_SIZE => 1, \Memcached::OPT_SOCKET_SEND_SIZE => 2, \Memcached::OPT_RETRY_TIMEOUT => 8),
);
}
}