Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit dd4df10

Browse files
[Cache] Add DSN, createClient & better error reporting to MemcachedAdapter
1 parent 61a67ec commit dd4df10

File tree

10 files changed

+198
-17
lines changed

10 files changed

+198
-17
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolPass.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
1313

14-
use Symfony\Component\Cache\Adapter\RedisAdapter;
14+
use Symfony\Component\Cache\Adapter\AbstractAdapter;
1515
use Symfony\Component\DependencyInjection\ChildDefinition;
1616
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
1717
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -108,13 +108,13 @@ public static function getServiceProvider(ContainerBuilder $container, $name)
108108
{
109109
$container->resolveEnvPlaceholders($name, null, $usedEnvs);
110110

111-
if (0 === strpos($name, 'redis://') || $usedEnvs) {
111+
if ($usedEnvs || preg_match('#^[a-z]++://#', $name)) {
112112
$dsn = $name;
113113

114114
if (!$container->hasDefinition($name = md5($dsn))) {
115-
$definition = new Definition(\Redis::class);
115+
$definition = new Definition('_');
116116
$definition->setPublic(false);
117-
$definition->setFactory(array(RedisAdapter::class, 'createConnection'));
117+
$definition->setFactory(array(AbstractAdapter::class, 'createConnection'));
118118
$definition->setArguments(array($dsn));
119119
$container->setDefinition($name, $definition);
120120
}

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,7 @@ private function addCacheSection(ArrayNodeDefinition $rootNode)
706706
->scalarNode('default_doctrine_provider')->end()
707707
->scalarNode('default_psr6_provider')->end()
708708
->scalarNode('default_redis_provider')->defaultValue('redis://localhost')->end()
709+
->scalarNode('default_memcached_provider')->defaultValue('memcached://localhost')->end()
709710
->arrayNode('pools')
710711
->useAttributeAsKey('name')
711712
->prototype('array')

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1256,7 +1256,7 @@ private function registerCacheConfiguration(array $config, ContainerBuilder $con
12561256
// Inline any env vars referenced in the parameter
12571257
$container->setParameter('cache.prefix.seed', $container->resolveEnvPlaceholders($container->getParameter('cache.prefix.seed'), true));
12581258
}
1259-
foreach (array('doctrine', 'psr6', 'redis') as $name) {
1259+
foreach (array('doctrine', 'psr6', 'redis', 'memcached') as $name) {
12601260
if (isset($config[$name = 'default_'.$name.'_provider'])) {
12611261
$container->setAlias('cache.'.$name, new Alias(Compiler\CachePoolPass::getServiceProvider($container, $config[$name]), false));
12621262
}

src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,17 @@
9393
</call>
9494
</service>
9595

96+
<service id="cache.adapter.memcached" class="Symfony\Component\Cache\Adapter\MemcachedAdapter" abstract="true">
97+
<tag name="cache.pool" provider="cache.default_memcached_provider" clearer="cache.default_clearer" />
98+
<tag name="monolog.logger" channel="cache" />
99+
<argument /> <!-- Memcached connection service -->
100+
<argument /> <!-- namespace -->
101+
<argument>0</argument> <!-- default lifetime -->
102+
<call method="setLogger">
103+
<argument type="service" id="logger" on-invalid="ignore" />
104+
</call>
105+
</service>
106+
96107
<service id="cache.default_clearer" class="Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer">
97108
<tag name="kernel.cache_clearer" />
98109
</service>

src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@
215215
<xsd:element name="default-doctrine-provider" type="xsd:string" minOccurs="0" maxOccurs="1" />
216216
<xsd:element name="default-psr6-provider" type="xsd:string" minOccurs="0" maxOccurs="1" />
217217
<xsd:element name="default-redis-provider" type="xsd:string" minOccurs="0" maxOccurs="1" />
218+
<xsd:element name="default-memcached-provider" type="xsd:string" minOccurs="0" maxOccurs="1" />
218219
<xsd:element name="pool" type="cache_pool" minOccurs="0" maxOccurs="unbounded" />
219220
</xsd:sequence>
220221
</xsd:complexType>

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ protected static function getBundleDefaultConfig()
272272
'system' => 'cache.adapter.system',
273273
'directory' => '%kernel.cache_dir%/pools',
274274
'default_redis_provider' => 'redis://localhost',
275+
'default_memcached_provider' => 'memcached://localhost',
275276
),
276277
'workflows' => array(),
277278
'php_errors' => array(

src/Symfony/Bundle/FrameworkBundle/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
],
1818
"require": {
1919
"php": ">=5.5.9",
20-
"symfony/cache": "~3.2",
20+
"symfony/cache": "~3.3",
2121
"symfony/class-loader": "~3.2",
2222
"symfony/dependency-injection": "~3.3",
2323
"symfony/config": "~2.8|~3.0",

src/Symfony/Bundle/FrameworkBundle/phpunit.xml.dist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<php>
1010
<ini name="error_reporting" value="-1" />
1111
<env name="REDIS_HOST" value="localhost" />
12+
<env name="MEMCACHED_HOST" value="localhost" />
1213
</php>
1314

1415
<testsuites>

src/Symfony/Component/Cache/Adapter/AbstractAdapter.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,21 @@ public static function createSystemCache($namespace, $defaultLifetime, $version,
115115
return new ChainAdapter(array($apcu, $fs));
116116
}
117117

118+
public static function createConnection($dsn, array $options = array())
119+
{
120+
if (!is_string($dsn)) {
121+
throw new InvalidArgumentException(sprintf('The %s() method expect argument #1 to be string, %s given.', __METHOD__, gettype($dsn)));
122+
}
123+
if (0 === strpos($dsn, 'redis://')) {
124+
return RedisAdapter::createConnection($dsn, $options);
125+
}
126+
if (0 === strpos($dsn, 'memcached://')) {
127+
return MemcachedAdapter::createConnection($dsn, $options);
128+
}
129+
130+
throw new InvalidArgumentException(sprintf('Unsupported DSN: %s.', $dsn));
131+
}
132+
118133
/**
119134
* Fetches several cache items.
120135
*

src/Symfony/Component/Cache/Adapter/MemcachedAdapter.php

Lines changed: 162 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,68 +11,219 @@
1111

1212
namespace Symfony\Component\Cache\Adapter;
1313

14+
use Symfony\Component\Cache\Exception\CacheException;
15+
use Symfony\Component\Cache\Exception\InvalidArgumentException;
16+
1417
/**
1518
* @author Rob Frawley 2nd <[email protected]>
19+
* @author Nicolas Grekas <[email protected]>
1620
*/
1721
class MemcachedAdapter extends AbstractAdapter
1822
{
23+
private static $defaultClientOptions = array(
24+
'persistent_id' => null,
25+
'username' => null,
26+
'password' => null,
27+
);
28+
29+
protected $maxIdLength = 250;
30+
1931
private $client;
2032

33+
public static function isSupported()
34+
{
35+
return extension_loaded('memcached') && version_compare(phpversion('memcached'), '2.2.0', '>=');
36+
}
37+
2138
public function __construct(\Memcached $client, $namespace = '', $defaultLifetime = 0)
2239
{
40+
if (!static::isSupported()) {
41+
throw new CacheException('Memcached >= 2.2.0 is required');
42+
}
43+
$opt = $client->getOption(\Memcached::OPT_SERIALIZER);
44+
if (\Memcached::SERIALIZER_PHP !== $opt && \Memcached::SERIALIZER_IGBINARY !== $opt) {
45+
throw new CacheException('MemcachedAdapter: serializer must be PHP or IGBINARY.');
46+
}
47+
if (!$client->getOption(\Memcached::OPT_BINARY_PROTOCOL)) {
48+
throw new CacheException('MemcachedAdapter: OPT_BINARY_PROTOCOL must be used.');
49+
}
50+
$this->maxIdLength -= strlen($client->getOption(\Memcached::OPT_PREFIX_KEY));
51+
2352
parent::__construct($namespace, $defaultLifetime);
2453
$this->client = $client;
2554
}
2655

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

32172
/**
33173
* {@inheritdoc}
34174
*/
35175
protected function doSave(array $values, $lifetime)
36176
{
37-
return $this->client->setMulti($values, $lifetime) && $this->client->getResultCode() === \Memcached::RES_SUCCESS;
177+
return $this->checkResultCode($this->client->setMulti($values, $lifetime));
38178
}
39179

40180
/**
41181
* {@inheritdoc}
42182
*/
43183
protected function doFetch(array $ids)
44184
{
45-
return $this->client->getMulti($ids);
185+
return $this->checkResultCode($this->client->getMulti($ids));
46186
}
47187

48188
/**
49189
* {@inheritdoc}
50190
*/
51191
protected function doHave($id)
52192
{
53-
return $this->client->get($id) !== false || $this->client->getResultCode() === \Memcached::RES_SUCCESS;
193+
return false !== $this->client->get($id) || $this->checkResultCode(\Memcached::RES_SUCCESS === $this->client->getResultCode());
54194
}
55195

56196
/**
57197
* {@inheritdoc}
58198
*/
59199
protected function doDelete(array $ids)
60200
{
61-
$toDelete = count($ids);
62-
foreach ($this->client->deleteMulti($ids) as $result) {
63-
if (\Memcached::RES_SUCCESS === $result || \Memcached::RES_NOTFOUND === $result) {
64-
--$toDelete;
201+
$ok = true;
202+
foreach ($this->checkResultCode($this->client->deleteMulti($ids)) as $result) {
203+
if (\Memcached::RES_SUCCESS !== $result && \Memcached::RES_NOTFOUND !== $result) {
204+
$ok = false;
65205
}
66206
}
67207

68-
return 0 === $toDelete;
208+
return $ok;
69209
}
70210

71211
/**
72212
* {@inheritdoc}
73213
*/
74214
protected function doClear($namespace)
75215
{
76-
return $this->client->flush();
216+
return $this->checkResultCode($this->client->flush());
217+
}
218+
219+
private function checkResultCode($result)
220+
{
221+
$code = $this->client->getResultCode();
222+
223+
if (\Memcached::RES_SUCCESS === $code || \Memcached::RES_NOTFOUND === $code) {
224+
return $result;
225+
}
226+
227+
throw new CacheException(sprintf('MemcachedAdapter client error: %s.', strtolower($this->client->getResultMessage())));
77228
}
78229
}

0 commit comments

Comments
 (0)