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:
commit
537c496dfe
@ -23,6 +23,7 @@ use Symfony\Bundle\FrameworkBundle\Command\YamlLintCommand;
|
|||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||||
use Symfony\Bundle\FrameworkBundle\Routing\AnnotatedRouteControllerLoader;
|
use Symfony\Bundle\FrameworkBundle\Routing\AnnotatedRouteControllerLoader;
|
||||||
|
use Symfony\Component\Cache\Adapter\AbstractAdapter;
|
||||||
use Symfony\Component\Cache\Adapter\AdapterInterface;
|
use Symfony\Component\Cache\Adapter\AdapterInterface;
|
||||||
use Symfony\Component\Cache\Adapter\ArrayAdapter;
|
use Symfony\Component\Cache\Adapter\ArrayAdapter;
|
||||||
use Symfony\Component\Cache\ResettableInterface;
|
use Symfony\Component\Cache\ResettableInterface;
|
||||||
@ -1706,7 +1707,7 @@ class FrameworkExtension extends Extension
|
|||||||
if (!$container->hasDefinition($connectionDefinitionId = $container->hash($storeDsn))) {
|
if (!$container->hasDefinition($connectionDefinitionId = $container->hash($storeDsn))) {
|
||||||
$connectionDefinition = new Definition(\stdClass::class);
|
$connectionDefinition = new Definition(\stdClass::class);
|
||||||
$connectionDefinition->setPublic(false);
|
$connectionDefinition->setPublic(false);
|
||||||
$connectionDefinition->setFactory(array(StoreFactory::class, 'createConnection'));
|
$connectionDefinition->setFactory(array(AbstractAdapter::class, 'createConnection'));
|
||||||
$connectionDefinition->setArguments(array($storeDsn));
|
$connectionDefinition->setArguments(array($storeDsn));
|
||||||
$container->setDefinition($connectionDefinitionId, $connectionDefinition);
|
$container->setDefinition($connectionDefinitionId, $connectionDefinition);
|
||||||
}
|
}
|
||||||
|
@ -58,128 +58,6 @@ class MemcachedStore implements StoreInterface
|
|||||||
$this->initialTtl = $initialTtl;
|
$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}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
|
@ -53,88 +53,6 @@ class RedisStore implements StoreInterface
|
|||||||
$this->initialTtl = $initialTtl;
|
$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}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
|
@ -20,21 +20,6 @@ use Symfony\Component\Lock\Exception\InvalidArgumentException;
|
|||||||
*/
|
*/
|
||||||
class StoreFactory
|
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
|
* @param \Redis|\RedisArray|\RedisCluster|\Predis\Client|\Memcached $connection
|
||||||
*
|
*
|
||||||
|
@ -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');
|
$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),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user