Skip to content

Commit 0e690b5

Browse files
jderussefabpot
authored andcommitted
[Lock] Include lock component in framework bundle
1 parent 28f0ea0 commit 0e690b5

File tree

6 files changed

+374
-3
lines changed

6 files changed

+374
-3
lines changed

Store/FlockStore.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,15 @@ class FlockStore implements StoreInterface
3232
private $lockPath;
3333

3434
/**
35-
* @param string $lockPath the directory to store the lock
35+
* @param string|null $lockPath the directory to store the lock, defaults to the system's temporary directory
3636
*
3737
* @throws LockStorageException If the lock directory could not be created or is not writable
3838
*/
39-
public function __construct($lockPath)
39+
public function __construct($lockPath = null)
4040
{
41+
if (null === $lockPath) {
42+
$lockPath = sys_get_temp_dir();
43+
}
4144
if (!is_dir($lockPath) || !is_writable($lockPath)) {
4245
throw new InvalidArgumentException(sprintf('The directory "%s" is not writable.', $lockPath));
4346
}

Store/MemcachedStore.php

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@
2424
*/
2525
class MemcachedStore implements StoreInterface
2626
{
27+
private static $defaultClientOptions = array(
28+
'persistent_id' => null,
29+
'username' => null,
30+
'password' => null,
31+
);
32+
2733
private $memcached;
2834
private $initialTtl;
2935
/** @var bool */
@@ -52,6 +58,128 @@ public function __construct(\Memcached $memcached, $initialTtl = 300)
5258
$this->initialTtl = $initialTtl;
5359
}
5460

61+
/**
62+
* Creates a Memcached instance.
63+
*
64+
* By default, the binary protocol, block, and libketama compatible options are enabled.
65+
*
66+
* Example DSN:
67+
* - 'memcached://user:pass@localhost?weight=33'
68+
* - array(array('localhost', 11211, 33))
69+
*
70+
* @param string $dsn A server or A DSN
71+
* @param array $options An array of options
72+
*
73+
* @return \Memcached
74+
*
75+
* @throws \ErrorEception When invalid options or server are provided
76+
*/
77+
public static function createConnection($server, array $options = array())
78+
{
79+
if (!static::isSupported()) {
80+
throw new InvalidArgumentException('Memcached extension is required');
81+
}
82+
set_error_handler(function ($type, $msg, $file, $line) { throw new \ErrorException($msg, 0, $type, $file, $line); });
83+
try {
84+
$options += static::$defaultClientOptions;
85+
$client = new \Memcached($options['persistent_id']);
86+
$username = $options['username'];
87+
$password = $options['password'];
88+
89+
// parse any DSN in $server
90+
if (is_string($server)) {
91+
if (0 !== strpos($server, 'memcached://')) {
92+
throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s does not start with "memcached://"', $server));
93+
}
94+
$params = preg_replace_callback('#^memcached://(?:([^@]*+)@)?#', function ($m) use (&$username, &$password) {
95+
if (!empty($m[1])) {
96+
list($username, $password) = explode(':', $m[1], 2) + array(1 => null);
97+
}
98+
99+
return 'file://';
100+
}, $server);
101+
if (false === $params = parse_url($params)) {
102+
throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s', $server));
103+
}
104+
if (!isset($params['host']) && !isset($params['path'])) {
105+
throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s', $server));
106+
}
107+
if (isset($params['path']) && preg_match('#/(\d+)$#', $params['path'], $m)) {
108+
$params['weight'] = $m[1];
109+
$params['path'] = substr($params['path'], 0, -strlen($m[0]));
110+
}
111+
$params += array(
112+
'host' => isset($params['host']) ? $params['host'] : $params['path'],
113+
'port' => isset($params['host']) ? 11211 : null,
114+
'weight' => 0,
115+
);
116+
if (isset($params['query'])) {
117+
parse_str($params['query'], $query);
118+
$params += $query;
119+
$options = $query + $options;
120+
}
121+
122+
$server = array($params['host'], $params['port'], $params['weight']);
123+
}
124+
125+
// set client's options
126+
unset($options['persistent_id'], $options['username'], $options['password'], $options['weight']);
127+
$options = array_change_key_case($options, CASE_UPPER);
128+
$client->setOption(\Memcached::OPT_BINARY_PROTOCOL, true);
129+
$client->setOption(\Memcached::OPT_NO_BLOCK, false);
130+
if (!array_key_exists('LIBKETAMA_COMPATIBLE', $options) && !array_key_exists(\Memcached::OPT_LIBKETAMA_COMPATIBLE, $options)) {
131+
$client->setOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE, true);
132+
}
133+
foreach ($options as $name => $value) {
134+
if (is_int($name)) {
135+
continue;
136+
}
137+
if ('HASH' === $name || 'SERIALIZER' === $name || 'DISTRIBUTION' === $name) {
138+
$value = constant('Memcached::'.$name.'_'.strtoupper($value));
139+
}
140+
$opt = constant('Memcached::OPT_'.$name);
141+
142+
unset($options[$name]);
143+
$options[$opt] = $value;
144+
}
145+
$client->setOptions($options);
146+
147+
// set client's servers, taking care of persistent connections
148+
if (!$client->isPristine()) {
149+
$oldServers = array();
150+
foreach ($client->getServerList() as $server) {
151+
$oldServers[] = array($server['host'], $server['port']);
152+
}
153+
154+
$newServers = array();
155+
if (1 < count($server)) {
156+
$server = array_values($server);
157+
unset($server[2]);
158+
$server[1] = (int) $server[1];
159+
}
160+
$newServers[] = $server;
161+
162+
if ($oldServers !== $newServers) {
163+
// before resetting, ensure $servers is valid
164+
$client->addServers(array($server));
165+
$client->resetServerList();
166+
}
167+
}
168+
$client->addServers(array($server));
169+
170+
if (null !== $username || null !== $password) {
171+
if (!method_exists($client, 'setSaslAuthData')) {
172+
trigger_error('Missing SASL support: the memcached extension must be compiled with --enable-memcached-sasl.');
173+
}
174+
$client->setSaslAuthData($username, $password);
175+
}
176+
177+
return $client;
178+
} finally {
179+
restore_error_handler();
180+
}
181+
}
182+
55183
/**
56184
* {@inheritdoc}
57185
*/

Store/RedisStore.php

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@
2424
*/
2525
class RedisStore implements StoreInterface
2626
{
27+
private static $defaultConnectionOptions = array(
28+
'class' => null,
29+
'persistent' => 0,
30+
'persistent_id' => null,
31+
'timeout' => 30,
32+
'read_timeout' => 0,
33+
'retry_interval' => 0,
34+
);
2735
private $redis;
2836
private $initialTtl;
2937

@@ -45,6 +53,88 @@ public function __construct($redisClient, $initialTtl = 300.0)
4553
$this->initialTtl = $initialTtl;
4654
}
4755

56+
/**
57+
* Creates a Redis connection using a DSN configuration.
58+
*
59+
* Example DSN:
60+
* - redis://localhost
61+
* - redis://example.com:1234
62+
* - redis://[email protected]/13
63+
* - redis:///var/run/redis.sock
64+
* - redis://secret@/var/run/redis.sock/13
65+
*
66+
* @param string $dsn
67+
* @param array $options See self::$defaultConnectionOptions
68+
*
69+
* @throws InvalidArgumentException When the DSN is invalid
70+
*
71+
* @return \Redis|\Predis\Client According to the "class" option
72+
*/
73+
public static function createConnection($dsn, array $options = array())
74+
{
75+
if (0 !== strpos($dsn, 'redis://')) {
76+
throw new InvalidArgumentException(sprintf('Invalid Redis DSN: %s does not start with "redis://"', $dsn));
77+
}
78+
$params = preg_replace_callback('#^redis://(?:(?:[^:@]*+:)?([^@]*+)@)?#', function ($m) use (&$auth) {
79+
if (isset($m[1])) {
80+
$auth = $m[1];
81+
}
82+
83+
return 'file://';
84+
}, $dsn);
85+
if (false === $params = parse_url($params)) {
86+
throw new InvalidArgumentException(sprintf('Invalid Redis DSN: %s', $dsn));
87+
}
88+
if (!isset($params['host']) && !isset($params['path'])) {
89+
throw new InvalidArgumentException(sprintf('Invalid Redis DSN: %s', $dsn));
90+
}
91+
if (isset($params['path']) && preg_match('#/(\d+)$#', $params['path'], $m)) {
92+
$params['dbindex'] = $m[1];
93+
$params['path'] = substr($params['path'], 0, -strlen($m[0]));
94+
}
95+
$params += array(
96+
'host' => isset($params['host']) ? $params['host'] : $params['path'],
97+
'port' => isset($params['host']) ? 6379 : null,
98+
'dbindex' => 0,
99+
);
100+
if (isset($params['query'])) {
101+
parse_str($params['query'], $query);
102+
$params += $query;
103+
}
104+
$params += $options + self::$defaultConnectionOptions;
105+
$class = null === $params['class'] ? (extension_loaded('redis') ? \Redis::class : \Predis\Client::class) : $params['class'];
106+
107+
if (is_a($class, \Redis::class, true)) {
108+
$connect = $params['persistent'] || $params['persistent_id'] ? 'pconnect' : 'connect';
109+
$redis = new $class();
110+
@$redis->{$connect}($params['host'], $params['port'], $params['timeout'], $params['persistent_id'], $params['retry_interval']);
111+
112+
if (@!$redis->isConnected()) {
113+
$e = ($e = error_get_last()) && preg_match('/^Redis::p?connect\(\): (.*)/', $e['message'], $e) ? sprintf(' (%s)', $e[1]) : '';
114+
throw new InvalidArgumentException(sprintf('Redis connection failed%s: %s', $e, $dsn));
115+
}
116+
117+
if ((null !== $auth && !$redis->auth($auth))
118+
|| ($params['dbindex'] && !$redis->select($params['dbindex']))
119+
|| ($params['read_timeout'] && !$redis->setOption(\Redis::OPT_READ_TIMEOUT, $params['read_timeout']))
120+
) {
121+
$e = preg_replace('/^ERR /', '', $redis->getLastError());
122+
throw new InvalidArgumentException(sprintf('Redis connection failed (%s): %s', $e, $dsn));
123+
}
124+
} elseif (is_a($class, \Predis\Client::class, true)) {
125+
$params['scheme'] = isset($params['host']) ? 'tcp' : 'unix';
126+
$params['database'] = $params['dbindex'] ?: null;
127+
$params['password'] = $auth;
128+
$redis = new $class((new Factory())->create($params));
129+
} elseif (class_exists($class, false)) {
130+
throw new InvalidArgumentException(sprintf('"%s" is not a subclass of "Redis" or "Predis\Client"', $class));
131+
} else {
132+
throw new InvalidArgumentException(sprintf('Class "%s" does not exist', $class));
133+
}
134+
135+
return $redis;
136+
}
137+
48138
/**
49139
* {@inheritdoc}
50140
*/

Store/StoreFactory.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Lock\Store;
13+
14+
use Symfony\Component\Lock\Exception\InvalidArgumentException;
15+
16+
/**
17+
* StoreFactory create stores and connections.
18+
*
19+
* @author Jérémy Derussé <[email protected]>
20+
*/
21+
class StoreFactory
22+
{
23+
public static function createConnection($dsn, array $options = array())
24+
{
25+
if (!is_string($dsn)) {
26+
throw new InvalidArgumentException(sprintf('The %s() method expects argument #1 to be string, %s given.', __METHOD__, gettype($dsn)));
27+
}
28+
if (0 === strpos($dsn, 'redis://')) {
29+
return RedisStore::createConnection($dsn, $options);
30+
}
31+
if (0 === strpos($dsn, 'memcached://')) {
32+
return MemcachedStore::createConnection($dsn, $options);
33+
}
34+
35+
throw new InvalidArgumentException(sprintf('Unsupported DSN: %s.', $dsn));
36+
}
37+
38+
/**
39+
* @param \Redis|\RedisArray|\RedisCluster|\Predis\Client|\Memcached $connection
40+
*
41+
* @return RedisStore|MemcachedStore
42+
*/
43+
public static function createStore($connection)
44+
{
45+
if ($connection instanceof \Redis || $connection instanceof \RedisArray || $connection instanceof \RedisCluster || $connection instanceof \Predis\Client) {
46+
return new RedisStore($connection);
47+
}
48+
if ($connection instanceof \Memcached) {
49+
return new MemcachedStore($connection);
50+
}
51+
52+
throw new InvalidArgumentException(sprintf('Unsupported Connection: %s.', get_class($connection)));
53+
}
54+
}

Tests/Store/FlockStoreTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class FlockStoreTest extends AbstractStoreTest
2626
*/
2727
protected function getStore()
2828
{
29-
return new FlockStore(sys_get_temp_dir());
29+
return new FlockStore();
3030
}
3131

3232
/**

0 commit comments

Comments
 (0)