Skip to content

Commit dec61d0

Browse files
committed
cache adapters for memcached and memcache extensions
1 parent d6e8937 commit dec61d0

File tree

6 files changed

+849
-0
lines changed

6 files changed

+849
-0
lines changed
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+
* - memcache://localhost : Specifies only the host (defaults used for port and weight)
41+
* - memcache://example.com:1234 : Specifies host and port (defaults weight)
42+
* - memcache://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') || !version_compare(phpversion('memcache'), '3.0.8', '>')) {
51+
throw new InvalidArgumentException('Failed to create memcache client due to missing "memcache" extension or version <3.0.9.');
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+
}
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 %s DSN: %s (expects "%s://example.com[:1234][?weight=<int>]")', $scheme, $dsn, $scheme));
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, $scheme);
97+
}
98+
99+
private function dsnSanitize(array $srv, $scheme)
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 %s DSN host: %s (expects resolvable IP or hostname)', $scheme, $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 %s DSN weight: %s (expects int >=1 and <= 100)', $scheme, $srv['weight']));
110+
}
111+
112+
return array($host, $srv['port'], $weight);
113+
}
114+
}

0 commit comments

Comments
 (0)