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_ExtendedInterface 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 0042 class Zend_Cache_Backend_TwoLevels extends Zend_Cache_Backend implements Zend_Cache_Backend_ExtendedInterface 0043 { 0044 /** 0045 * Available options 0046 * 0047 * =====> (string) slow_backend : 0048 * - Slow backend name 0049 * - Must implement the Zend_Cache_Backend_ExtendedInterface 0050 * - Should provide a big storage 0051 * 0052 * =====> (string) fast_backend : 0053 * - Flow backend name 0054 * - Must implement the Zend_Cache_Backend_ExtendedInterface 0055 * - Must be much faster than slow_backend 0056 * 0057 * =====> (array) slow_backend_options : 0058 * - Slow backend options (see corresponding backend) 0059 * 0060 * =====> (array) fast_backend_options : 0061 * - Fast backend options (see corresponding backend) 0062 * 0063 * =====> (int) stats_update_factor : 0064 * - Disable / Tune the computation of the fast backend filling percentage 0065 * - When saving a record into cache : 0066 * 1 => systematic computation of the fast backend filling percentage 0067 * x (integer) > 1 => computation of the fast backend filling percentage randomly 1 times on x cache write 0068 * 0069 * =====> (boolean) slow_backend_custom_naming : 0070 * =====> (boolean) fast_backend_custom_naming : 0071 * =====> (boolean) slow_backend_autoload : 0072 * =====> (boolean) fast_backend_autoload : 0073 * - See Zend_Cache::factory() method 0074 * 0075 * =====> (boolean) auto_fill_fast_cache 0076 * - If true, automatically fill the fast cache when a cache record was not found in fast cache, but did 0077 * exist in slow cache. This can be usefull when a non-persistent cache like APC or Memcached got 0078 * purged for whatever reason. 0079 * 0080 * =====> (boolean) auto_refresh_fast_cache 0081 * - If true, auto refresh the fast cache when a cache record is hit 0082 * 0083 * @var array available options 0084 */ 0085 protected $_options = array( 0086 'slow_backend' => 'File', 0087 'fast_backend' => 'Apc', 0088 'slow_backend_options' => array(), 0089 'fast_backend_options' => array(), 0090 'stats_update_factor' => 10, 0091 'slow_backend_custom_naming' => false, 0092 'fast_backend_custom_naming' => false, 0093 'slow_backend_autoload' => false, 0094 'fast_backend_autoload' => false, 0095 'auto_fill_fast_cache' => true, 0096 'auto_refresh_fast_cache' => true 0097 ); 0098 0099 /** 0100 * Slow Backend 0101 * 0102 * @var Zend_Cache_Backend_ExtendedInterface 0103 */ 0104 protected $_slowBackend; 0105 0106 /** 0107 * Fast Backend 0108 * 0109 * @var Zend_Cache_Backend_ExtendedInterface 0110 */ 0111 protected $_fastBackend; 0112 0113 /** 0114 * Cache for the fast backend filling percentage 0115 * 0116 * @var int 0117 */ 0118 protected $_fastBackendFillingPercentage = null; 0119 0120 /** 0121 * Constructor 0122 * 0123 * @param array $options Associative array of options 0124 * @throws Zend_Cache_Exception 0125 * @return void 0126 */ 0127 public function __construct(array $options = array()) 0128 { 0129 parent::__construct($options); 0130 0131 if ($this->_options['slow_backend'] === null) { 0132 Zend_Cache::throwException('slow_backend option has to set'); 0133 } elseif ($this->_options['slow_backend'] instanceof Zend_Cache_Backend_ExtendedInterface) { 0134 $this->_slowBackend = $this->_options['slow_backend']; 0135 } else { 0136 $this->_slowBackend = Zend_Cache::_makeBackend( 0137 $this->_options['slow_backend'], 0138 $this->_options['slow_backend_options'], 0139 $this->_options['slow_backend_custom_naming'], 0140 $this->_options['slow_backend_autoload'] 0141 ); 0142 if (!in_array('Zend_Cache_Backend_ExtendedInterface', class_implements($this->_slowBackend))) { 0143 Zend_Cache::throwException('slow_backend must implement the Zend_Cache_Backend_ExtendedInterface interface'); 0144 } 0145 } 0146 0147 if ($this->_options['fast_backend'] === null) { 0148 Zend_Cache::throwException('fast_backend option has to set'); 0149 } elseif ($this->_options['fast_backend'] instanceof Zend_Cache_Backend_ExtendedInterface) { 0150 $this->_fastBackend = $this->_options['fast_backend']; 0151 } else { 0152 $this->_fastBackend = Zend_Cache::_makeBackend( 0153 $this->_options['fast_backend'], 0154 $this->_options['fast_backend_options'], 0155 $this->_options['fast_backend_custom_naming'], 0156 $this->_options['fast_backend_autoload'] 0157 ); 0158 if (!in_array('Zend_Cache_Backend_ExtendedInterface', class_implements($this->_fastBackend))) { 0159 Zend_Cache::throwException('fast_backend must implement the Zend_Cache_Backend_ExtendedInterface interface'); 0160 } 0161 } 0162 0163 $this->_slowBackend->setDirectives($this->_directives); 0164 $this->_fastBackend->setDirectives($this->_directives); 0165 } 0166 0167 /** 0168 * Test if a cache is available or not (for the given id) 0169 * 0170 * @param string $id cache id 0171 * @return mixed|false (a cache is not available) or "last modified" timestamp (int) of the available cache record 0172 */ 0173 public function test($id) 0174 { 0175 $fastTest = $this->_fastBackend->test($id); 0176 if ($fastTest) { 0177 return $fastTest; 0178 } else { 0179 return $this->_slowBackend->test($id); 0180 } 0181 } 0182 0183 /** 0184 * Save some string datas into a cache record 0185 * 0186 * Note : $data is always "string" (serialization is done by the 0187 * core not by the backend) 0188 * 0189 * @param string $data Datas to cache 0190 * @param string $id Cache id 0191 * @param array $tags Array of strings, the cache record will be tagged by each string entry 0192 * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime) 0193 * @param int $priority integer between 0 (very low priority) and 10 (maximum priority) used by some particular backends 0194 * @return boolean true if no problem 0195 */ 0196 public function save($data, $id, $tags = array(), $specificLifetime = false, $priority = 8) 0197 { 0198 $usage = $this->_getFastFillingPercentage('saving'); 0199 $boolFast = true; 0200 $lifetime = $this->getLifetime($specificLifetime); 0201 $preparedData = $this->_prepareData($data, $lifetime, $priority); 0202 if (($priority > 0) && (10 * $priority >= $usage)) { 0203 $fastLifetime = $this->_getFastLifetime($lifetime, $priority); 0204 $boolFast = $this->_fastBackend->save($preparedData, $id, array(), $fastLifetime); 0205 $boolSlow = $this->_slowBackend->save($preparedData, $id, $tags, $lifetime); 0206 } else { 0207 $boolSlow = $this->_slowBackend->save($preparedData, $id, $tags, $lifetime); 0208 if ($boolSlow === true) { 0209 $boolFast = $this->_fastBackend->remove($id); 0210 if (!$boolFast && !$this->_fastBackend->test($id)) { 0211 // some backends return false on remove() even if the key never existed. (and it won't if fast is full) 0212 // all we care about is that the key doesn't exist now 0213 $boolFast = true; 0214 } 0215 } 0216 } 0217 0218 return ($boolFast && $boolSlow); 0219 } 0220 0221 /** 0222 * Test if a cache is available for the given id and (if yes) return it (false else) 0223 * 0224 * Note : return value is always "string" (unserialization is done by the core not by the backend) 0225 * 0226 * @param string $id Cache id 0227 * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested 0228 * @return string|false cached datas 0229 */ 0230 public function load($id, $doNotTestCacheValidity = false) 0231 { 0232 $resultFast = $this->_fastBackend->load($id, $doNotTestCacheValidity); 0233 if ($resultFast === false) { 0234 $resultSlow = $this->_slowBackend->load($id, $doNotTestCacheValidity); 0235 if ($resultSlow === false) { 0236 // there is no cache at all for this id 0237 return false; 0238 } 0239 } 0240 $array = $resultFast !== false ? unserialize($resultFast) : unserialize($resultSlow); 0241 0242 //In case no cache entry was found in the FastCache and auto-filling is enabled, copy data to FastCache 0243 if ($resultFast === false && $this->_options['auto_fill_fast_cache']) { 0244 $preparedData = $this->_prepareData($array['data'], $array['lifetime'], $array['priority']); 0245 $this->_fastBackend->save($preparedData, $id, array(), $array['lifetime']); 0246 } 0247 // maybe, we have to refresh the fast cache ? 0248 elseif ($this->_options['auto_refresh_fast_cache']) { 0249 if ($array['priority'] == 10) { 0250 // no need to refresh the fast cache with priority = 10 0251 return $array['data']; 0252 } 0253 $newFastLifetime = $this->_getFastLifetime($array['lifetime'], $array['priority'], time() - $array['expire']); 0254 // we have the time to refresh the fast cache 0255 $usage = $this->_getFastFillingPercentage('loading'); 0256 if (($array['priority'] > 0) && (10 * $array['priority'] >= $usage)) { 0257 // we can refresh the fast cache 0258 $preparedData = $this->_prepareData($array['data'], $array['lifetime'], $array['priority']); 0259 $this->_fastBackend->save($preparedData, $id, array(), $newFastLifetime); 0260 } 0261 } 0262 return $array['data']; 0263 } 0264 0265 /** 0266 * Remove a cache record 0267 * 0268 * @param string $id Cache id 0269 * @return boolean True if no problem 0270 */ 0271 public function remove($id) 0272 { 0273 $boolFast = $this->_fastBackend->remove($id); 0274 $boolSlow = $this->_slowBackend->remove($id); 0275 return $boolFast && $boolSlow; 0276 } 0277 0278 /** 0279 * Clean some cache records 0280 * 0281 * Available modes are : 0282 * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used) 0283 * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used) 0284 * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags 0285 * ($tags can be an array of strings or a single string) 0286 * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags} 0287 * ($tags can be an array of strings or a single string) 0288 * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags 0289 * ($tags can be an array of strings or a single string) 0290 * 0291 * @param string $mode Clean mode 0292 * @param array $tags Array of tags 0293 * @throws Zend_Cache_Exception 0294 * @return boolean true if no problem 0295 */ 0296 public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) 0297 { 0298 switch($mode) { 0299 case Zend_Cache::CLEANING_MODE_ALL: 0300 $boolFast = $this->_fastBackend->clean(Zend_Cache::CLEANING_MODE_ALL); 0301 $boolSlow = $this->_slowBackend->clean(Zend_Cache::CLEANING_MODE_ALL); 0302 return $boolFast && $boolSlow; 0303 break; 0304 case Zend_Cache::CLEANING_MODE_OLD: 0305 return $this->_slowBackend->clean(Zend_Cache::CLEANING_MODE_OLD); 0306 case Zend_Cache::CLEANING_MODE_MATCHING_TAG: 0307 $ids = $this->_slowBackend->getIdsMatchingTags($tags); 0308 $res = true; 0309 foreach ($ids as $id) { 0310 $bool = $this->remove($id); 0311 $res = $res && $bool; 0312 } 0313 return $res; 0314 break; 0315 case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG: 0316 $ids = $this->_slowBackend->getIdsNotMatchingTags($tags); 0317 $res = true; 0318 foreach ($ids as $id) { 0319 $bool = $this->remove($id); 0320 $res = $res && $bool; 0321 } 0322 return $res; 0323 break; 0324 case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG: 0325 $ids = $this->_slowBackend->getIdsMatchingAnyTags($tags); 0326 $res = true; 0327 foreach ($ids as $id) { 0328 $bool = $this->remove($id); 0329 $res = $res && $bool; 0330 } 0331 return $res; 0332 break; 0333 default: 0334 Zend_Cache::throwException('Invalid mode for clean() method'); 0335 break; 0336 } 0337 } 0338 0339 /** 0340 * Return an array of stored cache ids 0341 * 0342 * @return array array of stored cache ids (string) 0343 */ 0344 public function getIds() 0345 { 0346 return $this->_slowBackend->getIds(); 0347 } 0348 0349 /** 0350 * Return an array of stored tags 0351 * 0352 * @return array array of stored tags (string) 0353 */ 0354 public function getTags() 0355 { 0356 return $this->_slowBackend->getTags(); 0357 } 0358 0359 /** 0360 * Return an array of stored cache ids which match given tags 0361 * 0362 * In case of multiple tags, a logical AND is made between tags 0363 * 0364 * @param array $tags array of tags 0365 * @return array array of matching cache ids (string) 0366 */ 0367 public function getIdsMatchingTags($tags = array()) 0368 { 0369 return $this->_slowBackend->getIdsMatchingTags($tags); 0370 } 0371 0372 /** 0373 * Return an array of stored cache ids which don't match given tags 0374 * 0375 * In case of multiple tags, a logical OR is made between tags 0376 * 0377 * @param array $tags array of tags 0378 * @return array array of not matching cache ids (string) 0379 */ 0380 public function getIdsNotMatchingTags($tags = array()) 0381 { 0382 return $this->_slowBackend->getIdsNotMatchingTags($tags); 0383 } 0384 0385 /** 0386 * Return an array of stored cache ids which match any given tags 0387 * 0388 * In case of multiple tags, a logical AND is made between tags 0389 * 0390 * @param array $tags array of tags 0391 * @return array array of any matching cache ids (string) 0392 */ 0393 public function getIdsMatchingAnyTags($tags = array()) 0394 { 0395 return $this->_slowBackend->getIdsMatchingAnyTags($tags); 0396 } 0397 0398 /** 0399 * Return the filling percentage of the backend storage 0400 * 0401 * @return int integer between 0 and 100 0402 */ 0403 public function getFillingPercentage() 0404 { 0405 return $this->_slowBackend->getFillingPercentage(); 0406 } 0407 0408 /** 0409 * Return an array of metadatas for the given cache id 0410 * 0411 * The array must include these keys : 0412 * - expire : the expire timestamp 0413 * - tags : a string array of tags 0414 * - mtime : timestamp of last modification time 0415 * 0416 * @param string $id cache id 0417 * @return array array of metadatas (false if the cache id is not found) 0418 */ 0419 public function getMetadatas($id) 0420 { 0421 return $this->_slowBackend->getMetadatas($id); 0422 } 0423 0424 /** 0425 * Give (if possible) an extra lifetime to the given cache id 0426 * 0427 * @param string $id cache id 0428 * @param int $extraLifetime 0429 * @return boolean true if ok 0430 */ 0431 public function touch($id, $extraLifetime) 0432 { 0433 return $this->_slowBackend->touch($id, $extraLifetime); 0434 } 0435 0436 /** 0437 * Return an associative array of capabilities (booleans) of the backend 0438 * 0439 * The array must include these keys : 0440 * - automatic_cleaning (is automating cleaning necessary) 0441 * - tags (are tags supported) 0442 * - expired_read (is it possible to read expired cache records 0443 * (for doNotTestCacheValidity option for example)) 0444 * - priority does the backend deal with priority when saving 0445 * - infinite_lifetime (is infinite lifetime can work with this backend) 0446 * - get_list (is it possible to get the list of cache ids and the complete list of tags) 0447 * 0448 * @return array associative of with capabilities 0449 */ 0450 public function getCapabilities() 0451 { 0452 $slowBackendCapabilities = $this->_slowBackend->getCapabilities(); 0453 return array( 0454 'automatic_cleaning' => $slowBackendCapabilities['automatic_cleaning'], 0455 'tags' => $slowBackendCapabilities['tags'], 0456 'expired_read' => $slowBackendCapabilities['expired_read'], 0457 'priority' => $slowBackendCapabilities['priority'], 0458 'infinite_lifetime' => $slowBackendCapabilities['infinite_lifetime'], 0459 'get_list' => $slowBackendCapabilities['get_list'] 0460 ); 0461 } 0462 0463 /** 0464 * Prepare a serialized array to store datas and metadatas informations 0465 * 0466 * @param string $data data to store 0467 * @param int $lifetime original lifetime 0468 * @param int $priority priority 0469 * @return string serialize array to store into cache 0470 */ 0471 private function _prepareData($data, $lifetime, $priority) 0472 { 0473 $lt = $lifetime; 0474 if ($lt === null) { 0475 $lt = 9999999999; 0476 } 0477 return serialize(array( 0478 'data' => $data, 0479 'lifetime' => $lifetime, 0480 'expire' => time() + $lt, 0481 'priority' => $priority 0482 )); 0483 } 0484 0485 /** 0486 * Compute and return the lifetime for the fast backend 0487 * 0488 * @param int $lifetime original lifetime 0489 * @param int $priority priority 0490 * @param int $maxLifetime maximum lifetime 0491 * @return int lifetime for the fast backend 0492 */ 0493 private function _getFastLifetime($lifetime, $priority, $maxLifetime = null) 0494 { 0495 if ($lifetime <= 0) { 0496 // if no lifetime, we have an infinite lifetime 0497 // we need to use arbitrary lifetimes 0498 $fastLifetime = (int) (2592000 / (11 - $priority)); 0499 } else { 0500 // prevent computed infinite lifetime (0) by ceil 0501 $fastLifetime = (int) ceil($lifetime / (11 - $priority)); 0502 } 0503 0504 if ($maxLifetime >= 0 && $fastLifetime > $maxLifetime) { 0505 return $maxLifetime; 0506 } 0507 0508 return $fastLifetime; 0509 } 0510 0511 /** 0512 * PUBLIC METHOD FOR UNIT TESTING ONLY ! 0513 * 0514 * Force a cache record to expire 0515 * 0516 * @param string $id cache id 0517 */ 0518 public function ___expire($id) 0519 { 0520 $this->_fastBackend->remove($id); 0521 $this->_slowBackend->___expire($id); 0522 } 0523 0524 private function _getFastFillingPercentage($mode) 0525 { 0526 0527 if ($mode == 'saving') { 0528 // mode saving 0529 if ($this->_fastBackendFillingPercentage === null) { 0530 $this->_fastBackendFillingPercentage = $this->_fastBackend->getFillingPercentage(); 0531 } else { 0532 $rand = rand(1, $this->_options['stats_update_factor']); 0533 if ($rand == 1) { 0534 // we force a refresh 0535 $this->_fastBackendFillingPercentage = $this->_fastBackend->getFillingPercentage(); 0536 } 0537 } 0538 } else { 0539 // mode loading 0540 // we compute the percentage only if it's not available in cache 0541 if ($this->_fastBackendFillingPercentage === null) { 0542 $this->_fastBackendFillingPercentage = $this->_fastBackend->getFillingPercentage(); 0543 } 0544 } 0545 return $this->_fastBackendFillingPercentage; 0546 } 0547 0548 }