Index: tests/TestConfiguration.php.dist =================================================================== --- tests/TestConfiguration.php.dist (revision 23123) +++ tests/TestConfiguration.php.dist (working copy) @@ -56,11 +56,13 @@ /** * Zend_Cache * - * TESTS_ZEND_CACHE_SQLITE_ENABLED => sqlite extension has to be enabled - * TESTS_ZEND_CACHE_APC_ENABLED => apc extension has to be enabled - * TESTS_ZEND_CACHE_MEMCACHED_ENABLED => memcache extension has to be enabled and - * a memcached server has to be available - * TESTS_ZEND_CACHE_XCACHE_ENABLED => xcache extension has to be enabled + * TESTS_ZEND_CACHE_SQLITE_ENABLED => sqlite extension has to be enabled + * TESTS_ZEND_CACHE_APC_ENABLED => apc extension has to be enabled + * TESTS_ZEND_CACHE_MEMCACHED_ENABLED => memcache extension has to be enabled and + * a memcached server has to be available + * TESTS_ZEND_CACHE_MEMCACHED2_ENABLED => memcached extension has to be enabled and + * a memcached server has to be available + * TESTS_ZEND_CACHE_XCACHE_ENABLED => xcache extension has to be enabled */ define('TESTS_ZEND_CACHE_SQLITE_ENABLED', false); define('TESTS_ZEND_CACHE_APC_ENABLED', false); @@ -72,6 +74,10 @@ define('TESTS_ZEND_CACHE_MEMCACHED_HOST', '127.0.0.1'); define('TESTS_ZEND_CACHE_MEMCACHED_PORT', 11211); define('TESTS_ZEND_CACHE_MEMCACHED_PERSISTENT', true); +define('TESTS_ZEND_CACHE_MEMCACHED2_ENABLED', false); +define('TESTS_ZEND_CACHE_MEMCACHED2_HOST', '127.0.0.1'); +define('TESTS_ZEND_CACHE_MEMCACHED2_PORT', 11211); +define('TESTS_ZEND_CACHE_MEMCACHED2_WEIGHT', 1); /** * Zend_Cloud online tests Index: tests/Zend/Cache/SkipTests.php =================================================================== --- tests/Zend/Cache/SkipTests.php (revision 23123) +++ tests/Zend/Cache/SkipTests.php (working copy) @@ -98,6 +98,19 @@ * @license http://framework.zend.com/license/new-bsd New BSD License * @group Zend_Cache */ +class Zend_Cache_Memcached2BackendTest_SkipTests extends Zend_Cache_BackendTest_SkipTests +{ +} + + +/** + * @category Zend + * @package Zend_Cache + * @subpackage UnitTests + * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @group Zend_Cache + */ class Zend_Cache_SqliteBackendTest_SkipTests extends Zend_Cache_BackendTest_SkipTests { } Index: tests/Zend/Cache/AllTests.php =================================================================== --- tests/Zend/Cache/AllTests.php (revision 23123) +++ tests/Zend/Cache/AllTests.php (working copy) @@ -35,6 +35,7 @@ require_once 'Zend/Cache/FunctionFrontendTest.php'; require_once 'Zend/Cache/ManagerTest.php'; require_once 'Zend/Cache/MemcachedBackendTest.php'; +require_once 'Zend/Cache/Memcached2BackendTest.php'; require_once 'Zend/Cache/OutputFrontendTest.php'; require_once 'Zend/Cache/PageFrontendTest.php'; require_once 'Zend/Cache/SkipTests.php'; @@ -150,6 +151,31 @@ } /* + * Check if Memcached2 tests are enabled, and if extension is available. + */ + if (!defined('TESTS_ZEND_CACHE_MEMCACHED2_ENABLED') || + constant('TESTS_ZEND_CACHE_MEMCACHED2_ENABLED') === false) { + $skipTest = new Zend_Cache_Memcached2BackendTest_SkipTests(); + $skipTest->message = 'Tests are not enabled in TestConfiguration.php'; + $suite->addTest($skipTest); + } else if (!extension_loaded('memcached')) { + $skipTest = new Zend_Cache_Memcached2BackendTest_SkipTests(); + $skipTest->message = "Extension 'Memcached' is not loaded"; + $suite->addTest($skipTest); + } else { + if (!defined('TESTS_ZEND_CACHE_MEMCACHED2_HOST')) { + define('TESTS_ZEND_CACHE_MEMCACHED2_HOST', '127.0.0.1'); + } + if (!defined('TESTS_ZEND_CACHE_MEMCACHED2_PORT')) { + define('TESTS_ZEND_CACHE_MEMCACHED2_PORT', 11211); + } + if (!defined('TESTS_ZEND_CACHE_MEMCACHED2_WEIGHT')) { + define('TESTS_ZEND_CACHE_MEMCACHED2_WEIGHT', 1); + } + $suite->addTestSuite('Zend_Cache_Memcached2BackendTest'); + } + + /* * Check if Zend Platform tests are enabled, and if extension is available. */ if (!defined('TESTS_ZEND_CACHE_PLATFORM_ENABLED') || Index: tests/Zend/Cache/Memcached2BackendTest.php =================================================================== --- tests/Zend/Cache/Memcached2BackendTest.php (revision 0) +++ tests/Zend/Cache/Memcached2BackendTest.php (revision 0) @@ -0,0 +1,178 @@ + TESTS_ZEND_CACHE_MEMCACHED2_HOST, + 'port' => TESTS_ZEND_CACHE_MEMCACHED2_PORT, + 'weight' => TESTS_ZEND_CACHE_MEMCACHED2_WEIGHT + ); + $serverFail = array( + 'host' => 'not.exist', + 'port' => TESTS_ZEND_CACHE_MEMCACHED2_PORT, + 'weight' => TESTS_ZEND_CACHE_MEMCACHED2_WEIGHT + ); + $options = array( + 'servers' => array($serverValid, $serverFail) + ); + $this->_instance = new Zend_Cache_Backend_Memcached2($options); + parent::setUp($notag); + } + + public function tearDown() + { + parent::tearDown(); + unset($this->_instance); + // We have to wait after a memcached flush + sleep(1); + } + + public function testConstructorCorrectCall() + { + $test = new Zend_Cache_Backend_Memcached2(); + } + + public function testCleanModeOld() + { + $this->_instance->setDirectives(array('logging' => false)); + $this->_instance->clean('old'); + // do nothing, just to see if an error occured + $this->_instance->setDirectives(array('logging' => true)); + } + + public function testCleanModeMatchingTags() + { + $this->_instance->setDirectives(array('logging' => false)); + $this->_instance->clean('matchingTag', array('tag1')); + // do nothing, just to see if an error occured + $this->_instance->setDirectives(array('logging' => true)); + } + + public function testCleanModeNotMatchingTags() + { + $this->_instance->setDirectives(array('logging' => false)); + $this->_instance->clean('notMatchingTag', array('tag1')); + // do nothing, just to see if an error occured + $this->_instance->setDirectives(array('logging' => true)); + } + + public function testGetWithCompression() + { + $this->_instance->setOption('compression', true); + $this->testGetWithAnExistingCacheIdAndUTFCharacters(); + } + + public function testConstructorWithAnAlternativeSyntax() + { + $server = array( + 'host' => TESTS_ZEND_CACHE_MEMCACHED2_HOST, + 'port' => TESTS_ZEND_CACHE_MEMCACHED2_PORT, + 'weight' => TESTS_ZEND_CACHE_MEMCACHED2_WEIGHT + ); + $options = array( + 'servers' => $server + ); + $this->_instance = new Zend_Cache_Backend_Memcached2($options); + $this->testGetWithAnExistingCacheIdAndUTFCharacters(); + } + + // Because of limitations of this backend... + public function testGetWithAnExpiredCacheId() {} + public function testCleanModeMatchingTags2() {} + public function testCleanModeNotMatchingTags2() {} + public function testCleanModeNotMatchingTags3() {} + public function testSaveCorrectCall() + { + $this->_instance->setDirectives(array('logging' => false)); + parent::testSaveCorrectCall(); + $this->_instance->setDirectives(array('logging' => true)); + } + + public function testSaveWithNullLifeTime() + { + $this->_instance->setDirectives(array('logging' => false)); + parent::testSaveWithNullLifeTime(); + $this->_instance->setDirectives(array('logging' => true)); + } + + public function testSaveWithSpecificLifeTime() + { + + $this->_instance->setDirectives(array('logging' => false)); + parent::testSaveWithSpecificLifeTime(); + $this->_instance->setDirectives(array('logging' => true)); + } + + public function testGetMetadatas($notag = false) + { + parent::testGetMetadatas(true); + } + + public function testGetFillingPercentage() + { + $this->_instance->setDirectives(array('logging' => false)); + parent::testGetFillingPercentage(); + } + + public function testGetFillingPercentageOnEmptyBackend() + { + $this->_instance->setDirectives(array('logging' => false)); + parent::testGetFillingPercentageOnEmptyBackend(); + } + +} + + Property changes on: tests/Zend/Cache/Memcached2BackendTest.php ___________________________________________________________________ Added: svn:keywords + "Date Author Revision" Added: svn:eol-style + native Index: library/Zend/Cache/Backend/Memcached2.php =================================================================== --- library/Zend/Cache/Backend/Memcached2.php (revision 0) +++ library/Zend/Cache/Backend/Memcached2.php (revision 0) @@ -0,0 +1,512 @@ + (array) servers : + * an array of memcached server ; each memcached server is described by an associative array : + * 'host' => (string) : the name of the memcached server + * 'port' => (int) : the port of the memcached server + * 'weight' => (int) : number of buckets to create for this server which in turn control its + * probability of it being selected. The probability is relative to the total + * weight of all servers. + * =====> (array) client : + * an array of memcached client options ; the memcached client is described by an associative array : + * 'binary_protocol' => (bool) : Enable the use of the binary protocol. + * 'buffer_writes' => (bool) : Enables or disables buffered I/O. + * 'cache_lookups' => (bool) : Enables or disables caching of DNS lookups. + * 'compression ' => (bool) : Enables or disables payload compression. + * 'connect_timeout' => (int) : In non-blocking mode this set the value of the timeout during + * socket connection, in milliseconds. + * 'distribution' => (int) : Specifies the method of distributing item keys to the servers. + * 'hash' => (int) : Specifies the hashing algorithm used for the item keys. + * 'libketama_compatible' => (bool) Enables or disables compatibility with libketama-like behavior. + * 'no_block' => (bool) Enables or disables asynchronous I/O. + * 'poll_timeout' => (int) Timeout for connection polling, in milliseconds. + * 'recv_timeout' => (int) Socket reading timeout, in microseconds. + * 'retry_timeout' => (int) The amount of time, in seconds, to wait until retrying a failed connection attempt. + * 'send_timeout' => (int) Socket sending timeout, in microseconds. + * 'serializer' => (int) Specifies the serializer to use for serializing non-scalar values. + * 'server_failure_limit' => (int) Specifies the failure limit for server connection attempts. + * 'tcp_nodelay' => (bool) Enables or disables the no-delay feature for connecting sockets. + * + * @var array available options + */ + protected $_options = array( + 'servers' => array(array( + 'host' => self::DEFAULT_HOST, + 'port' => self::DEFAULT_PORT, + 'weight' => self::DEFAULT_WEIGHT, + )), + 'client' => array( + 'binary_protocol' => self::DEFAULT_BINARY_PROTOCOL, + 'buffer_writes' => self::DEFAULT_BUFFER_WRITES, + 'cache_lookups' => self::DEFAULT_CACHE_LOOKUPS, + 'compression' => self::DEFAULT_COMPRESSION, + 'connect_timeout' => self::DEFAULT_CONNECT_TIMEOUT, + 'distribution' => self::DEFAULT_DISTRIBUTION, + 'hash' => self::DEFAULT_HASH, + 'libketama_compatible' => self::DEFAULT_LIBKETAMA_COMPATIBLE, + 'no_block' => self::DEFAULT_NO_BLOCK, + 'poll_timeout' => self::DEFAULT_POLL_TIMEOUT, + 'recv_timeout' => self::DEFAULT_RECV_TIMEOUT, + 'retry_timeout' => self::DEFAULT_RETRY_TIMEOUT, + 'send_timeout' => self::DEFAULT_SEND_TIMEOUT, + 'serializer' => self::DEFAULT_SERIALIZER, + 'server_failure_limit' => self::DEFAULT_SERVER_FAILURE_LIMIT, + 'tcp_nodelay' => self::DEFAULT_TCP_NODELAY, + + ) + ); + + /** + * Memcached object + * + * @var mixed memcached object + */ + protected $_memcache = null; + + /** + * Constructor + * + * @param array $options associative array of options + * @throws Zend_Cache_Exception + * @return void + */ + public function __construct(array $options = array()) + { + if (!extension_loaded('memcached')) { + Zend_Cache::throwException('The memcached extension must be loaded for using this backend !'); + } + + parent::__construct($options); + if (isset($this->_options['servers'])) { + $value = $this->_options['servers']; + if (isset($value['host'])) { + // in this case, $value seems to be a simple associative array (one server only) + $value = array(0 => $value); // let's transform it into a classical array of associative arrays + } + $this->setOption('servers', $value); + } + $this->_memcache = new Memcached; + + // setup memcached client options + foreach ($this->_options['client'] as $option => $value) { + $option = 'OPT_' . strtoupper($option); + $this->_memcache->setOption(constant('Memcached::' . $option), $value); + } + + // setup memcached servers + $servers = array(); + foreach ($this->_options['servers'] as $server) { + if (!array_key_exists('port', $server)) { + $server['port'] = self::DEFAULT_PORT; + } + if (!array_key_exists('weight', $server)) { + $server['weight'] = self::DEFAULT_WEIGHT; + } + + $servers[] = array($server['host'], $server['port'], $server['weight']); + } + $this->_memcache->addServers($servers); + } + + /** + * Test if a cache is available for the given id and (if yes) return it (false else) + * + * @param string $id Cache id + * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested + * @return string|false cached datas + */ + public function load($id, $doNotTestCacheValidity = false) + { + $tmp = $this->_memcache->get($id); + if (is_array($tmp) && isset($tmp[0])) { + return $tmp[0]; + } + return false; + } + + /** + * Test if a cache is available or not (for the given id) + * + * @param string $id Cache id + * @return mixed|false (a cache is not available) or "last modified" timestamp (int) of the available cache record + */ + public function test($id) + { + $tmp = $this->_memcache->get($id); + if (is_array($tmp) && isset($tmp[1])) { + return $tmp[1]; + } + return false; + } + + /** + * Save some string datas into a cache record + * + * Note : $data is always "string" (serialization is done by the + * core not by the backend) + * + * @param string $data Datas to cache + * @param string $id Cache id + * @param array $tags Array of strings, the cache record will be tagged by each string entry + * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime) + * @return boolean True if no problem + */ + public function save($data, $id, $tags = array(), $specificLifetime = false) + { + $lifetime = $this->getLifetime($specificLifetime); + + // ZF-8856: using set because add needs a second request if item already exists + $result = @$this->_memcache->set($id, array($data, time(), $lifetime), $lifetime); + + if (count($tags) > 0) { + $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_MEMCACHED_BACKEND); + } + + return $result; + } + + /** + * Remove a cache record + * + * @param string $id Cache id + * @return boolean True if no problem + */ + public function remove($id) + { + return $this->_memcache->delete($id); + } + + /** + * Clean some cache records + * + * Available modes are : + * 'all' (default) => remove all cache entries ($tags is not used) + * 'old' => unsupported + * 'matchingTag' => unsupported + * 'notMatchingTag' => unsupported + * 'matchingAnyTag' => unsupported + * + * @param string $mode Clean mode + * @param array $tags Array of tags + * @throws Zend_Cache_Exception + * @return boolean True if no problem + */ + public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) + { + switch ($mode) { + case Zend_Cache::CLEANING_MODE_ALL: + return $this->_memcache->flush(); + break; + case Zend_Cache::CLEANING_MODE_OLD: + $this->_log("Zend_Cache_Backend_Memcached2::clean() : CLEANING_MODE_OLD is unsupported by the Memcached2 backend"); + break; + case Zend_Cache::CLEANING_MODE_MATCHING_TAG: + case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG: + case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG: + $this->_log(self::TAGS_UNSUPPORTED_BY_CLEAN_OF_MEMCACHED_BACKEND); + break; + default: + Zend_Cache::throwException('Invalid mode for clean() method'); + break; + } + } + + /** + * Return true if the automatic cleaning is available for the backend + * + * @return boolean + */ + public function isAutomaticCleaningAvailable() + { + return false; + } + + /** + * Set the frontend directives + * + * @param array $directives Assoc of directives + * @throws Zend_Cache_Exception + * @return void + */ + public function setDirectives($directives) + { + parent::setDirectives($directives); + $lifetime = $this->getLifetime(false); + if ($lifetime > 2592000) { + // #ZF-3490 : For the memcached backend, there is a lifetime limit of 30 days (2592000 seconds) + $this->_log('memcached backend has a limit of 30 days (2592000 seconds) for the lifetime'); + } + if ($lifetime === null) { + // #ZF-4614 : we tranform null to zero to get the maximal lifetime + parent::setDirectives(array('lifetime' => 0)); + } + } + + /** + * Return an array of stored cache ids + * + * @return array array of stored cache ids (string) + */ + public function getIds() + { + $this->_log("Zend_Cache_Backend_Memcached2::save() : getting the list of cache ids is unsupported by the Memcache backend"); + return array(); + } + + /** + * Return an array of stored tags + * + * @return array array of stored tags (string) + */ + public function getTags() + { + $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_MEMCACHED_BACKEND); + return array(); + } + + /** + * Return an array of stored cache ids which match given tags + * + * In case of multiple tags, a logical AND is made between tags + * + * @param array $tags array of tags + * @return array array of matching cache ids (string) + */ + public function getIdsMatchingTags($tags = array()) + { + $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_MEMCACHED_BACKEND); + return array(); + } + + /** + * Return an array of stored cache ids which don't match given tags + * + * In case of multiple tags, a logical OR is made between tags + * + * @param array $tags array of tags + * @return array array of not matching cache ids (string) + */ + public function getIdsNotMatchingTags($tags = array()) + { + $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_MEMCACHED_BACKEND); + return array(); + } + + /** + * Return an array of stored cache ids which match any given tags + * + * In case of multiple tags, a logical AND is made between tags + * + * @param array $tags array of tags + * @return array array of any matching cache ids (string) + */ + public function getIdsMatchingAnyTags($tags = array()) + { + $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_MEMCACHED_BACKEND); + return array(); + } + + /** + * Return the filling percentage of the backend storage + * + * @throws Zend_Cache_Exception + * @return int integer between 0 and 100 + */ + public function getFillingPercentage() + { + $mems = $this->_memcache->getStats(); + if ($mems === false) { + return 0; + } + + $memSize = null; + $memUsed = null; + foreach ($mems as $key => $mem) { + if ($mem === false) { + $this->_log('can\'t get stat from ' . $key); + continue; + } + + $eachSize = $mem['limit_maxbytes']; + $eachUsed = $mem['bytes']; + if ($eachUsed > $eachSize) { + $eachUsed = $eachSize; + } + + $memSize += $eachSize; + $memUsed += $eachUsed; + } + + if ($memSize === null || $memUsed === null) { + Zend_Cache::throwException('Can\'t get filling percentage'); + } + + return ((int) (100. * ($memUsed / $memSize))); + } + + /** + * Return an array of metadatas for the given cache id + * + * The array must include these keys : + * - expire : the expire timestamp + * - tags : a string array of tags + * - mtime : timestamp of last modification time + * + * @param string $id cache id + * @return array array of metadatas (false if the cache id is not found) + */ + public function getMetadatas($id) + { + $tmp = $this->_memcache->get($id); + if (is_array($tmp)) { + $data = $tmp[0]; + $mtime = $tmp[1]; + if (!isset($tmp[2])) { + // because this record is only with 1.7 release + // if old cache records are still there... + return false; + } + $lifetime = $tmp[2]; + return array( + 'expire' => $mtime + $lifetime, + 'tags' => array(), + 'mtime' => $mtime + ); + } + return false; + } + + /** + * Give (if possible) an extra lifetime to the given cache id + * + * @param string $id cache id + * @param int $extraLifetime + * @return boolean true if ok + */ + public function touch($id, $extraLifetime) + { + $tmp = $this->_memcache->get($id); + if (is_array($tmp)) { + $data = $tmp[0]; + $mtime = $tmp[1]; + if (!isset($tmp[2])) { + // because this record is only with 1.7 release + // if old cache records are still there... + return false; + } + $lifetime = $tmp[2]; + $newLifetime = $lifetime - (time() - $mtime) + $extraLifetime; + if ($newLifetime <=0) { + return false; + } + // #ZF-5702 : we try replace() first becase set() seems to be slower + if (!($result = $this->_memcache->replace($id, array($data, time(), $newLifetime), $newLifetime))) { + $result = $this->_memcache->set($id, array($data, time(), $newLifetime), $newLifetime); + } + return $result; + } + return false; + } + + /** + * Return an associative array of capabilities (booleans) of the backend + * + * The array must include these keys : + * - automatic_cleaning (is automating cleaning necessary) + * - tags (are tags supported) + * - expired_read (is it possible to read expired cache records + * (for doNotTestCacheValidity option for example)) + * - priority does the backend deal with priority when saving + * - infinite_lifetime (is infinite lifetime can work with this backend) + * - get_list (is it possible to get the list of cache ids and the complete list of tags) + * + * @return array associative of with capabilities + */ + public function getCapabilities() + { + return array( + 'automatic_cleaning' => false, + 'tags' => false, + 'expired_read' => false, + 'priority' => false, + 'infinite_lifetime' => false, + 'get_list' => false + ); + } + +} Property changes on: library/Zend/Cache/Backend/Memcached2.php ___________________________________________________________________ Added: svn:keywords + "Date Author Revision" Added: svn:eol-style + native