File indexing completed on 2025-01-19 05:20:57
0001 <?php 0002 /** 0003 * Zend Framework 0004 * 0005 * LICENSE 0006 * 0007 * This source file is subject to the new BSD license that is bundled 0008 * with this package in the file LICENSE.txt. 0009 * It is also available through the world-wide-web at this URL: 0010 * http://framework.zend.com/license/new-bsd 0011 * If you did not receive a copy of the license and are unable to 0012 * obtain it through the world-wide-web, please send an email 0013 * to license@zend.com so we can send you a copy immediately. 0014 * 0015 * @category Zend 0016 * @package Zend_Cache 0017 * @subpackage Zend_Cache_Backend 0018 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0019 * @license http://framework.zend.com/license/new-bsd New BSD License 0020 * @version $Id$ 0021 */ 0022 0023 0024 /** 0025 * @see Zend_Cache_Backend_Interface 0026 */ 0027 // require_once 'Zend/Cache/Backend/ExtendedInterface.php'; 0028 0029 /** 0030 * @see Zend_Cache_Backend 0031 */ 0032 // require_once 'Zend/Cache/Backend.php'; 0033 0034 0035 /** 0036 * @package Zend_Cache 0037 * @subpackage Zend_Cache_Backend 0038 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0039 * @license http://framework.zend.com/license/new-bsd New BSD License 0040 */ 0041 class Zend_Cache_Backend_Memcached extends Zend_Cache_Backend implements Zend_Cache_Backend_ExtendedInterface 0042 { 0043 /** 0044 * Default Values 0045 */ 0046 const DEFAULT_HOST = '127.0.0.1'; 0047 const DEFAULT_PORT = 11211; 0048 const DEFAULT_PERSISTENT = true; 0049 const DEFAULT_WEIGHT = 1; 0050 const DEFAULT_TIMEOUT = 1; 0051 const DEFAULT_RETRY_INTERVAL = 15; 0052 const DEFAULT_STATUS = true; 0053 const DEFAULT_FAILURE_CALLBACK = null; 0054 0055 /** 0056 * Log message 0057 */ 0058 const TAGS_UNSUPPORTED_BY_CLEAN_OF_MEMCACHED_BACKEND = 'Zend_Cache_Backend_Memcached::clean() : tags are unsupported by the Memcached backend'; 0059 const TAGS_UNSUPPORTED_BY_SAVE_OF_MEMCACHED_BACKEND = 'Zend_Cache_Backend_Memcached::save() : tags are unsupported by the Memcached backend'; 0060 0061 /** 0062 * Available options 0063 * 0064 * =====> (array) servers : 0065 * an array of memcached server ; each memcached server is described by an associative array : 0066 * 'host' => (string) : the name of the memcached server 0067 * 'port' => (int) : the port of the memcached server 0068 * 'persistent' => (bool) : use or not persistent connections to this memcached server 0069 * 'weight' => (int) : number of buckets to create for this server which in turn control its 0070 * probability of it being selected. The probability is relative to the total 0071 * weight of all servers. 0072 * 'timeout' => (int) : value in seconds which will be used for connecting to the daemon. Think twice 0073 * before changing the default value of 1 second - you can lose all the 0074 * advantages of caching if your connection is too slow. 0075 * 'retry_interval' => (int) : controls how often a failed server will be retried, the default value 0076 * is 15 seconds. Setting this parameter to -1 disables automatic retry. 0077 * 'status' => (bool) : controls if the server should be flagged as online. 0078 * 'failure_callback' => (callback) : Allows the user to specify a callback function to run upon 0079 * encountering an error. The callback is run before failover 0080 * is attempted. The function takes two parameters, the hostname 0081 * and port of the failed server. 0082 * 0083 * =====> (boolean) compression : 0084 * true if you want to use on-the-fly compression 0085 * 0086 * =====> (boolean) compatibility : 0087 * true if you use old memcache server or extension 0088 * 0089 * @var array available options 0090 */ 0091 protected $_options = array( 0092 'servers' => array(array( 0093 'host' => self::DEFAULT_HOST, 0094 'port' => self::DEFAULT_PORT, 0095 'persistent' => self::DEFAULT_PERSISTENT, 0096 'weight' => self::DEFAULT_WEIGHT, 0097 'timeout' => self::DEFAULT_TIMEOUT, 0098 'retry_interval' => self::DEFAULT_RETRY_INTERVAL, 0099 'status' => self::DEFAULT_STATUS, 0100 'failure_callback' => self::DEFAULT_FAILURE_CALLBACK 0101 )), 0102 'compression' => false, 0103 'compatibility' => false, 0104 ); 0105 0106 /** 0107 * Memcache object 0108 * 0109 * @var mixed memcache object 0110 */ 0111 protected $_memcache = null; 0112 0113 /** 0114 * Constructor 0115 * 0116 * @param array $options associative array of options 0117 * @throws Zend_Cache_Exception 0118 * @return void 0119 */ 0120 public function __construct(array $options = array()) 0121 { 0122 if (!extension_loaded('memcache')) { 0123 Zend_Cache::throwException('The memcache extension must be loaded for using this backend !'); 0124 } 0125 parent::__construct($options); 0126 if (isset($this->_options['servers'])) { 0127 $value= $this->_options['servers']; 0128 if (isset($value['host'])) { 0129 // in this case, $value seems to be a simple associative array (one server only) 0130 $value = array(0 => $value); // let's transform it into a classical array of associative arrays 0131 } 0132 $this->setOption('servers', $value); 0133 } 0134 $this->_memcache = new Memcache; 0135 foreach ($this->_options['servers'] as $server) { 0136 if (!array_key_exists('port', $server)) { 0137 $server['port'] = self::DEFAULT_PORT; 0138 } 0139 if (!array_key_exists('persistent', $server)) { 0140 $server['persistent'] = self::DEFAULT_PERSISTENT; 0141 } 0142 if (!array_key_exists('weight', $server)) { 0143 $server['weight'] = self::DEFAULT_WEIGHT; 0144 } 0145 if (!array_key_exists('timeout', $server)) { 0146 $server['timeout'] = self::DEFAULT_TIMEOUT; 0147 } 0148 if (!array_key_exists('retry_interval', $server)) { 0149 $server['retry_interval'] = self::DEFAULT_RETRY_INTERVAL; 0150 } 0151 if (!array_key_exists('status', $server)) { 0152 $server['status'] = self::DEFAULT_STATUS; 0153 } 0154 if (!array_key_exists('failure_callback', $server)) { 0155 $server['failure_callback'] = self::DEFAULT_FAILURE_CALLBACK; 0156 } 0157 if ($this->_options['compatibility']) { 0158 // No status for compatibility mode (#ZF-5887) 0159 $this->_memcache->addServer($server['host'], $server['port'], $server['persistent'], 0160 $server['weight'], $server['timeout'], 0161 $server['retry_interval']); 0162 } else { 0163 $this->_memcache->addServer($server['host'], $server['port'], $server['persistent'], 0164 $server['weight'], $server['timeout'], 0165 $server['retry_interval'], 0166 $server['status'], $server['failure_callback']); 0167 } 0168 } 0169 } 0170 0171 /** 0172 * Test if a cache is available for the given id and (if yes) return it (false else) 0173 * 0174 * @param string $id Cache id 0175 * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested 0176 * @return string|false cached datas 0177 */ 0178 public function load($id, $doNotTestCacheValidity = false) 0179 { 0180 $tmp = $this->_memcache->get($id); 0181 if (is_array($tmp) && isset($tmp[0])) { 0182 return $tmp[0]; 0183 } 0184 return false; 0185 } 0186 0187 /** 0188 * Test if a cache is available or not (for the given id) 0189 * 0190 * @param string $id Cache id 0191 * @return mixed|false (a cache is not available) or "last modified" timestamp (int) of the available cache record 0192 */ 0193 public function test($id) 0194 { 0195 $tmp = $this->_memcache->get($id); 0196 if (is_array($tmp)) { 0197 return $tmp[1]; 0198 } 0199 return false; 0200 } 0201 0202 /** 0203 * Save some string datas into a cache record 0204 * 0205 * Note : $data is always "string" (serialization is done by the 0206 * core not by the backend) 0207 * 0208 * @param string $data Datas to cache 0209 * @param string $id Cache id 0210 * @param array $tags Array of strings, the cache record will be tagged by each string entry 0211 * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime) 0212 * @return boolean True if no problem 0213 */ 0214 public function save($data, $id, $tags = array(), $specificLifetime = false) 0215 { 0216 $lifetime = $this->getLifetime($specificLifetime); 0217 if ($this->_options['compression']) { 0218 $flag = MEMCACHE_COMPRESSED; 0219 } else { 0220 $flag = 0; 0221 } 0222 0223 // ZF-8856: using set because add needs a second request if item already exists 0224 $result = @$this->_memcache->set($id, array($data, time(), $lifetime), $flag, $lifetime); 0225 0226 if (count($tags) > 0) { 0227 $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_MEMCACHED_BACKEND); 0228 } 0229 0230 return $result; 0231 } 0232 0233 /** 0234 * Remove a cache record 0235 * 0236 * @param string $id Cache id 0237 * @return boolean True if no problem 0238 */ 0239 public function remove($id) 0240 { 0241 return $this->_memcache->delete($id, 0); 0242 } 0243 0244 /** 0245 * Clean some cache records 0246 * 0247 * Available modes are : 0248 * 'all' (default) => remove all cache entries ($tags is not used) 0249 * 'old' => unsupported 0250 * 'matchingTag' => unsupported 0251 * 'notMatchingTag' => unsupported 0252 * 'matchingAnyTag' => unsupported 0253 * 0254 * @param string $mode Clean mode 0255 * @param array $tags Array of tags 0256 * @throws Zend_Cache_Exception 0257 * @return boolean True if no problem 0258 */ 0259 public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) 0260 { 0261 switch ($mode) { 0262 case Zend_Cache::CLEANING_MODE_ALL: 0263 return $this->_memcache->flush(); 0264 break; 0265 case Zend_Cache::CLEANING_MODE_OLD: 0266 $this->_log("Zend_Cache_Backend_Memcached::clean() : CLEANING_MODE_OLD is unsupported by the Memcached backend"); 0267 break; 0268 case Zend_Cache::CLEANING_MODE_MATCHING_TAG: 0269 case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG: 0270 case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG: 0271 $this->_log(self::TAGS_UNSUPPORTED_BY_CLEAN_OF_MEMCACHED_BACKEND); 0272 break; 0273 default: 0274 Zend_Cache::throwException('Invalid mode for clean() method'); 0275 break; 0276 } 0277 } 0278 0279 /** 0280 * Return true if the automatic cleaning is available for the backend 0281 * 0282 * @return boolean 0283 */ 0284 public function isAutomaticCleaningAvailable() 0285 { 0286 return false; 0287 } 0288 0289 /** 0290 * Set the frontend directives 0291 * 0292 * @param array $directives Assoc of directives 0293 * @throws Zend_Cache_Exception 0294 * @return void 0295 */ 0296 public function setDirectives($directives) 0297 { 0298 parent::setDirectives($directives); 0299 $lifetime = $this->getLifetime(false); 0300 if ($lifetime > 2592000) { 0301 // #ZF-3490 : For the memcached backend, there is a lifetime limit of 30 days (2592000 seconds) 0302 $this->_log('memcached backend has a limit of 30 days (2592000 seconds) for the lifetime'); 0303 } 0304 if ($lifetime === null) { 0305 // #ZF-4614 : we tranform null to zero to get the maximal lifetime 0306 parent::setDirectives(array('lifetime' => 0)); 0307 } 0308 } 0309 0310 /** 0311 * Return an array of stored cache ids 0312 * 0313 * @return array array of stored cache ids (string) 0314 */ 0315 public function getIds() 0316 { 0317 $this->_log("Zend_Cache_Backend_Memcached::save() : getting the list of cache ids is unsupported by the Memcache backend"); 0318 return array(); 0319 } 0320 0321 /** 0322 * Return an array of stored tags 0323 * 0324 * @return array array of stored tags (string) 0325 */ 0326 public function getTags() 0327 { 0328 $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_MEMCACHED_BACKEND); 0329 return array(); 0330 } 0331 0332 /** 0333 * Return an array of stored cache ids which match given tags 0334 * 0335 * In case of multiple tags, a logical AND is made between tags 0336 * 0337 * @param array $tags array of tags 0338 * @return array array of matching cache ids (string) 0339 */ 0340 public function getIdsMatchingTags($tags = array()) 0341 { 0342 $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_MEMCACHED_BACKEND); 0343 return array(); 0344 } 0345 0346 /** 0347 * Return an array of stored cache ids which don't match given tags 0348 * 0349 * In case of multiple tags, a logical OR is made between tags 0350 * 0351 * @param array $tags array of tags 0352 * @return array array of not matching cache ids (string) 0353 */ 0354 public function getIdsNotMatchingTags($tags = array()) 0355 { 0356 $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_MEMCACHED_BACKEND); 0357 return array(); 0358 } 0359 0360 /** 0361 * Return an array of stored cache ids which match any given tags 0362 * 0363 * In case of multiple tags, a logical AND is made between tags 0364 * 0365 * @param array $tags array of tags 0366 * @return array array of any matching cache ids (string) 0367 */ 0368 public function getIdsMatchingAnyTags($tags = array()) 0369 { 0370 $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_MEMCACHED_BACKEND); 0371 return array(); 0372 } 0373 0374 /** 0375 * Return the filling percentage of the backend storage 0376 * 0377 * @throws Zend_Cache_Exception 0378 * @return int integer between 0 and 100 0379 */ 0380 public function getFillingPercentage() 0381 { 0382 $mems = $this->_memcache->getExtendedStats(); 0383 0384 $memSize = null; 0385 $memUsed = null; 0386 foreach ($mems as $key => $mem) { 0387 if ($mem === false) { 0388 $this->_log('can\'t get stat from ' . $key); 0389 continue; 0390 } 0391 0392 $eachSize = $mem['limit_maxbytes']; 0393 0394 /** 0395 * Couchbase 1.x uses 'mem_used' instead of 'bytes' 0396 * @see https://www.couchbase.com/issues/browse/MB-3466 0397 */ 0398 $eachUsed = isset($mem['bytes']) ? $mem['bytes'] : $mem['mem_used']; 0399 if ($eachUsed > $eachSize) { 0400 $eachUsed = $eachSize; 0401 } 0402 0403 $memSize += $eachSize; 0404 $memUsed += $eachUsed; 0405 } 0406 0407 if ($memSize === null || $memUsed === null) { 0408 Zend_Cache::throwException('Can\'t get filling percentage'); 0409 } 0410 0411 return ((int) (100. * ($memUsed / $memSize))); 0412 } 0413 0414 /** 0415 * Return an array of metadatas for the given cache id 0416 * 0417 * The array must include these keys : 0418 * - expire : the expire timestamp 0419 * - tags : a string array of tags 0420 * - mtime : timestamp of last modification time 0421 * 0422 * @param string $id cache id 0423 * @return array array of metadatas (false if the cache id is not found) 0424 */ 0425 public function getMetadatas($id) 0426 { 0427 $tmp = $this->_memcache->get($id); 0428 if (is_array($tmp)) { 0429 $data = $tmp[0]; 0430 $mtime = $tmp[1]; 0431 if (!isset($tmp[2])) { 0432 // because this record is only with 1.7 release 0433 // if old cache records are still there... 0434 return false; 0435 } 0436 $lifetime = $tmp[2]; 0437 return array( 0438 'expire' => $mtime + $lifetime, 0439 'tags' => array(), 0440 'mtime' => $mtime 0441 ); 0442 } 0443 return false; 0444 } 0445 0446 /** 0447 * Give (if possible) an extra lifetime to the given cache id 0448 * 0449 * @param string $id cache id 0450 * @param int $extraLifetime 0451 * @return boolean true if ok 0452 */ 0453 public function touch($id, $extraLifetime) 0454 { 0455 if ($this->_options['compression']) { 0456 $flag = MEMCACHE_COMPRESSED; 0457 } else { 0458 $flag = 0; 0459 } 0460 $tmp = $this->_memcache->get($id); 0461 if (is_array($tmp)) { 0462 $data = $tmp[0]; 0463 $mtime = $tmp[1]; 0464 if (!isset($tmp[2])) { 0465 // because this record is only with 1.7 release 0466 // if old cache records are still there... 0467 return false; 0468 } 0469 $lifetime = $tmp[2]; 0470 $newLifetime = $lifetime - (time() - $mtime) + $extraLifetime; 0471 if ($newLifetime <=0) { 0472 return false; 0473 } 0474 // #ZF-5702 : we try replace() first becase set() seems to be slower 0475 if (!($result = $this->_memcache->replace($id, array($data, time(), $newLifetime), $flag, $newLifetime))) { 0476 $result = $this->_memcache->set($id, array($data, time(), $newLifetime), $flag, $newLifetime); 0477 } 0478 return $result; 0479 } 0480 return false; 0481 } 0482 0483 /** 0484 * Return an associative array of capabilities (booleans) of the backend 0485 * 0486 * The array must include these keys : 0487 * - automatic_cleaning (is automating cleaning necessary) 0488 * - tags (are tags supported) 0489 * - expired_read (is it possible to read expired cache records 0490 * (for doNotTestCacheValidity option for example)) 0491 * - priority does the backend deal with priority when saving 0492 * - infinite_lifetime (is infinite lifetime can work with this backend) 0493 * - get_list (is it possible to get the list of cache ids and the complete list of tags) 0494 * 0495 * @return array associative of with capabilities 0496 */ 0497 public function getCapabilities() 0498 { 0499 return array( 0500 'automatic_cleaning' => false, 0501 'tags' => false, 0502 'expired_read' => false, 0503 'priority' => false, 0504 'infinite_lifetime' => false, 0505 'get_list' => false 0506 ); 0507 } 0508 0509 }