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_Libmemcached extends Zend_Cache_Backend implements Zend_Cache_Backend_ExtendedInterface 0042 { 0043 /** 0044 * Default Server Values 0045 */ 0046 const DEFAULT_HOST = '127.0.0.1'; 0047 const DEFAULT_PORT = 11211; 0048 const DEFAULT_WEIGHT = 1; 0049 0050 /** 0051 * Log message 0052 */ 0053 const TAGS_UNSUPPORTED_BY_CLEAN_OF_LIBMEMCACHED_BACKEND = 'Zend_Cache_Backend_Libmemcached::clean() : tags are unsupported by the Libmemcached backend'; 0054 const TAGS_UNSUPPORTED_BY_SAVE_OF_LIBMEMCACHED_BACKEND = 'Zend_Cache_Backend_Libmemcached::save() : tags are unsupported by the Libmemcached backend'; 0055 0056 /** 0057 * Available options 0058 * 0059 * =====> (array) servers : 0060 * an array of memcached server ; each memcached server is described by an associative array : 0061 * 'host' => (string) : the name of the memcached server 0062 * 'port' => (int) : the port of the memcached server 0063 * 'weight' => (int) : number of buckets to create for this server which in turn control its 0064 * probability of it being selected. The probability is relative to the total 0065 * weight of all servers. 0066 * =====> (array) client : 0067 * an array of memcached client options ; the memcached client is described by an associative array : 0068 * @see http://php.net/manual/memcached.constants.php 0069 * - The option name can be the name of the constant without the prefix 'OPT_' 0070 * or the integer value of this option constant 0071 * 0072 * @var array available options 0073 */ 0074 protected $_options = array( 0075 'servers' => array(array( 0076 'host' => self::DEFAULT_HOST, 0077 'port' => self::DEFAULT_PORT, 0078 'weight' => self::DEFAULT_WEIGHT, 0079 )), 0080 'client' => array() 0081 ); 0082 0083 /** 0084 * Memcached object 0085 * 0086 * @var mixed memcached object 0087 */ 0088 protected $_memcache = null; 0089 0090 /** 0091 * Constructor 0092 * 0093 * @param array $options associative array of options 0094 * @throws Zend_Cache_Exception 0095 * @return void 0096 */ 0097 public function __construct(array $options = array()) 0098 { 0099 if (!extension_loaded('memcached')) { 0100 Zend_Cache::throwException('The memcached extension must be loaded for using this backend !'); 0101 } 0102 0103 // override default client options 0104 $this->_options['client'] = array( 0105 Memcached::OPT_DISTRIBUTION => Memcached::DISTRIBUTION_CONSISTENT, 0106 Memcached::OPT_HASH => Memcached::HASH_MD5, 0107 Memcached::OPT_LIBKETAMA_COMPATIBLE => true, 0108 ); 0109 0110 parent::__construct($options); 0111 0112 if (isset($this->_options['servers'])) { 0113 $value = $this->_options['servers']; 0114 if (isset($value['host'])) { 0115 // in this case, $value seems to be a simple associative array (one server only) 0116 $value = array(0 => $value); // let's transform it into a classical array of associative arrays 0117 } 0118 $this->setOption('servers', $value); 0119 } 0120 $this->_memcache = new Memcached; 0121 0122 // setup memcached client options 0123 foreach ($this->_options['client'] as $name => $value) { 0124 $optId = null; 0125 if (is_int($name)) { 0126 $optId = $name; 0127 } else { 0128 $optConst = 'Memcached::OPT_' . strtoupper($name); 0129 if (defined($optConst)) { 0130 $optId = constant($optConst); 0131 } else { 0132 $this->_log("Unknown memcached client option '{$name}' ({$optConst})"); 0133 } 0134 } 0135 if (null !== $optId) { 0136 if (!$this->_memcache->setOption($optId, $value)) { 0137 $this->_log("Setting memcached client option '{$optId}' failed"); 0138 } 0139 } 0140 } 0141 0142 // setup memcached servers 0143 $servers = array(); 0144 foreach ($this->_options['servers'] as $server) { 0145 if (!array_key_exists('port', $server)) { 0146 $server['port'] = self::DEFAULT_PORT; 0147 } 0148 if (!array_key_exists('weight', $server)) { 0149 $server['weight'] = self::DEFAULT_WEIGHT; 0150 } 0151 0152 $servers[] = array($server['host'], $server['port'], $server['weight']); 0153 } 0154 $this->_memcache->addServers($servers); 0155 } 0156 0157 /** 0158 * Test if a cache is available for the given id and (if yes) return it (false else) 0159 * 0160 * @param string $id Cache id 0161 * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested 0162 * @return string|false cached datas 0163 */ 0164 public function load($id, $doNotTestCacheValidity = false) 0165 { 0166 $tmp = $this->_memcache->get($id); 0167 if (isset($tmp[0])) { 0168 return $tmp[0]; 0169 } 0170 return false; 0171 } 0172 0173 /** 0174 * Test if a cache is available or not (for the given id) 0175 * 0176 * @param string $id Cache id 0177 * @return int|false (a cache is not available) or "last modified" timestamp (int) of the available cache record 0178 */ 0179 public function test($id) 0180 { 0181 $tmp = $this->_memcache->get($id); 0182 if (isset($tmp[0], $tmp[1])) { 0183 return (int)$tmp[1]; 0184 } 0185 return false; 0186 } 0187 0188 /** 0189 * Save some string datas into a cache record 0190 * 0191 * Note : $data is always "string" (serialization is done by the 0192 * core not by the backend) 0193 * 0194 * @param string $data Datas to cache 0195 * @param string $id Cache id 0196 * @param array $tags Array of strings, the cache record will be tagged by each string entry 0197 * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime) 0198 * @return boolean True if no problem 0199 */ 0200 public function save($data, $id, $tags = array(), $specificLifetime = false) 0201 { 0202 $lifetime = $this->getLifetime($specificLifetime); 0203 0204 // ZF-8856: using set because add needs a second request if item already exists 0205 $result = @$this->_memcache->set($id, array($data, time(), $lifetime), $lifetime); 0206 if ($result === false) { 0207 $rsCode = $this->_memcache->getResultCode(); 0208 $rsMsg = $this->_memcache->getResultMessage(); 0209 $this->_log("Memcached::set() failed: [{$rsCode}] {$rsMsg}"); 0210 } 0211 0212 if (count($tags) > 0) { 0213 $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_LIBMEMCACHED_BACKEND); 0214 } 0215 0216 return $result; 0217 } 0218 0219 /** 0220 * Remove a cache record 0221 * 0222 * @param string $id Cache id 0223 * @return boolean True if no problem 0224 */ 0225 public function remove($id) 0226 { 0227 return $this->_memcache->delete($id); 0228 } 0229 0230 /** 0231 * Clean some cache records 0232 * 0233 * Available modes are : 0234 * 'all' (default) => remove all cache entries ($tags is not used) 0235 * 'old' => unsupported 0236 * 'matchingTag' => unsupported 0237 * 'notMatchingTag' => unsupported 0238 * 'matchingAnyTag' => unsupported 0239 * 0240 * @param string $mode Clean mode 0241 * @param array $tags Array of tags 0242 * @throws Zend_Cache_Exception 0243 * @return boolean True if no problem 0244 */ 0245 public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) 0246 { 0247 switch ($mode) { 0248 case Zend_Cache::CLEANING_MODE_ALL: 0249 return $this->_memcache->flush(); 0250 break; 0251 case Zend_Cache::CLEANING_MODE_OLD: 0252 $this->_log("Zend_Cache_Backend_Libmemcached::clean() : CLEANING_MODE_OLD is unsupported by the Libmemcached backend"); 0253 break; 0254 case Zend_Cache::CLEANING_MODE_MATCHING_TAG: 0255 case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG: 0256 case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG: 0257 $this->_log(self::TAGS_UNSUPPORTED_BY_CLEAN_OF_LIBMEMCACHED_BACKEND); 0258 break; 0259 default: 0260 Zend_Cache::throwException('Invalid mode for clean() method'); 0261 break; 0262 } 0263 } 0264 0265 /** 0266 * Return true if the automatic cleaning is available for the backend 0267 * 0268 * @return boolean 0269 */ 0270 public function isAutomaticCleaningAvailable() 0271 { 0272 return false; 0273 } 0274 0275 /** 0276 * Set the frontend directives 0277 * 0278 * @param array $directives Assoc of directives 0279 * @throws Zend_Cache_Exception 0280 * @return void 0281 */ 0282 public function setDirectives($directives) 0283 { 0284 parent::setDirectives($directives); 0285 $lifetime = $this->getLifetime(false); 0286 if ($lifetime > 2592000) { 0287 // #ZF-3490 : For the memcached backend, there is a lifetime limit of 30 days (2592000 seconds) 0288 $this->_log('memcached backend has a limit of 30 days (2592000 seconds) for the lifetime'); 0289 } 0290 if ($lifetime === null) { 0291 // #ZF-4614 : we tranform null to zero to get the maximal lifetime 0292 parent::setDirectives(array('lifetime' => 0)); 0293 } 0294 } 0295 0296 /** 0297 * Return an array of stored cache ids 0298 * 0299 * @return array array of stored cache ids (string) 0300 */ 0301 public function getIds() 0302 { 0303 $this->_log("Zend_Cache_Backend_Libmemcached::save() : getting the list of cache ids is unsupported by the Libmemcached backend"); 0304 return array(); 0305 } 0306 0307 /** 0308 * Return an array of stored tags 0309 * 0310 * @return array array of stored tags (string) 0311 */ 0312 public function getTags() 0313 { 0314 $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_LIBMEMCACHED_BACKEND); 0315 return array(); 0316 } 0317 0318 /** 0319 * Return an array of stored cache ids which match given tags 0320 * 0321 * In case of multiple tags, a logical AND is made between tags 0322 * 0323 * @param array $tags array of tags 0324 * @return array array of matching cache ids (string) 0325 */ 0326 public function getIdsMatchingTags($tags = array()) 0327 { 0328 $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_LIBMEMCACHED_BACKEND); 0329 return array(); 0330 } 0331 0332 /** 0333 * Return an array of stored cache ids which don't match given tags 0334 * 0335 * In case of multiple tags, a logical OR is made between tags 0336 * 0337 * @param array $tags array of tags 0338 * @return array array of not matching cache ids (string) 0339 */ 0340 public function getIdsNotMatchingTags($tags = array()) 0341 { 0342 $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_LIBMEMCACHED_BACKEND); 0343 return array(); 0344 } 0345 0346 /** 0347 * Return an array of stored cache ids which match any given tags 0348 * 0349 * In case of multiple tags, a logical AND is made between tags 0350 * 0351 * @param array $tags array of tags 0352 * @return array array of any matching cache ids (string) 0353 */ 0354 public function getIdsMatchingAnyTags($tags = array()) 0355 { 0356 $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_LIBMEMCACHED_BACKEND); 0357 return array(); 0358 } 0359 0360 /** 0361 * Return the filling percentage of the backend storage 0362 * 0363 * @throws Zend_Cache_Exception 0364 * @return int integer between 0 and 100 0365 */ 0366 public function getFillingPercentage() 0367 { 0368 $mems = $this->_memcache->getStats(); 0369 if ($mems === false) { 0370 return 0; 0371 } 0372 0373 $memSize = null; 0374 $memUsed = null; 0375 foreach ($mems as $key => $mem) { 0376 if ($mem === false) { 0377 $this->_log('can\'t get stat from ' . $key); 0378 continue; 0379 } 0380 0381 $eachSize = $mem['limit_maxbytes']; 0382 $eachUsed = $mem['bytes']; 0383 if ($eachUsed > $eachSize) { 0384 $eachUsed = $eachSize; 0385 } 0386 0387 $memSize += $eachSize; 0388 $memUsed += $eachUsed; 0389 } 0390 0391 if ($memSize === null || $memUsed === null) { 0392 Zend_Cache::throwException('Can\'t get filling percentage'); 0393 } 0394 0395 return ((int) (100. * ($memUsed / $memSize))); 0396 } 0397 0398 /** 0399 * Return an array of metadatas for the given cache id 0400 * 0401 * The array must include these keys : 0402 * - expire : the expire timestamp 0403 * - tags : a string array of tags 0404 * - mtime : timestamp of last modification time 0405 * 0406 * @param string $id cache id 0407 * @return array array of metadatas (false if the cache id is not found) 0408 */ 0409 public function getMetadatas($id) 0410 { 0411 $tmp = $this->_memcache->get($id); 0412 if (isset($tmp[0], $tmp[1], $tmp[2])) { 0413 $data = $tmp[0]; 0414 $mtime = $tmp[1]; 0415 $lifetime = $tmp[2]; 0416 return array( 0417 'expire' => $mtime + $lifetime, 0418 'tags' => array(), 0419 'mtime' => $mtime 0420 ); 0421 } 0422 0423 return false; 0424 } 0425 0426 /** 0427 * Give (if possible) an extra lifetime to the given cache id 0428 * 0429 * @param string $id cache id 0430 * @param int $extraLifetime 0431 * @return boolean true if ok 0432 */ 0433 public function touch($id, $extraLifetime) 0434 { 0435 $tmp = $this->_memcache->get($id); 0436 if (isset($tmp[0], $tmp[1], $tmp[2])) { 0437 $data = $tmp[0]; 0438 $mtime = $tmp[1]; 0439 $lifetime = $tmp[2]; 0440 $newLifetime = $lifetime - (time() - $mtime) + $extraLifetime; 0441 if ($newLifetime <=0) { 0442 return false; 0443 } 0444 // #ZF-5702 : we try replace() first becase set() seems to be slower 0445 if (!($result = $this->_memcache->replace($id, array($data, time(), $newLifetime), $newLifetime))) { 0446 $result = $this->_memcache->set($id, array($data, time(), $newLifetime), $newLifetime); 0447 if ($result === false) { 0448 $rsCode = $this->_memcache->getResultCode(); 0449 $rsMsg = $this->_memcache->getResultMessage(); 0450 $this->_log("Memcached::set() failed: [{$rsCode}] {$rsMsg}"); 0451 } 0452 } 0453 return $result; 0454 } 0455 return false; 0456 } 0457 0458 /** 0459 * Return an associative array of capabilities (booleans) of the backend 0460 * 0461 * The array must include these keys : 0462 * - automatic_cleaning (is automating cleaning necessary) 0463 * - tags (are tags supported) 0464 * - expired_read (is it possible to read expired cache records 0465 * (for doNotTestCacheValidity option for example)) 0466 * - priority does the backend deal with priority when saving 0467 * - infinite_lifetime (is infinite lifetime can work with this backend) 0468 * - get_list (is it possible to get the list of cache ids and the complete list of tags) 0469 * 0470 * @return array associative of with capabilities 0471 */ 0472 public function getCapabilities() 0473 { 0474 return array( 0475 'automatic_cleaning' => false, 0476 'tags' => false, 0477 'expired_read' => false, 0478 'priority' => false, 0479 'infinite_lifetime' => false, 0480 'get_list' => false 0481 ); 0482 } 0483 0484 }