Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 0b7f4ef

Browse filesBrowse files
committed
cache adapter implementation for memcached extension
squashed commits: - fabbot.io diff applied to fix style issues - removal of phpunit 5.x features to support legacy PHP versions, changes to texting server array for HHVM fix - fabbot.io patch for style fix - removed unnessissary abstract test class (copied from redis tests during initial creation of the tests for this code - changes per @nicolas-grekas and @stof - fabbot.io fixes - cleanup docblocks - move to allow dsn for memcached client config - fabbot.io style fixes - cleanup of docblocks to bring in line with symfony standards - minor cleanup and refactoring - fully function dsn server input, enhanced test, general refactoring - fabbot.io style fixes - cleanup - simplified server/dsn/option logic - added test for no host provided in dsn - fixup tests - made default client options a static class property - major unit test cleanup - fabbot.io style fixes - cleanup of var_filter handling - fabbot.io style fixes - fixed extension check - refactored abstract class into trait, reworked tests - fabbot.io style fixes
1 parent d6e8937 commit 0b7f4ef
Copy full SHA for 0b7f4ef

File tree

6 files changed

+841
-0
lines changed
Filter options

6 files changed

+841
-0
lines changed
+146Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Cache\Adapter;
13+
14+
use Symfony\Component\Cache\Exception\InvalidArgumentException;
15+
16+
/**
17+
* @author Rob Frawley 2nd <rmf@src.run>
18+
*/
19+
class MemcacheAdapter extends AbstractAdapter
20+
{
21+
use MemcacheAdapterTrait;
22+
23+
/**
24+
* Construct adapter by passing a \Memcache instance and an optional namespace and default cache entry ttl.
25+
*
26+
* @param \Memcache $client
27+
* @param string|null $namespace
28+
* @param int $defaultLifetime
29+
*/
30+
public function __construct(\Memcache $client, $namespace = '', $defaultLifetime = 0)
31+
{
32+
parent::__construct($namespace, $defaultLifetime);
33+
$this->client = $client;
34+
}
35+
36+
/**
37+
* Factory creation method that provides an instance of this adapter with a\Memcache client instantiated and setup.
38+
*
39+
* Valid DSN values include the following:
40+
* - memcached://localhost : Specifies only the host (defaults used for port and weight)
41+
* - memcached://example.com:1234 : Specifies host and port (defaults weight)
42+
* - memcached://example.com:1234?weight=50 : Specifies host, port, and weight (no defaults used)
43+
*
44+
* @param string|null $dsn
45+
*
46+
* @return MemcacheAdapter
47+
*/
48+
public static function create($dsn = null)
49+
{
50+
if (!extension_loaded('memcache')) {
51+
throw new InvalidArgumentException('Failed to create Memcache client due to missing "memcache" extension.');
52+
}
53+
54+
$adapter = new static(new \Memcache());
55+
$adapter->setup($dsn ? array($dsn) : array());
56+
57+
return $adapter;
58+
}
59+
60+
/**
61+
* {@inheritdoc}
62+
*/
63+
protected function doSave(array $values, $lifetime)
64+
{
65+
$result = true;
66+
67+
foreach ($values as $id => $val) {
68+
$result = $this->client->set($id, $val, null, $lifetime) && $result;
69+
}
70+
71+
return $result;
72+
}
73+
74+
/**
75+
* {@inheritdoc}
76+
*/
77+
protected function doFetch(array $ids)
78+
{
79+
foreach ($this->client->get($ids) as $id => $val) {
80+
yield $id => $val;
81+
}
82+
}
83+
84+
/**
85+
* {@inheritdoc}
86+
*/
87+
protected function doHave($id)
88+
{
89+
return $this->client->get($id) !== false;
90+
}
91+
92+
/**
93+
* {@inheritdoc}
94+
*/
95+
protected function doDelete(array $ids)
96+
{
97+
$remaining = array_filter($ids, function ($id) {
98+
return false !== $this->client->get($id) && false === $this->client->delete($id);
99+
});
100+
101+
return 0 === count($remaining);
102+
}
103+
104+
private function getIdsByPrefix($namespace)
105+
{
106+
$ids = array();
107+
foreach ($this->client->getExtendedStats('slabs') as $slabGroup) {
108+
foreach ($slabGroup as $slabId => $slabMetadata) {
109+
if (!is_array($slabMetadata)) {
110+
continue;
111+
}
112+
foreach ($this->client->getExtendedStats('cachedump', (int) $slabId, 1000) as $slabIds) {
113+
if (is_array($slabIds)) {
114+
$ids = array_merge($ids, array_keys($slabIds));
115+
}
116+
}
117+
}
118+
}
119+
120+
return array_filter((array) $ids, function ($id) use ($namespace) {
121+
return 0 === strpos($id, $namespace);
122+
});
123+
}
124+
125+
private function addServer($dsn)
126+
{
127+
list($host, $port, $weight) = $this->dsnExtract($dsn);
128+
129+
return $this->isServerInClientPool($host, $port)
130+
|| $this->client->addServer($host, $port, false, $weight);
131+
}
132+
133+
private function setOption($opt, $val)
134+
{
135+
return true;
136+
}
137+
138+
private function isServerInClientPool($host, $port)
139+
{
140+
$restore = error_reporting(~E_ALL);
141+
$srvStat = $this->client->getServerStatus($host, $port);
142+
error_reporting($restore);
143+
144+
return 1 === $srvStat;
145+
}
146+
}
+114Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Cache\Adapter;
13+
14+
use Symfony\Component\Cache\Exception\InvalidArgumentException;
15+
16+
/**
17+
* @author Rob Frawley 2nd <rmf@src.run>
18+
*
19+
* @internal
20+
*/
21+
trait MemcacheAdapterTrait
22+
{
23+
private static $defaultClientServerValues = array(
24+
'host' => '127.0.0.1',
25+
'port' => 11211,
26+
'weight' => 100,
27+
);
28+
29+
/**
30+
* @var \Memcache|\Memcached
31+
*/
32+
private $client;
33+
34+
/**
35+
* Provide ability to reconfigure adapter after construction. See {@see create()} for acceptable DSN formats.
36+
*
37+
* @param string[] $dsns
38+
* @param mixed[] $opts
39+
*
40+
* @return bool
41+
*/
42+
public function setup(array $dsns = array(), array $opts = array())
43+
{
44+
$return = true;
45+
46+
foreach ($opts as $opt => $val) {
47+
$return = $this->setOption($opt, $val) && $return;
48+
}
49+
foreach ($dsns as $dsn) {
50+
$return = $this->addServer($dsn) && $return;
51+
}
52+
53+
return $return;
54+
}
55+
56+
/**
57+
* Returns the Memcache client instance.
58+
*
59+
* @return \Memcache|\Memcached
60+
*/
61+
public function getClient()
62+
{
63+
return $this->client;
64+
}
65+
66+
/**
67+
* {@inheritdoc}
68+
*/
69+
protected function doClear($namespace)
70+
{
71+
if (!isset($namespace[0]) || false === $ids = $this->getIdsByPrefix($namespace)) {
72+
return $this->client->flush();
73+
}
74+
75+
$return = true;
76+
77+
do {
78+
$return = $this->doDelete($ids) && $return;
79+
} while ($ids = $this->getIdsByPrefix($namespace));
80+
81+
return $return;
82+
}
83+
84+
private function dsnExtract($dsn)
85+
{
86+
$scheme = false !== strpos(static::class, 'Memcached') ? 'memcached' : 'memcache';
87+
88+
if (false === ($srv = parse_url($dsn)) || $srv['scheme'] !== $scheme || count($srv) > 4) {
89+
throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s (expects "memcached://example.com[:1234][?weight=<int>]")', $dsn));
90+
}
91+
92+
if (isset($srv['query']) && 1 === preg_match('{weight=([^&]{1,})}', $srv['query'], $weight)) {
93+
$srv['weight'] = (int) $weight[1];
94+
}
95+
96+
return $this->dsnSanitize($srv);
97+
}
98+
99+
private function dsnSanitize(array $srv)
100+
{
101+
$srv += self::$defaultClientServerValues;
102+
103+
if (false === ($host = filter_var($srv['host'], FILTER_VALIDATE_IP)) ||
104+
false === ($host = filter_var($srv['host'], FILTER_SANITIZE_URL))) {
105+
throw new InvalidArgumentException(sprintf('Invalid Memcached DSN host: %s (expects resolvable IP or hostname)', $srv['host']));
106+
}
107+
108+
if (false === ($weight = filter_var($srv['weight'], FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 100))))) {
109+
throw new InvalidArgumentException(sprintf('Invalid Memcached DSN weight: %s (expects int >=1 and <= 100)', $srv['weight']));
110+
}
111+
112+
return array($host, $srv['port'], $weight);
113+
}
114+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.