From 0cb5c2c91e92a155ca5c1700479476b80c30dece Mon Sep 17 00:00:00 2001 From: hrach Date: Thu, 18 Feb 2016 10:34:28 +0100 Subject: [PATCH] added NewMemcachedStorage using memcached extension [Closes #38] --- src/Caching/Storages/NewMemcachedStorage.php | 190 +++++++++++++++++++ tests/Storages/Memcached.expiration.phpt | 4 +- tests/Storages/Memcached.files.phpt | 6 +- tests/Storages/Memcached.priority.phpt | 17 +- tests/Storages/Memcached.sliding.phpt | 4 +- tests/Storages/Memcached.tags.phpt | 17 +- tests/Storages/NewMemcached.expiration.phpt | 41 ++++ tests/Storages/NewMemcached.files.phpt | 61 ++++++ tests/Storages/NewMemcached.priority.phpt | 51 +++++ tests/Storages/NewMemcached.sliding.phpt | 46 +++++ tests/Storages/NewMemcached.tags.phpt | 51 +++++ tests/php-unix.ini | 1 + tests/php-win.ini | 1 + 13 files changed, 470 insertions(+), 20 deletions(-) create mode 100644 src/Caching/Storages/NewMemcachedStorage.php create mode 100644 tests/Storages/NewMemcached.expiration.phpt create mode 100644 tests/Storages/NewMemcached.files.phpt create mode 100644 tests/Storages/NewMemcached.priority.phpt create mode 100644 tests/Storages/NewMemcached.sliding.phpt create mode 100644 tests/Storages/NewMemcached.tags.phpt diff --git a/src/Caching/Storages/NewMemcachedStorage.php b/src/Caching/Storages/NewMemcachedStorage.php new file mode 100644 index 00000000..ca73d951 --- /dev/null +++ b/src/Caching/Storages/NewMemcachedStorage.php @@ -0,0 +1,190 @@ +prefix = $prefix; + $this->journal = $journal; + $this->memcached = new \Memcached; + if ($host) { + $this->addServer($host, $port); + } + } + + + public function addServer($host = 'localhost', $port = 11211) + { + if ($this->memcached->addServer($host, $port, 1) === FALSE) { + $error = error_get_last(); + throw new Nette\InvalidStateException("Memcached::addServer(): $error[message]."); + } + } + + + /** + * @return \Memcached + */ + public function getConnection() + { + return $this->memcached; + } + + + /** + * Read from cache. + * @param string key + * @return mixed|NULL + */ + public function read($key) + { + $key = urlencode($this->prefix . $key); + $meta = $this->memcached->get($key); + if (!$meta) { + return NULL; + } + + // meta structure: + // array( + // data => stored data + // delta => relative (sliding) expiration + // callbacks => array of callbacks (function, args) + // ) + + // verify dependencies + if (!empty($meta[self::META_CALLBACKS]) && !Cache::checkCallbacks($meta[self::META_CALLBACKS])) { + $this->memcached->delete($key, 0); + return NULL; + } + + if (!empty($meta[self::META_DELTA])) { + $this->memcached->replace($key, $meta, $meta[self::META_DELTA] + time()); + } + + return $meta[self::META_DATA]; + } + + + /** + * Prevents item reading and writing. Lock is released by write() or remove(). + * @param string key + * @return void + */ + public function lock($key) + { + } + + + /** + * Writes item into the cache. + * @param string key + * @param mixed data + * @param array dependencies + * @return void + */ + public function write($key, $data, array $dp) + { + if (isset($dp[Cache::ITEMS])) { + throw new Nette\NotSupportedException('Dependent items are not supported by MemcachedStorage.'); + } + + $key = urlencode($this->prefix . $key); + $meta = array( + self::META_DATA => $data, + ); + + $expire = 0; + if (isset($dp[Cache::EXPIRATION])) { + $expire = (int) $dp[Cache::EXPIRATION]; + if (!empty($dp[Cache::SLIDING])) { + $meta[self::META_DELTA] = $expire; // sliding time + } + } + + if (isset($dp[Cache::CALLBACKS])) { + $meta[self::META_CALLBACKS] = $dp[Cache::CALLBACKS]; + } + + if (isset($dp[Cache::TAGS]) || isset($dp[Cache::PRIORITY])) { + if (!$this->journal) { + throw new Nette\InvalidStateException('CacheJournal has not been provided.'); + } + $this->journal->write($key, $dp); + } + + $this->memcached->set($key, $meta, $expire); + } + + + /** + * Removes item from the cache. + * @param string key + * @return void + */ + public function remove($key) + { + $this->memcached->delete(urlencode($this->prefix . $key), 0); + } + + + /** + * Removes items from the cache by conditions & garbage collector. + * @param array conditions + * @return void + */ + public function clean(array $conditions) + { + if (!empty($conditions[Cache::ALL])) { + $this->memcached->flush(); + + } elseif ($this->journal) { + foreach ($this->journal->clean($conditions) as $entry) { + $this->memcached->delete($entry, 0); + } + } + } + +} diff --git a/tests/Storages/Memcached.expiration.phpt b/tests/Storages/Memcached.expiration.phpt index 2226cc9b..108232f1 100644 --- a/tests/Storages/Memcached.expiration.phpt +++ b/tests/Storages/Memcached.expiration.phpt @@ -16,8 +16,10 @@ if (!MemcachedStorage::isAvailable()) { Tester\Environment::skip('Requires PHP extension Memcache.'); } +Tester\Environment::lock('memcached-expiration', TEMP_DIR); -$key = 'nette-expiration-key'; + +$key = 'nette-memcache-expiration-key'; $value = 'rulez'; $cache = new Cache(new MemcachedStorage('localhost')); diff --git a/tests/Storages/Memcached.files.phpt b/tests/Storages/Memcached.files.phpt index bef02aca..28996136 100644 --- a/tests/Storages/Memcached.files.phpt +++ b/tests/Storages/Memcached.files.phpt @@ -16,14 +16,16 @@ if (!MemcachedStorage::isAvailable()) { Tester\Environment::skip('Requires PHP extension Memcache.'); } +Tester\Environment::lock('memcached-files', TEMP_DIR); -$key = 'nette-files-key'; + +$key = 'nette-memcache-files-key'; $value = 'rulez'; $cache = new Cache(new MemcachedStorage('localhost')); -$dependentFile = TEMP_DIR . '/spec.file'; +$dependentFile = TEMP_DIR . '/spec-memcache.file'; @unlink($dependentFile); // Writing cache... diff --git a/tests/Storages/Memcached.priority.phpt b/tests/Storages/Memcached.priority.phpt index 81e567ca..60fc78a4 100644 --- a/tests/Storages/Memcached.priority.phpt +++ b/tests/Storages/Memcached.priority.phpt @@ -17,25 +17,26 @@ if (!MemcachedStorage::isAvailable()) { Tester\Environment::skip('Requires PHP extension Memcache.'); } +Tester\Environment::lock('memcached-priority', TEMP_DIR); $storage = new MemcachedStorage('localhost', 11211, '', new FileJournal(TEMP_DIR)); $cache = new Cache($storage); // Writing cache... -$cache->save('nette-priority-key1', 'value1', array( +$cache->save('nette-memcache-priority-key1', 'value1', array( Cache::PRIORITY => 100, )); -$cache->save('nette-priority-key2', 'value2', array( +$cache->save('nette-memcache-priority-key2', 'value2', array( Cache::PRIORITY => 200, )); -$cache->save('nette-priority-key3', 'value3', array( +$cache->save('nette-memcache-priority-key3', 'value3', array( Cache::PRIORITY => 300, )); -$cache->save('nette-priority-key4', 'value4'); +$cache->save('nette-memcache-priority-key4', 'value4'); // Cleaning by priority... @@ -43,7 +44,7 @@ $cache->clean(array( Cache::PRIORITY => '200', )); -Assert::null($cache->load('nette-priority-key1')); -Assert::null($cache->load('nette-priority-key2')); -Assert::truthy($cache->load('nette-priority-key3')); -Assert::truthy($cache->load('nette-priority-key4')); +Assert::null($cache->load('nette-memcache-priority-key1')); +Assert::null($cache->load('nette-memcache-priority-key2')); +Assert::truthy($cache->load('nette-memcache-priority-key3')); +Assert::truthy($cache->load('nette-memcache-priority-key4')); diff --git a/tests/Storages/Memcached.sliding.phpt b/tests/Storages/Memcached.sliding.phpt index c96571bc..364cd915 100644 --- a/tests/Storages/Memcached.sliding.phpt +++ b/tests/Storages/Memcached.sliding.phpt @@ -16,8 +16,10 @@ if (!MemcachedStorage::isAvailable()) { Tester\Environment::skip('Requires PHP extension Memcache.'); } +Tester\Environment::lock('memcached-sliding', TEMP_DIR); -$key = 'nette-sliding-key'; + +$key = 'nette-memcache-sliding-key'; $value = 'rulez'; $cache = new Cache(new MemcachedStorage('localhost')); diff --git a/tests/Storages/Memcached.tags.phpt b/tests/Storages/Memcached.tags.phpt index fff5b650..bef217ee 100644 --- a/tests/Storages/Memcached.tags.phpt +++ b/tests/Storages/Memcached.tags.phpt @@ -17,25 +17,26 @@ if (!MemcachedStorage::isAvailable()) { Tester\Environment::skip('Requires PHP extension Memcache.'); } +Tester\Environment::lock('memcached-tags', TEMP_DIR); $storage = new MemcachedStorage('localhost', 11211, '', new FileJournal(TEMP_DIR)); $cache = new Cache($storage); // Writing cache... -$cache->save('nette-tags-key1', 'value1', array( +$cache->save('nette-memcache-tags-key1', 'value1', array( Cache::TAGS => array('one', 'two'), )); -$cache->save('nette-tags-key2', 'value2', array( +$cache->save('nette-memcache-tags-key2', 'value2', array( Cache::TAGS => array('one', 'three'), )); -$cache->save('nette-tags-key3', 'value3', array( +$cache->save('nette-memcache-tags-key3', 'value3', array( Cache::TAGS => array('two', 'three'), )); -$cache->save('nette-tags-key4', 'value4'); +$cache->save('nette-memcache-tags-key4', 'value4'); // Cleaning by tags... @@ -43,7 +44,7 @@ $cache->clean(array( Cache::TAGS => 'one', )); -Assert::null($cache->load('nette-tags-key1')); -Assert::null($cache->load('nette-tags-key2')); -Assert::truthy($cache->load('nette-tags-key3')); -Assert::truthy($cache->load('nette-tags-key4')); +Assert::null($cache->load('nette-memcache-tags-key1')); +Assert::null($cache->load('nette-memcache-tags-key2')); +Assert::truthy($cache->load('nette-memcache-tags-key3')); +Assert::truthy($cache->load('nette-memcache-tags-key4')); diff --git a/tests/Storages/NewMemcached.expiration.phpt b/tests/Storages/NewMemcached.expiration.phpt new file mode 100644 index 00000000..f15fa97d --- /dev/null +++ b/tests/Storages/NewMemcached.expiration.phpt @@ -0,0 +1,41 @@ +save($key, $value, array( + Cache::EXPIRATION => time() + 3, +)); + + +// Sleeping 1 second +sleep(1); +Assert::truthy($cache->load($key)); + + +// Sleeping 3 seconds +sleep(3); +Assert::null($cache->load($key)); diff --git a/tests/Storages/NewMemcached.files.phpt b/tests/Storages/NewMemcached.files.phpt new file mode 100644 index 00000000..da5e6bb1 --- /dev/null +++ b/tests/Storages/NewMemcached.files.phpt @@ -0,0 +1,61 @@ +save($key, $value, array( + Cache::FILES => array( + __FILE__, + $dependentFile, + ), +)); + +Assert::truthy($cache->load($key)); + + +// Modifing dependent file +file_put_contents($dependentFile, 'a'); + +Assert::null($cache->load($key)); + + +// Writing cache... +$cache->save($key, $value, array( + Cache::FILES => $dependentFile, +)); + +Assert::truthy($cache->load($key)); + + +// Modifing dependent file +sleep(2); +file_put_contents($dependentFile, 'b'); +clearstatcache(); + +Assert::null($cache->load($key)); diff --git a/tests/Storages/NewMemcached.priority.phpt b/tests/Storages/NewMemcached.priority.phpt new file mode 100644 index 00000000..082d606d --- /dev/null +++ b/tests/Storages/NewMemcached.priority.phpt @@ -0,0 +1,51 @@ +save('nette-memcached-priority-key1', 'value1', array( + Cache::PRIORITY => 100, +)); + +$cache->save('nette-memcached-priority-key2', 'value2', array( + Cache::PRIORITY => 200, +)); + +$cache->save('nette-memcached-priority-key3', 'value3', array( + Cache::PRIORITY => 300, +)); + +$cache->save('nette-memcached-priority-key4', 'value4'); + + +// Cleaning by priority... +$cache->clean(array( + Cache::PRIORITY => '200', +)); + +Assert::null($cache->load('nette-memcached-priority-key1')); +Assert::null($cache->load('nette-memcached-priority-key2')); +Assert::truthy($cache->load('nette-memcached-priority-key3')); +Assert::truthy($cache->load('nette-memcached-priority-key4')); diff --git a/tests/Storages/NewMemcached.sliding.phpt b/tests/Storages/NewMemcached.sliding.phpt new file mode 100644 index 00000000..40da94f4 --- /dev/null +++ b/tests/Storages/NewMemcached.sliding.phpt @@ -0,0 +1,46 @@ +save($key, $value, array( + Cache::EXPIRATION => time() + 3, + Cache::SLIDING => TRUE, +)); + + +for ($i = 0; $i < 5; $i++) { + // Sleeping 1 second + sleep(1); + + Assert::truthy($cache->load($key)); + +} + +// Sleeping few seconds... +sleep(5); + +Assert::null($cache->load($key)); diff --git a/tests/Storages/NewMemcached.tags.phpt b/tests/Storages/NewMemcached.tags.phpt new file mode 100644 index 00000000..1d0792ce --- /dev/null +++ b/tests/Storages/NewMemcached.tags.phpt @@ -0,0 +1,51 @@ +save('nette-memcached-tags-key1', 'value1', array( + Cache::TAGS => array('one', 'two'), +)); + +$cache->save('nette-memcached-tags-key2', 'value2', array( + Cache::TAGS => array('one', 'three'), +)); + +$cache->save('nette-memcached-tags-key3', 'value3', array( + Cache::TAGS => array('two', 'three'), +)); + +$cache->save('nette-memcached-tags-key4', 'value4'); + + +// Cleaning by tags... +$cache->clean(array( + Cache::TAGS => 'one', +)); + +Assert::null($cache->load('nette-memcached-tags-key1')); +Assert::null($cache->load('nette-memcached-tags-key2')); +Assert::truthy($cache->load('nette-memcached-tags-key3')); +Assert::truthy($cache->load('nette-memcached-tags-key4')); diff --git a/tests/php-unix.ini b/tests/php-unix.ini index 4e0d57fd..21af7c42 100644 --- a/tests/php-unix.ini +++ b/tests/php-unix.ini @@ -1,6 +1,7 @@ [PHP] ;extension_dir = "./ext" extension=memcache.so +extension=memcached.so [Zend] ;zend_extension="./ext/zend_extension" diff --git a/tests/php-win.ini b/tests/php-win.ini index e48a932b..241381ad 100644 --- a/tests/php-win.ini +++ b/tests/php-win.ini @@ -2,6 +2,7 @@ extension_dir = "./ext" extension=php_mbstring.dll ;extension=php_memcache.dll +;extension=php_memcached.dll [Zend] ;zend_extension="./ext/php_xdebug-2.0.5-5.3-vc6.dll"