v7‰PNG  IHDR Ÿ f Õ†C1 sRGB ®Îé gAMA ± üa pHYs à ÃÇo¨d GIDATx^íÜL”÷ð÷Yçªö("Bh_ò«®¸¢§q5kÖ*:þ0A­ºšÖ¥]VkJ¢M»¶f¸±8\k2íll£1]q®ÙÔ‚ÆT Null.php000064400000001257152101625270006175 0ustar00_directives = $directives; } public function load($id, $doNotTestCacheValidity = false) { if (isset($this->_cache[$id])) return $this->_cache[$id]; return false; } public function test($id) { return isset($this->_cache[$id]); } public function save($data, $label, $tags = array(), $specificLifetime = false) { $this->_cache[$label] = $data; return true; } public function remove($id) { unset($this->_cache[$id]); return true; } public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) { switch ($mode) { case Zend_Cache::CLEANING_MODE_ALL: // delete all data and all tags $this->_cache = array(); break; } } public function getCapabilities() { return array(); } public function getFillingPercentage() { return 0; } public function getIds() { return array_keys($this->_cache); } public function getIdsMatchingAnyTags($tags = array()) { return array(); } public function getIdsMatchingTags($tags = array()) { return array(); } public function getIdsNotMatchingTags($tags = array()) { return array(); } public function getMetadatas($id) { return array(); } public function getTags() { return array(); } public function touch($id, $extraLifetime) { } public function __sleep() { return array(); } }Apc.php000064400000025451152101643540005770 0ustar00 infinite lifetime) * @return boolean true if no problem */ public function save($data, $id, $tags = array(), $specificLifetime = false) { $lifetime = $this->getLifetime($specificLifetime); $result = apc_store($id, array($data, time(), $lifetime), $lifetime); if (count($tags) > 0) { $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_APC_BACKEND); } return $result; } /** * Remove a cache record * * @param string $id cache id * @return boolean true if no problem */ public function remove($id) { return apc_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 apc_clear_cache('user'); break; case Zend_Cache::CLEANING_MODE_OLD: $this->_log("Zend_Cache_Backend_Apc::clean() : CLEANING_MODE_OLD is unsupported by the Apc 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_APC_BACKEND); break; default: Zend_Cache::throwException('Invalid mode for clean() method'); break; } } /** * Return true if the automatic cleaning is available for the backend * * DEPRECATED : use getCapabilities() instead * * @deprecated * @return boolean */ public function isAutomaticCleaningAvailable() { return false; } /** * Return the filling percentage of the backend storage * * @throws Zend_Cache_Exception * @return int integer between 0 and 100 */ public function getFillingPercentage() { $mem = apc_sma_info(true); $memSize = $mem['num_seg'] * $mem['seg_size']; $memAvailable= $mem['avail_mem']; $memUsed = $memSize - $memAvailable; if ($memSize == 0) { Zend_Cache::throwException('can\'t get apc memory size'); } if ($memUsed > $memSize) { return 100; } return ((int) (100. * ($memUsed / $memSize))); } /** * Return an array of stored tags * * @return array array of stored tags (string) */ public function getTags() { $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_APC_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_APC_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_APC_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_APC_BACKEND); return array(); } /** * Return an array of stored cache ids * * @return array array of stored cache ids (string) */ public function getIds() { $ids = array(); $iterator = new APCIterator('user', null, APC_ITER_KEY); foreach ($iterator as $item) { $ids[] = $item['key']; } return $ids; } /** * 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 = apc_fetch($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 = apc_fetch($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; } apc_store($id, array($data, time(), $newLifetime), $newLifetime); return true; } 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' => true ); } } Xcache.php000064400000016215152101643540006456 0ustar00 (string) user : * xcache.admin.user (necessary for the clean() method) * * =====> (string) password : * xcache.admin.pass (clear, not MD5) (necessary for the clean() method) * * @var array available options */ protected $_options = array( 'user' => null, 'password' => null ); /** * Constructor * * @param array $options associative array of options * @throws Zend_Cache_Exception * @return void */ public function __construct(array $options = array()) { if (!extension_loaded('xcache')) { Zend_Cache::throwException('The xcache extension must be loaded for using this backend !'); } parent::__construct($options); } /** * Test if a cache is available for the given id and (if yes) return it (false else) * * WARNING $doNotTestCacheValidity=true is unsupported by the Xcache backend * * @param string $id cache id * @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested * @return string cached datas (or false) */ public function load($id, $doNotTestCacheValidity = false) { if ($doNotTestCacheValidity) { $this->_log("Zend_Cache_Backend_Xcache::load() : \$doNotTestCacheValidity=true is unsupported by the Xcache backend"); } $tmp = xcache_get($id); if (is_array($tmp)) { 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) { if (xcache_isset($id)) { $tmp = xcache_get($id); if (is_array($tmp)) { 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); $result = xcache_set($id, array($data, time()), $lifetime); if (count($tags) > 0) { $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_XCACHE_BACKEND); } return $result; } /** * Remove a cache record * * @param string $id cache id * @return boolean true if no problem */ public function remove($id) { return xcache_unset($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: // Necessary because xcache_clear_cache() need basic authentification $backup = array(); if (isset($_SERVER['PHP_AUTH_USER'])) { $backup['PHP_AUTH_USER'] = $_SERVER['PHP_AUTH_USER']; } if (isset($_SERVER['PHP_AUTH_PW'])) { $backup['PHP_AUTH_PW'] = $_SERVER['PHP_AUTH_PW']; } if ($this->_options['user']) { $_SERVER['PHP_AUTH_USER'] = $this->_options['user']; } if ($this->_options['password']) { $_SERVER['PHP_AUTH_PW'] = $this->_options['password']; } $cnt = xcache_count(XC_TYPE_VAR); for ($i=0; $i < $cnt; $i++) { xcache_clear_cache(XC_TYPE_VAR, $i); } if (isset($backup['PHP_AUTH_USER'])) { $_SERVER['PHP_AUTH_USER'] = $backup['PHP_AUTH_USER']; $_SERVER['PHP_AUTH_PW'] = $backup['PHP_AUTH_PW']; } return true; break; case Zend_Cache::CLEANING_MODE_OLD: $this->_log("Zend_Cache_Backend_Xcache::clean() : CLEANING_MODE_OLD is unsupported by the Xcache 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_XCACHE_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; } } Interface.php000064400000007417152101643540007167 0ustar00 infinite lifetime) * @return boolean true if no problem */ public function save($data, $id, $tags = array(), $specificLifetime = false); /** * Remove a cache record * * @param string $id Cache id * @return boolean True if no problem */ public function remove($id); /** * Clean some cache records * * Available modes are : * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used) * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used) * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags * ($tags can be an array of strings or a single string) * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags} * ($tags can be an array of strings or a single string) * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags * ($tags can be an array of strings or a single string) * * @param string $mode Clean mode * @param array $tags Array of tags * @return boolean true if no problem */ public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()); } File.php000064400000105647152101643540006152 0ustar00 (string) cache_dir : * - Directory where to put the cache files * * =====> (boolean) file_locking : * - Enable / disable file_locking * - Can avoid cache corruption under bad circumstances but it doesn't work on multithread * webservers and on NFS filesystems for example * * =====> (boolean) read_control : * - Enable / disable read control * - If enabled, a control key is embeded in cache file and this key is compared with the one * calculated after the reading. * * =====> (string) read_control_type : * - Type of read control (only if read control is enabled). Available values are : * 'md5' for a md5 hash control (best but slowest) * 'crc32' for a crc32 hash control (lightly less safe but faster, better choice) * 'adler32' for an adler32 hash control (excellent choice too, faster than crc32) * 'strlen' for a length only test (fastest) * * =====> (int) hashed_directory_level : * - Hashed directory level * - Set the hashed directory structure level. 0 means "no hashed directory * structure", 1 means "one level of directory", 2 means "two levels"... * This option can speed up the cache only when you have many thousands of * cache file. Only specific benchs can help you to choose the perfect value * for you. Maybe, 1 or 2 is a good start. * * =====> (int) hashed_directory_umask : * - deprecated * - Permissions for hashed directory structure * * =====> (int) hashed_directory_perm : * - Permissions for hashed directory structure * * =====> (string) file_name_prefix : * - prefix for cache files * - be really carefull with this option because a too generic value in a system cache dir * (like /tmp) can cause disasters when cleaning the cache * * =====> (int) cache_file_umask : * - deprecated * - Permissions for cache files * * =====> (int) cache_file_perm : * - Permissions for cache files * * =====> (int) metatadatas_array_max_size : * - max size for the metadatas array (don't change this value unless you * know what you are doing) * * @var array available options */ protected $_options = array( 'cache_dir' => null, 'file_locking' => true, 'read_control' => true, 'read_control_type' => 'crc32', 'hashed_directory_level' => 0, 'hashed_directory_perm' => 0700, 'file_name_prefix' => 'zend_cache', 'cache_file_perm' => 0600, 'metadatas_array_max_size' => 100 ); /** * Array of metadatas (each item is an associative array) * * @var array */ protected $_metadatasArray = array(); /** * Constructor * * @param array $options associative array of options * @throws Zend_Cache_Exception * @return void */ public function __construct(array $options = array()) { parent::__construct($options); if ($this->_options['cache_dir'] !== null) { // particular case for this option $this->setCacheDir($this->_options['cache_dir']); } else { $this->setCacheDir(self::getTmpDir() . DIRECTORY_SEPARATOR, false); } if (isset($this->_options['file_name_prefix'])) { // particular case for this option if (!preg_match('~^[a-zA-Z0-9_]+$~D', $this->_options['file_name_prefix'])) { Zend_Cache::throwException('Invalid file_name_prefix : must use only [a-zA-Z0-9_]'); } } if ($this->_options['metadatas_array_max_size'] < 10) { Zend_Cache::throwException('Invalid metadatas_array_max_size, must be > 10'); } if (isset($options['hashed_directory_umask'])) { // See #ZF-12047 trigger_error("'hashed_directory_umask' is deprecated -> please use 'hashed_directory_perm' instead", E_USER_NOTICE); if (!isset($options['hashed_directory_perm'])) { $options['hashed_directory_perm'] = $options['hashed_directory_umask']; } } if (isset($options['hashed_directory_perm']) && is_string($options['hashed_directory_perm'])) { // See #ZF-4422 $this->_options['hashed_directory_perm'] = octdec($this->_options['hashed_directory_perm']); } if (isset($options['cache_file_umask'])) { // See #ZF-12047 trigger_error("'cache_file_umask' is deprecated -> please use 'cache_file_perm' instead", E_USER_NOTICE); if (!isset($options['cache_file_perm'])) { $options['cache_file_perm'] = $options['cache_file_umask']; } } if (isset($options['cache_file_perm']) && is_string($options['cache_file_perm'])) { // See #ZF-4422 $this->_options['cache_file_perm'] = octdec($this->_options['cache_file_perm']); } } /** * Set the cache_dir (particular case of setOption() method) * * @param string $value * @param boolean $trailingSeparator If true, add a trailing separator is necessary * @throws Zend_Cache_Exception * @return void */ public function setCacheDir($value, $trailingSeparator = true) { if (!is_dir($value)) { Zend_Cache::throwException(sprintf('cache_dir "%s" must be a directory', $value)); } if (!is_writable($value)) { Zend_Cache::throwException(sprintf('cache_dir "%s" is not writable', $value)); } if ($trailingSeparator) { // add a trailing DIRECTORY_SEPARATOR if necessary $value = rtrim(realpath($value), '\\/') . DIRECTORY_SEPARATOR; } $this->_options['cache_dir'] = $value; } /** * 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) { if (!($this->_test($id, $doNotTestCacheValidity))) { // The cache is not hit ! return false; } $metadatas = $this->_getMetadatas($id); $file = $this->_file($id); $data = $this->_fileGetContents($file); if ($this->_options['read_control']) { $hashData = $this->_hash($data, $this->_options['read_control_type']); $hashControl = $metadatas['hash']; if ($hashData != $hashControl) { // Problem detected by the read control ! $this->_log('Zend_Cache_Backend_File::load() / read_control : stored hash and computed hash do not match'); $this->remove($id); return false; } } return $data; } /** * 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) { clearstatcache(); return $this->_test($id, 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) { clearstatcache(); $file = $this->_file($id); $path = $this->_path($id); if ($this->_options['hashed_directory_level'] > 0) { if (!is_writable($path)) { // maybe, we just have to build the directory structure $this->_recursiveMkdirAndChmod($id); } if (!is_writable($path)) { return false; } } if ($this->_options['read_control']) { $hash = $this->_hash($data, $this->_options['read_control_type']); } else { $hash = ''; } $metadatas = array( 'hash' => $hash, 'mtime' => time(), 'expire' => $this->_expireTime($this->getLifetime($specificLifetime)), 'tags' => $tags ); $res = $this->_setMetadatas($id, $metadatas); if (!$res) { $this->_log('Zend_Cache_Backend_File::save() / error on saving metadata'); return false; } $res = $this->_filePutContents($file, $data); return $res; } /** * Remove a cache record * * @param string $id cache id * @return boolean true if no problem */ public function remove($id) { $file = $this->_file($id); $boolRemove = $this->_remove($file); $boolMetadata = $this->_delMetadatas($id); return $boolMetadata && $boolRemove; } /** * Clean some cache records * * Available modes are : * * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used) * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used) * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags * ($tags can be an array of strings or a single string) * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags} * ($tags can be an array of strings or a single string) * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags * ($tags can be an array of strings or a single string) * * @param string $mode clean mode * @param tags array $tags array of tags * @return boolean true if no problem */ public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) { // We use this protected method to hide the recursive stuff clearstatcache(); return $this->_clean($this->_options['cache_dir'], $mode, $tags); } /** * Return an array of stored cache ids * * @return array array of stored cache ids (string) */ public function getIds() { return $this->_get($this->_options['cache_dir'], 'ids', array()); } /** * Return an array of stored tags * * @return array array of stored tags (string) */ public function getTags() { return $this->_get($this->_options['cache_dir'], 'tags', 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()) { return $this->_get($this->_options['cache_dir'], 'matching', $tags); } /** * 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()) { return $this->_get($this->_options['cache_dir'], 'notMatching', $tags); } /** * 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()) { return $this->_get($this->_options['cache_dir'], 'matchingAny', $tags); } /** * Return the filling percentage of the backend storage * * @throws Zend_Cache_Exception * @return int integer between 0 and 100 */ public function getFillingPercentage() { $free = disk_free_space($this->_options['cache_dir']); $total = disk_total_space($this->_options['cache_dir']); if ($total == 0) { Zend_Cache::throwException('can\'t get disk_total_space'); } else { if ($free >= $total) { return 100; } return ((int) (100. * ($total - $free) / $total)); } } /** * 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) { $metadatas = $this->_getMetadatas($id); if (!$metadatas) { return false; } if (time() > $metadatas['expire']) { return false; } return array( 'expire' => $metadatas['expire'], 'tags' => $metadatas['tags'], 'mtime' => $metadatas['mtime'] ); } /** * 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) { $metadatas = $this->_getMetadatas($id); if (!$metadatas) { return false; } if (time() > $metadatas['expire']) { return false; } $newMetadatas = array( 'hash' => $metadatas['hash'], 'mtime' => time(), 'expire' => $metadatas['expire'] + $extraLifetime, 'tags' => $metadatas['tags'] ); $res = $this->_setMetadatas($id, $newMetadatas); if (!$res) { return false; } return true; } /** * 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' => true, 'tags' => true, 'expired_read' => true, 'priority' => false, 'infinite_lifetime' => true, 'get_list' => true ); } /** * PUBLIC METHOD FOR UNIT TESTING ONLY ! * * Force a cache record to expire * * @param string $id cache id */ public function ___expire($id) { $metadatas = $this->_getMetadatas($id); if ($metadatas) { $metadatas['expire'] = 1; $this->_setMetadatas($id, $metadatas); } } /** * Get a metadatas record * * @param string $id Cache id * @return array|false Associative array of metadatas */ protected function _getMetadatas($id) { if (isset($this->_metadatasArray[$id])) { return $this->_metadatasArray[$id]; } else { $metadatas = $this->_loadMetadatas($id); if (!$metadatas) { return false; } $this->_setMetadatas($id, $metadatas, false); return $metadatas; } } /** * Set a metadatas record * * @param string $id Cache id * @param array $metadatas Associative array of metadatas * @param boolean $save optional pass false to disable saving to file * @return boolean True if no problem */ protected function _setMetadatas($id, $metadatas, $save = true) { if (count($this->_metadatasArray) >= $this->_options['metadatas_array_max_size']) { $n = (int) ($this->_options['metadatas_array_max_size'] / 10); $this->_metadatasArray = array_slice($this->_metadatasArray, $n); } if ($save) { $result = $this->_saveMetadatas($id, $metadatas); if (!$result) { return false; } } $this->_metadatasArray[$id] = $metadatas; return true; } /** * Drop a metadata record * * @param string $id Cache id * @return boolean True if no problem */ protected function _delMetadatas($id) { if (isset($this->_metadatasArray[$id])) { unset($this->_metadatasArray[$id]); } $file = $this->_metadatasFile($id); return $this->_remove($file); } /** * Clear the metadatas array * * @return void */ protected function _cleanMetadatas() { $this->_metadatasArray = array(); } /** * Load metadatas from disk * * @param string $id Cache id * @return array|false Metadatas associative array */ protected function _loadMetadatas($id) { $file = $this->_metadatasFile($id); $result = $this->_fileGetContents($file); if (!$result) { return false; } $tmp = @unserialize($result); return $tmp; } /** * Save metadatas to disk * * @param string $id Cache id * @param array $metadatas Associative array * @return boolean True if no problem */ protected function _saveMetadatas($id, $metadatas) { $file = $this->_metadatasFile($id); $result = $this->_filePutContents($file, serialize($metadatas)); if (!$result) { return false; } return true; } /** * Make and return a file name (with path) for metadatas * * @param string $id Cache id * @return string Metadatas file name (with path) */ protected function _metadatasFile($id) { $path = $this->_path($id); $fileName = $this->_idToFileName('internal-metadatas---' . $id); return $path . $fileName; } /** * Check if the given filename is a metadatas one * * @param string $fileName File name * @return boolean True if it's a metadatas one */ protected function _isMetadatasFile($fileName) { $id = $this->_fileNameToId($fileName); if (substr($id, 0, 21) == 'internal-metadatas---') { return true; } else { return false; } } /** * Remove a file * * If we can't remove the file (because of locks or any problem), we will touch * the file to invalidate it * * @param string $file Complete file path * @return boolean True if ok */ protected function _remove($file) { if (!is_file($file)) { return false; } if (!@unlink($file)) { # we can't remove the file (because of locks or any problem) $this->_log("Zend_Cache_Backend_File::_remove() : we can't remove $file"); return false; } return true; } /** * Clean some cache records (protected method used for recursive stuff) * * Available modes are : * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used) * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used) * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags * ($tags can be an array of strings or a single string) * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags} * ($tags can be an array of strings or a single string) * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags * ($tags can be an array of strings or a single string) * * @param string $dir Directory to clean * @param string $mode Clean mode * @param array $tags Array of tags * @throws Zend_Cache_Exception * @return boolean True if no problem */ protected function _clean($dir, $mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) { if (!is_dir($dir)) { return false; } $result = true; $prefix = $this->_options['file_name_prefix']; $glob = @glob($dir . $prefix . '--*'); if ($glob === false) { // On some systems it is impossible to distinguish between empty match and an error. return true; } foreach ($glob as $file) { if (is_file($file)) { $fileName = basename($file); if ($this->_isMetadatasFile($fileName)) { // in CLEANING_MODE_ALL, we drop anything, even remainings old metadatas files if ($mode != Zend_Cache::CLEANING_MODE_ALL) { continue; } } $id = $this->_fileNameToId($fileName); $metadatas = $this->_getMetadatas($id); if ($metadatas === FALSE) { $metadatas = array('expire' => 1, 'tags' => array()); } switch ($mode) { case Zend_Cache::CLEANING_MODE_ALL: $res = $this->remove($id); if (!$res) { // in this case only, we accept a problem with the metadatas file drop $res = $this->_remove($file); } $result = $result && $res; break; case Zend_Cache::CLEANING_MODE_OLD: if (time() > $metadatas['expire']) { $result = $this->remove($id) && $result; } break; case Zend_Cache::CLEANING_MODE_MATCHING_TAG: $matching = true; foreach ($tags as $tag) { if (!in_array($tag, $metadatas['tags'])) { $matching = false; break; } } if ($matching) { $result = $this->remove($id) && $result; } break; case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG: $matching = false; foreach ($tags as $tag) { if (in_array($tag, $metadatas['tags'])) { $matching = true; break; } } if (!$matching) { $result = $this->remove($id) && $result; } break; case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG: $matching = false; foreach ($tags as $tag) { if (in_array($tag, $metadatas['tags'])) { $matching = true; break; } } if ($matching) { $result = $this->remove($id) && $result; } break; default: Zend_Cache::throwException('Invalid mode for clean() method'); break; } } if ((is_dir($file)) and ($this->_options['hashed_directory_level']>0)) { // Recursive call $result = $this->_clean($file . DIRECTORY_SEPARATOR, $mode, $tags) && $result; if ($mode == Zend_Cache::CLEANING_MODE_ALL) { // we try to drop the structure too @rmdir($file); } } } return $result; } protected function _get($dir, $mode, $tags = array()) { if (!is_dir($dir)) { return false; } $result = array(); $prefix = $this->_options['file_name_prefix']; $glob = @glob($dir . $prefix . '--*'); if ($glob === false) { // On some systems it is impossible to distinguish between empty match and an error. return array(); } foreach ($glob as $file) { if (is_file($file)) { $fileName = basename($file); $id = $this->_fileNameToId($fileName); $metadatas = $this->_getMetadatas($id); if ($metadatas === FALSE) { continue; } if (time() > $metadatas['expire']) { continue; } switch ($mode) { case 'ids': $result[] = $id; break; case 'tags': $result = array_unique(array_merge($result, $metadatas['tags'])); break; case 'matching': $matching = true; foreach ($tags as $tag) { if (!in_array($tag, $metadatas['tags'])) { $matching = false; break; } } if ($matching) { $result[] = $id; } break; case 'notMatching': $matching = false; foreach ($tags as $tag) { if (in_array($tag, $metadatas['tags'])) { $matching = true; break; } } if (!$matching) { $result[] = $id; } break; case 'matchingAny': $matching = false; foreach ($tags as $tag) { if (in_array($tag, $metadatas['tags'])) { $matching = true; break; } } if ($matching) { $result[] = $id; } break; default: Zend_Cache::throwException('Invalid mode for _get() method'); break; } } if ((is_dir($file)) and ($this->_options['hashed_directory_level']>0)) { // Recursive call $recursiveRs = $this->_get($file . DIRECTORY_SEPARATOR, $mode, $tags); if ($recursiveRs === false) { $this->_log('Zend_Cache_Backend_File::_get() / recursive call : can\'t list entries of "'.$file.'"'); } else { $result = array_unique(array_merge($result, $recursiveRs)); } } } return array_unique($result); } /** * Compute & return the expire time * * @return int expire time (unix timestamp) */ protected function _expireTime($lifetime) { if ($lifetime === null) { return 9999999999; } return time() + $lifetime; } /** * Make a control key with the string containing datas * * @param string $data Data * @param string $controlType Type of control 'md5', 'crc32' or 'strlen' * @throws Zend_Cache_Exception * @return string Control key */ protected function _hash($data, $controlType) { switch ($controlType) { case 'md5': return md5($data); case 'crc32': return crc32($data); case 'strlen': return strlen($data); case 'adler32': return hash('adler32', $data); default: Zend_Cache::throwException("Incorrect hash function : $controlType"); } } /** * Transform a cache id into a file name and return it * * @param string $id Cache id * @return string File name */ protected function _idToFileName($id) { $prefix = $this->_options['file_name_prefix']; $result = $prefix . '---' . $id; return $result; } /** * Make and return a file name (with path) * * @param string $id Cache id * @return string File name (with path) */ protected function _file($id) { $path = $this->_path($id); $fileName = $this->_idToFileName($id); return $path . $fileName; } /** * Return the complete directory path of a filename (including hashedDirectoryStructure) * * @param string $id Cache id * @param boolean $parts if true, returns array of directory parts instead of single string * @return string Complete directory path */ protected function _path($id, $parts = false) { $partsArray = array(); $root = $this->_options['cache_dir']; $prefix = $this->_options['file_name_prefix']; if ($this->_options['hashed_directory_level']>0) { $hash = hash('adler32', $id); for ($i=0 ; $i < $this->_options['hashed_directory_level'] ; $i++) { $root = $root . $prefix . '--' . substr($hash, 0, $i + 1) . DIRECTORY_SEPARATOR; $partsArray[] = $root; } } if ($parts) { return $partsArray; } else { return $root; } } /** * Make the directory strucuture for the given id * * @param string $id cache id * @return boolean true */ protected function _recursiveMkdirAndChmod($id) { if ($this->_options['hashed_directory_level'] <=0) { return true; } $partsArray = $this->_path($id, true); foreach ($partsArray as $part) { if (!is_dir($part)) { @mkdir($part, $this->_options['hashed_directory_perm']); @chmod($part, $this->_options['hashed_directory_perm']); // see #ZF-320 (this line is required in some configurations) } } return true; } /** * Test if the given cache id is available (and still valid as a cache record) * * @param string $id Cache id * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested * @return boolean|mixed false (a cache is not available) or "last modified" timestamp (int) of the available cache record */ protected function _test($id, $doNotTestCacheValidity) { $metadatas = $this->_getMetadatas($id); if (!$metadatas) { return false; } if ($doNotTestCacheValidity || (time() <= $metadatas['expire'])) { return $metadatas['mtime']; } return false; } /** * Return the file content of the given file * * @param string $file File complete path * @return string File content (or false if problem) */ protected function _fileGetContents($file) { $result = false; if (!is_file($file)) { return false; } $f = @fopen($file, 'rb'); if ($f) { if ($this->_options['file_locking']) @flock($f, LOCK_SH); $result = stream_get_contents($f); if ($this->_options['file_locking']) @flock($f, LOCK_UN); @fclose($f); } return $result; } /** * Put the given string into the given file * * @param string $file File complete path * @param string $string String to put in file * @return boolean true if no problem */ protected function _filePutContents($file, $string) { $result = false; $f = @fopen($file, 'ab+'); if ($f) { if ($this->_options['file_locking']) @flock($f, LOCK_EX); fseek($f, 0); ftruncate($f, 0); $tmp = @fwrite($f, $string); if (!($tmp === FALSE)) { $result = true; } @fclose($f); } @chmod($file, $this->_options['cache_file_perm']); return $result; } /** * Transform a file name into cache id and return it * * @param string $fileName File name * @return string Cache id */ protected function _fileNameToId($fileName) { $prefix = $this->_options['file_name_prefix']; return preg_replace('~^' . $prefix . '---(.*)$~', '$1', $fileName); } } Memcached.php000064400000042673152101643540007140 0ustar00 (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 * 'persistent' => (bool) : use or not persistent connections to this 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. * 'timeout' => (int) : value in seconds which will be used for connecting to the daemon. Think twice * before changing the default value of 1 second - you can lose all the * advantages of caching if your connection is too slow. * 'retry_interval' => (int) : controls how often a failed server will be retried, the default value * is 15 seconds. Setting this parameter to -1 disables automatic retry. * 'status' => (bool) : controls if the server should be flagged as online. * 'failure_callback' => (callback) : Allows the user to specify a callback function to run upon * encountering an error. The callback is run before failover * is attempted. The function takes two parameters, the hostname * and port of the failed server. * * =====> (boolean) compression : * true if you want to use on-the-fly compression * * =====> (boolean) compatibility : * true if you use old memcache server or extension * * @var array available options */ protected $_options = array( 'servers' => array(array( 'host' => self::DEFAULT_HOST, 'port' => self::DEFAULT_PORT, 'persistent' => self::DEFAULT_PERSISTENT, 'weight' => self::DEFAULT_WEIGHT, 'timeout' => self::DEFAULT_TIMEOUT, 'retry_interval' => self::DEFAULT_RETRY_INTERVAL, 'status' => self::DEFAULT_STATUS, 'failure_callback' => self::DEFAULT_FAILURE_CALLBACK )), 'compression' => false, 'compatibility' => false, ); /** * Memcache object * * @var mixed memcache 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('memcache')) { Zend_Cache::throwException('The memcache 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 Memcache; foreach ($this->_options['servers'] as $server) { if (!array_key_exists('port', $server)) { $server['port'] = self::DEFAULT_PORT; } if (!array_key_exists('persistent', $server)) { $server['persistent'] = self::DEFAULT_PERSISTENT; } if (!array_key_exists('weight', $server)) { $server['weight'] = self::DEFAULT_WEIGHT; } if (!array_key_exists('timeout', $server)) { $server['timeout'] = self::DEFAULT_TIMEOUT; } if (!array_key_exists('retry_interval', $server)) { $server['retry_interval'] = self::DEFAULT_RETRY_INTERVAL; } if (!array_key_exists('status', $server)) { $server['status'] = self::DEFAULT_STATUS; } if (!array_key_exists('failure_callback', $server)) { $server['failure_callback'] = self::DEFAULT_FAILURE_CALLBACK; } if ($this->_options['compatibility']) { // No status for compatibility mode (#ZF-5887) $this->_memcache->addServer($server['host'], $server['port'], $server['persistent'], $server['weight'], $server['timeout'], $server['retry_interval']); } else { $this->_memcache->addServer($server['host'], $server['port'], $server['persistent'], $server['weight'], $server['timeout'], $server['retry_interval'], $server['status'], $server['failure_callback']); } } } /** * 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)) { 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); if ($this->_options['compression']) { $flag = MEMCACHE_COMPRESSED; } else { $flag = 0; } // ZF-8856: using set because add needs a second request if item already exists $result = @$this->_memcache->set($id, array($data, time(), $lifetime), $flag, $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, 0); } /** * 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_Memcached::clean() : CLEANING_MODE_OLD is unsupported by the Memcached 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_Memcached::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->getExtendedStats(); $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) { if ($this->_options['compression']) { $flag = MEMCACHE_COMPRESSED; } else { $flag = 0; } $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), $flag, $newLifetime))) { $result = $this->_memcache->set($id, array($data, time(), $newLifetime), $flag, $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 ); } } ExtendedInterface.php000064400000007615152101643540010650 0ustar00 (string) slow_backend : * - Slow backend name * - Must implement the Zend_Cache_Backend_ExtendedInterface * - Should provide a big storage * * =====> (string) fast_backend : * - Flow backend name * - Must implement the Zend_Cache_Backend_ExtendedInterface * - Must be much faster than slow_backend * * =====> (array) slow_backend_options : * - Slow backend options (see corresponding backend) * * =====> (array) fast_backend_options : * - Fast backend options (see corresponding backend) * * =====> (int) stats_update_factor : * - Disable / Tune the computation of the fast backend filling percentage * - When saving a record into cache : * 1 => systematic computation of the fast backend filling percentage * x (integer) > 1 => computation of the fast backend filling percentage randomly 1 times on x cache write * * =====> (boolean) slow_backend_custom_naming : * =====> (boolean) fast_backend_custom_naming : * =====> (boolean) slow_backend_autoload : * =====> (boolean) fast_backend_autoload : * - See Zend_Cache::factory() method * * =====> (boolean) auto_refresh_fast_cache * - If true, auto refresh the fast cache when a cache record is hit * * @var array available options */ protected $_options = array( 'slow_backend' => 'File', 'fast_backend' => 'Apc', 'slow_backend_options' => array(), 'fast_backend_options' => array(), 'stats_update_factor' => 10, 'slow_backend_custom_naming' => false, 'fast_backend_custom_naming' => false, 'slow_backend_autoload' => false, 'fast_backend_autoload' => false, 'auto_refresh_fast_cache' => true ); /** * Slow Backend * * @var Zend_Cache_Backend_ExtendedInterface */ protected $_slowBackend; /** * Fast Backend * * @var Zend_Cache_Backend_ExtendedInterface */ protected $_fastBackend; /** * Cache for the fast backend filling percentage * * @var int */ protected $_fastBackendFillingPercentage = null; /** * Constructor * * @param array $options Associative array of options * @throws Zend_Cache_Exception * @return void */ public function __construct(array $options = array()) { parent::__construct($options); if ($this->_options['slow_backend'] === null) { Zend_Cache::throwException('slow_backend option has to set'); } elseif ($this->_options['slow_backend'] instanceof Zend_Cache_Backend_ExtendedInterface) { $this->_slowBackend = $this->_options['slow_backend']; } else { $this->_slowBackend = Zend_Cache::_makeBackend( $this->_options['slow_backend'], $this->_options['slow_backend_options'], $this->_options['slow_backend_custom_naming'], $this->_options['slow_backend_autoload'] ); if (!in_array('Zend_Cache_Backend_ExtendedInterface', class_implements($this->_slowBackend))) { Zend_Cache::throwException('slow_backend must implement the Zend_Cache_Backend_ExtendedInterface interface'); } } if ($this->_options['fast_backend'] === null) { Zend_Cache::throwException('fast_backend option has to set'); } elseif ($this->_options['fast_backend'] instanceof Zend_Cache_Backend_ExtendedInterface) { $this->_fastBackend = $this->_options['fast_backend']; } else { $this->_fastBackend = Zend_Cache::_makeBackend( $this->_options['fast_backend'], $this->_options['fast_backend_options'], $this->_options['fast_backend_custom_naming'], $this->_options['fast_backend_autoload'] ); if (!in_array('Zend_Cache_Backend_ExtendedInterface', class_implements($this->_fastBackend))) { Zend_Cache::throwException('fast_backend must implement the Zend_Cache_Backend_ExtendedInterface interface'); } } $this->_slowBackend->setDirectives($this->_directives); $this->_fastBackend->setDirectives($this->_directives); } /** * 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) { $fastTest = $this->_fastBackend->test($id); if ($fastTest) { return $fastTest; } else { return $this->_slowBackend->test($id); } } /** * 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) * @param int $priority integer between 0 (very low priority) and 10 (maximum priority) used by some particular backends * @return boolean true if no problem */ public function save($data, $id, $tags = array(), $specificLifetime = false, $priority = 8) { $usage = $this->_getFastFillingPercentage('saving'); $boolFast = true; $lifetime = $this->getLifetime($specificLifetime); $preparedData = $this->_prepareData($data, $lifetime, $priority); if (($priority > 0) && (10 * $priority >= $usage)) { $fastLifetime = $this->_getFastLifetime($lifetime, $priority); $boolFast = $this->_fastBackend->save($preparedData, $id, array(), $fastLifetime); $boolSlow = $this->_slowBackend->save($preparedData, $id, $tags, $lifetime); } else { $boolSlow = $this->_slowBackend->save($preparedData, $id, $tags, $lifetime); if ($boolSlow === true) { $boolFast = $this->_fastBackend->remove($id); if (!$boolFast && !$this->_fastBackend->test($id)) { // some backends return false on remove() even if the key never existed. (and it won't if fast is full) // all we care about is that the key doesn't exist now $boolFast = true; } } } return ($boolFast && $boolSlow); } /** * Test if a cache is available for the given id and (if yes) return it (false else) * * Note : return value is always "string" (unserialization is done by the core not by the backend) * * @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) { $res = $this->_fastBackend->load($id, $doNotTestCacheValidity); if ($res === false) { $res = $this->_slowBackend->load($id, $doNotTestCacheValidity); if ($res === false) { // there is no cache at all for this id return false; } } $array = unserialize($res); // maybe, we have to refresh the fast cache ? if ($this->_options['auto_refresh_fast_cache']) { if ($array['priority'] == 10) { // no need to refresh the fast cache with priority = 10 return $array['data']; } $newFastLifetime = $this->_getFastLifetime($array['lifetime'], $array['priority'], time() - $array['expire']); // we have the time to refresh the fast cache $usage = $this->_getFastFillingPercentage('loading'); if (($array['priority'] > 0) && (10 * $array['priority'] >= $usage)) { // we can refresh the fast cache $preparedData = $this->_prepareData($array['data'], $array['lifetime'], $array['priority']); $this->_fastBackend->save($preparedData, $id, array(), $newFastLifetime); } } return $array['data']; } /** * Remove a cache record * * @param string $id Cache id * @return boolean True if no problem */ public function remove($id) { $boolFast = $this->_fastBackend->remove($id); $boolSlow = $this->_slowBackend->remove($id); return $boolFast && $boolSlow; } /** * Clean some cache records * * Available modes are : * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used) * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used) * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags * ($tags can be an array of strings or a single string) * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags} * ($tags can be an array of strings or a single string) * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags * ($tags can be an array of strings or a single string) * * @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: $boolFast = $this->_fastBackend->clean(Zend_Cache::CLEANING_MODE_ALL); $boolSlow = $this->_slowBackend->clean(Zend_Cache::CLEANING_MODE_ALL); return $boolFast && $boolSlow; break; case Zend_Cache::CLEANING_MODE_OLD: return $this->_slowBackend->clean(Zend_Cache::CLEANING_MODE_OLD); case Zend_Cache::CLEANING_MODE_MATCHING_TAG: $ids = $this->_slowBackend->getIdsMatchingTags($tags); $res = true; foreach ($ids as $id) { $bool = $this->remove($id); $res = $res && $bool; } return $res; break; case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG: $ids = $this->_slowBackend->getIdsNotMatchingTags($tags); $res = true; foreach ($ids as $id) { $bool = $this->remove($id); $res = $res && $bool; } return $res; break; case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG: $ids = $this->_slowBackend->getIdsMatchingAnyTags($tags); $res = true; foreach ($ids as $id) { $bool = $this->remove($id); $res = $res && $bool; } return $res; break; default: Zend_Cache::throwException('Invalid mode for clean() method'); break; } } /** * Return an array of stored cache ids * * @return array array of stored cache ids (string) */ public function getIds() { return $this->_slowBackend->getIds(); } /** * Return an array of stored tags * * @return array array of stored tags (string) */ public function getTags() { return $this->_slowBackend->getTags(); } /** * 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()) { return $this->_slowBackend->getIdsMatchingTags($tags); } /** * 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()) { return $this->_slowBackend->getIdsNotMatchingTags($tags); } /** * 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()) { return $this->_slowBackend->getIdsMatchingAnyTags($tags); } /** * Return the filling percentage of the backend storage * * @return int integer between 0 and 100 */ public function getFillingPercentage() { return $this->_slowBackend->getFillingPercentage(); } /** * 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) { return $this->_slowBackend->getMetadatas($id); } /** * 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) { return $this->_slowBackend->touch($id, $extraLifetime); } /** * 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() { $slowBackendCapabilities = $this->_slowBackend->getCapabilities(); return array( 'automatic_cleaning' => $slowBackendCapabilities['automatic_cleaning'], 'tags' => $slowBackendCapabilities['tags'], 'expired_read' => $slowBackendCapabilities['expired_read'], 'priority' => $slowBackendCapabilities['priority'], 'infinite_lifetime' => $slowBackendCapabilities['infinite_lifetime'], 'get_list' => $slowBackendCapabilities['get_list'] ); } /** * Prepare a serialized array to store datas and metadatas informations * * @param string $data data to store * @param int $lifetime original lifetime * @param int $priority priority * @return string serialize array to store into cache */ private function _prepareData($data, $lifetime, $priority) { $lt = $lifetime; if ($lt === null) { $lt = 9999999999; } return serialize(array( 'data' => $data, 'lifetime' => $lifetime, 'expire' => time() + $lt, 'priority' => $priority )); } /** * Compute and return the lifetime for the fast backend * * @param int $lifetime original lifetime * @param int $priority priority * @param int $maxLifetime maximum lifetime * @return int lifetime for the fast backend */ private function _getFastLifetime($lifetime, $priority, $maxLifetime = null) { if ($lifetime <= 0) { // if no lifetime, we have an infinite lifetime // we need to use arbitrary lifetimes $fastLifetime = (int) (2592000 / (11 - $priority)); } else { // prevent computed infinite lifetime (0) by ceil $fastLifetime = (int) ceil($lifetime / (11 - $priority)); } if ($maxLifetime >= 0 && $fastLifetime > $maxLifetime) { return $maxLifetime; } return $fastLifetime; } /** * PUBLIC METHOD FOR UNIT TESTING ONLY ! * * Force a cache record to expire * * @param string $id cache id */ public function ___expire($id) { $this->_fastBackend->remove($id); $this->_slowBackend->___expire($id); } private function _getFastFillingPercentage($mode) { if ($mode == 'saving') { // mode saving if ($this->_fastBackendFillingPercentage === null) { $this->_fastBackendFillingPercentage = $this->_fastBackend->getFillingPercentage(); } else { $rand = rand(1, $this->_options['stats_update_factor']); if ($rand == 1) { // we force a refresh $this->_fastBackendFillingPercentage = $this->_fastBackend->getFillingPercentage(); } } } else { // mode loading // we compute the percentage only if it's not available in cache if ($this->_fastBackendFillingPercentage === null) { $this->_fastBackendFillingPercentage = $this->_fastBackend->getFillingPercentage(); } } return $this->_fastBackendFillingPercentage; } }