Skip to content

Commit 9bcb689

Browse files
hrachdg
authored andcommitted
added NewMemcachedStorage using memcached extension [Closes #38]
1 parent 52497ab commit 9bcb689

13 files changed

+474
-22
lines changed

Diff for: src/Caching/Storages/NewMemcachedStorage.php

+190
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
<?php
2+
3+
/**
4+
* This file is part of the Nette Framework (https://nette.org)
5+
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6+
*/
7+
8+
namespace Nette\Caching\Storages;
9+
10+
use Nette;
11+
use Nette\Caching\Cache;
12+
13+
14+
/**
15+
* Memcached storage using memcached extension.
16+
*/
17+
class NewMemcachedStorage extends Nette\Object implements Nette\Caching\IStorage
18+
{
19+
/** @internal cache structure */
20+
const META_CALLBACKS = 'callbacks',
21+
META_DATA = 'data',
22+
META_DELTA = 'delta';
23+
24+
/** @var \Memcached */
25+
private $memcached;
26+
27+
/** @var string */
28+
private $prefix;
29+
30+
/** @var IJournal */
31+
private $journal;
32+
33+
34+
/**
35+
* Checks if Memcached extension is available.
36+
* @return bool
37+
*/
38+
public static function isAvailable()
39+
{
40+
return extension_loaded('memcached');
41+
}
42+
43+
44+
public function __construct($host = 'localhost', $port = 11211, $prefix = '', IJournal $journal = NULL)
45+
{
46+
if (!static::isAvailable()) {
47+
throw new Nette\NotSupportedException("PHP extension 'memcached' is not loaded.");
48+
}
49+
50+
$this->prefix = $prefix;
51+
$this->journal = $journal;
52+
$this->memcached = new \Memcached;
53+
if ($host) {
54+
$this->addServer($host, $port);
55+
}
56+
}
57+
58+
59+
public function addServer($host = 'localhost', $port = 11211)
60+
{
61+
if ($this->memcached->addServer($host, $port, 1) === FALSE) {
62+
$error = error_get_last();
63+
throw new Nette\InvalidStateException("Memcached::addServer(): $error[message].");
64+
}
65+
}
66+
67+
68+
/**
69+
* @return \Memcached
70+
*/
71+
public function getConnection()
72+
{
73+
return $this->memcached;
74+
}
75+
76+
77+
/**
78+
* Read from cache.
79+
* @param string key
80+
* @return mixed|NULL
81+
*/
82+
public function read($key)
83+
{
84+
$key = urlencode($this->prefix . $key);
85+
$meta = $this->memcached->get($key);
86+
if (!$meta) {
87+
return NULL;
88+
}
89+
90+
// meta structure:
91+
// array(
92+
// data => stored data
93+
// delta => relative (sliding) expiration
94+
// callbacks => array of callbacks (function, args)
95+
// )
96+
97+
// verify dependencies
98+
if (!empty($meta[self::META_CALLBACKS]) && !Cache::checkCallbacks($meta[self::META_CALLBACKS])) {
99+
$this->memcached->delete($key, 0);
100+
return NULL;
101+
}
102+
103+
if (!empty($meta[self::META_DELTA])) {
104+
$this->memcached->replace($key, $meta, $meta[self::META_DELTA] + time());
105+
}
106+
107+
return $meta[self::META_DATA];
108+
}
109+
110+
111+
/**
112+
* Prevents item reading and writing. Lock is released by write() or remove().
113+
* @param string key
114+
* @return void
115+
*/
116+
public function lock($key)
117+
{
118+
}
119+
120+
121+
/**
122+
* Writes item into the cache.
123+
* @param string key
124+
* @param mixed data
125+
* @param array dependencies
126+
* @return void
127+
*/
128+
public function write($key, $data, array $dp)
129+
{
130+
if (isset($dp[Cache::ITEMS])) {
131+
throw new Nette\NotSupportedException('Dependent items are not supported by MemcachedStorage.');
132+
}
133+
134+
$key = urlencode($this->prefix . $key);
135+
$meta = [
136+
self::META_DATA => $data,
137+
];
138+
139+
$expire = 0;
140+
if (isset($dp[Cache::EXPIRATION])) {
141+
$expire = (int) $dp[Cache::EXPIRATION];
142+
if (!empty($dp[Cache::SLIDING])) {
143+
$meta[self::META_DELTA] = $expire; // sliding time
144+
}
145+
}
146+
147+
if (isset($dp[Cache::CALLBACKS])) {
148+
$meta[self::META_CALLBACKS] = $dp[Cache::CALLBACKS];
149+
}
150+
151+
if (isset($dp[Cache::TAGS]) || isset($dp[Cache::PRIORITY])) {
152+
if (!$this->journal) {
153+
throw new Nette\InvalidStateException('CacheJournal has not been provided.');
154+
}
155+
$this->journal->write($key, $dp);
156+
}
157+
158+
$this->memcached->set($key, $meta, $expire);
159+
}
160+
161+
162+
/**
163+
* Removes item from the cache.
164+
* @param string key
165+
* @return void
166+
*/
167+
public function remove($key)
168+
{
169+
$this->memcached->delete(urlencode($this->prefix . $key), 0);
170+
}
171+
172+
173+
/**
174+
* Removes items from the cache by conditions & garbage collector.
175+
* @param array conditions
176+
* @return void
177+
*/
178+
public function clean(array $conditions)
179+
{
180+
if (!empty($conditions[Cache::ALL])) {
181+
$this->memcached->flush();
182+
183+
} elseif ($this->journal) {
184+
foreach ($this->journal->clean($conditions) as $entry) {
185+
$this->memcached->delete($entry, 0);
186+
}
187+
}
188+
}
189+
190+
}

Diff for: tests/Storages/Memcached.expiration.phpt

+3-1
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ if (!MemcachedStorage::isAvailable()) {
1616
Tester\Environment::skip('Requires PHP extension Memcache.');
1717
}
1818

19+
Tester\Environment::lock('memcached-expiration', TEMP_DIR);
1920

20-
$key = 'nette-expiration-key';
21+
22+
$key = 'nette-memcache-expiration-key';
2123
$value = 'rulez';
2224

2325
$cache = new Cache(new MemcachedStorage('localhost'));

Diff for: tests/Storages/Memcached.files.phpt

+4-2
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,16 @@ if (!MemcachedStorage::isAvailable()) {
1616
Tester\Environment::skip('Requires PHP extension Memcache.');
1717
}
1818

19+
Tester\Environment::lock('memcached-files', TEMP_DIR);
1920

20-
$key = 'nette-files-key';
21+
22+
$key = 'nette-memcache-files-key';
2123
$value = 'rulez';
2224

2325
$cache = new Cache(new MemcachedStorage('localhost'));
2426

2527

26-
$dependentFile = TEMP_DIR . '/spec.file';
28+
$dependentFile = TEMP_DIR . '/spec-memcache.file';
2729
@unlink($dependentFile);
2830

2931
// Writing cache...

Diff for: tests/Storages/Memcached.priority.phpt

+11-9
Original file line numberDiff line numberDiff line change
@@ -17,33 +17,35 @@ if (!MemcachedStorage::isAvailable()) {
1717
Tester\Environment::skip('Requires PHP extension Memcache.');
1818
}
1919

20+
Tester\Environment::lock('memcached-priority', TEMP_DIR);
2021

21-
$storage = new MemcachedStorage('localhost', 11211, '', new SQLiteJournal(TEMP_DIR . '/journal.s3db'));
22+
23+
$storage = new MemcachedStorage('localhost', 11211, '', new SQLiteJournal(TEMP_DIR . '/journal-memcache.s3db'));
2224
$cache = new Cache($storage);
2325

2426

2527
// Writing cache...
26-
$cache->save('nette-priority-key1', 'value1', [
28+
$cache->save('nette-memcache-priority-key1', 'value1', [
2729
Cache::PRIORITY => 100,
2830
]);
2931

30-
$cache->save('nette-priority-key2', 'value2', [
32+
$cache->save('nette-memcache-priority-key2', 'value2', [
3133
Cache::PRIORITY => 200,
3234
]);
3335

34-
$cache->save('nette-priority-key3', 'value3', [
36+
$cache->save('nette-memcache-priority-key3', 'value3', [
3537
Cache::PRIORITY => 300,
3638
]);
3739

38-
$cache->save('nette-priority-key4', 'value4');
40+
$cache->save('nette-memcache-priority-key4', 'value4');
3941

4042

4143
// Cleaning by priority...
4244
$cache->clean([
4345
Cache::PRIORITY => '200',
4446
]);
4547

46-
Assert::null($cache->load('nette-priority-key1'));
47-
Assert::null($cache->load('nette-priority-key2'));
48-
Assert::truthy($cache->load('nette-priority-key3'));
49-
Assert::truthy($cache->load('nette-priority-key4'));
48+
Assert::null($cache->load('nette-memcache-priority-key1'));
49+
Assert::null($cache->load('nette-memcache-priority-key2'));
50+
Assert::truthy($cache->load('nette-memcache-priority-key3'));
51+
Assert::truthy($cache->load('nette-memcache-priority-key4'));

Diff for: tests/Storages/Memcached.sliding.phpt

+3-1
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ if (!MemcachedStorage::isAvailable()) {
1616
Tester\Environment::skip('Requires PHP extension Memcache.');
1717
}
1818

19+
Tester\Environment::lock('memcached-sliding', TEMP_DIR);
1920

20-
$key = 'nette-sliding-key';
21+
22+
$key = 'nette-memcache-sliding-key';
2123
$value = 'rulez';
2224

2325
$cache = new Cache(new MemcachedStorage('localhost'));

Diff for: tests/Storages/Memcached.tags.phpt

+11-9
Original file line numberDiff line numberDiff line change
@@ -17,33 +17,35 @@ if (!MemcachedStorage::isAvailable()) {
1717
Tester\Environment::skip('Requires PHP extension Memcache.');
1818
}
1919

20+
Tester\Environment::lock('memcached-tags', TEMP_DIR);
2021

21-
$storage = new MemcachedStorage('localhost', 11211, '', new SQLiteJournal(TEMP_DIR . '/journal.s3db'));
22+
23+
$storage = new MemcachedStorage('localhost', 11211, '', new SQLiteJournal(TEMP_DIR . '/journal-memcache.s3db'));
2224
$cache = new Cache($storage);
2325

2426

2527
// Writing cache...
26-
$cache->save('nette-tags-key1', 'value1', [
28+
$cache->save('nette-memcache-tags-key1', 'value1', [
2729
Cache::TAGS => ['one', 'two'],
2830
]);
2931

30-
$cache->save('nette-tags-key2', 'value2', [
32+
$cache->save('nette-memcache-tags-key2', 'value2', [
3133
Cache::TAGS => ['one', 'three'],
3234
]);
3335

34-
$cache->save('nette-tags-key3', 'value3', [
36+
$cache->save('nette-memcache-tags-key3', 'value3', [
3537
Cache::TAGS => ['two', 'three'],
3638
]);
3739

38-
$cache->save('nette-tags-key4', 'value4');
40+
$cache->save('nette-memcache-tags-key4', 'value4');
3941

4042

4143
// Cleaning by tags...
4244
$cache->clean([
4345
Cache::TAGS => 'one',
4446
]);
4547

46-
Assert::null($cache->load('nette-tags-key1'));
47-
Assert::null($cache->load('nette-tags-key2'));
48-
Assert::truthy($cache->load('nette-tags-key3'));
49-
Assert::truthy($cache->load('nette-tags-key4'));
48+
Assert::null($cache->load('nette-memcache-tags-key1'));
49+
Assert::null($cache->load('nette-memcache-tags-key2'));
50+
Assert::truthy($cache->load('nette-memcache-tags-key3'));
51+
Assert::truthy($cache->load('nette-memcache-tags-key4'));

Diff for: tests/Storages/NewMemcached.expiration.phpt

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
/**
4+
* Test: Nette\Caching\Storages\NewMemcachedStorage expiration test.
5+
*/
6+
7+
use Nette\Caching\Storages\NewMemcachedStorage;
8+
use Nette\Caching\Cache;
9+
use Tester\Assert;
10+
11+
12+
require __DIR__ . '/../bootstrap.php';
13+
14+
15+
if (!NewMemcachedStorage::isAvailable()) {
16+
Tester\Environment::skip('Requires PHP extension Memcached.');
17+
}
18+
19+
Tester\Environment::lock('memcached-expiration', TEMP_DIR);
20+
21+
22+
$key = 'nette-memcached-expiration-key';
23+
$value = 'rulez';
24+
25+
$cache = new Cache(new NewMemcachedStorage('localhost'));
26+
27+
28+
// Writing cache...
29+
$cache->save($key, $value, [
30+
Cache::EXPIRATION => time() + 3,
31+
]);
32+
33+
34+
// Sleeping 1 second
35+
sleep(1);
36+
Assert::truthy($cache->load($key));
37+
38+
39+
// Sleeping 3 seconds
40+
sleep(3);
41+
Assert::null($cache->load($key));

0 commit comments

Comments
 (0)