00001 <?php
00026 require_once 'Zend/Cache/Backend/ExtendedInterface.php';
00027
00031 require_once 'Zend/Cache/Backend.php';
00032
00033
00040 class Zend_Cache_Backend_File extends Zend_Cache_Backend implements Zend_Cache_Backend_ExtendedInterface
00041 {
00090 protected $_options = array(
00091 'cache_dir' => null,
00092 'file_locking' => true,
00093 'read_control' => true,
00094 'read_control_type' => 'crc32',
00095 'hashed_directory_level' => 0,
00096 'hashed_directory_umask' => 0700,
00097 'file_name_prefix' => 'zend_cache',
00098 'cache_file_umask' => 0600,
00099 'metadatas_array_max_size' => 100
00100 );
00101
00107 protected $_metadatasArray = array();
00108
00109
00117 public function __construct(array $options = array())
00118 {
00119 parent::__construct($options);
00120 if ($this->_options['cache_dir'] !== null) {
00121 $this->setCacheDir($this->_options['cache_dir']);
00122 } else {
00123 $this->setCacheDir(self::getTmpDir() . DIRECTORY_SEPARATOR, false);
00124 }
00125 if (isset($this->_options['file_name_prefix'])) {
00126 if (!preg_match('~^[\w]+$~', $this->_options['file_name_prefix'])) {
00127 Zend_Cache::throwException('Invalid file_name_prefix : must use only [a-zA-A0-9_]');
00128 }
00129 }
00130 if ($this->_options['metadatas_array_max_size'] < 10) {
00131 Zend_Cache::throwException('Invalid metadatas_array_max_size, must be > 10');
00132 }
00133 if (isset($options['hashed_directory_umask']) && is_string($options['hashed_directory_umask'])) {
00134
00135 $this->_options['hashed_directory_umask'] = octdec($this->_options['hashed_directory_umask']);
00136 }
00137 if (isset($options['cache_file_umask']) && is_string($options['cache_file_umask'])) {
00138
00139 $this->_options['cache_file_umask'] = octdec($this->_options['cache_file_umask']);
00140 }
00141 }
00142
00151 public function setCacheDir($value, $trailingSeparator = true)
00152 {
00153 if (!is_dir($value)) {
00154 Zend_Cache::throwException('cache_dir must be a directory');
00155 }
00156 if (!is_writable($value)) {
00157 Zend_Cache::throwException('cache_dir is not writable');
00158 }
00159 if ($trailingSeparator) {
00160
00161 $value = rtrim(realpath($value), '\\/') . DIRECTORY_SEPARATOR;
00162 }
00163 $this->_options['cache_dir'] = $value;
00164 }
00165
00173 public function load($id, $doNotTestCacheValidity = false)
00174 {
00175 if (!($this->_test($id, $doNotTestCacheValidity))) {
00176
00177 return false;
00178 }
00179 $metadatas = $this->_getMetadatas($id);
00180 $file = $this->_file($id);
00181 $data = $this->_fileGetContents($file);
00182 if ($this->_options['read_control']) {
00183 $hashData = $this->_hash($data, $this->_options['read_control_type']);
00184 $hashControl = $metadatas['hash'];
00185 if ($hashData != $hashControl) {
00186
00187 $this->_log('Zend_Cache_Backend_File::load() / read_control : stored hash and computed hash do not match');
00188 $this->remove($id);
00189 return false;
00190 }
00191 }
00192 return $data;
00193 }
00194
00201 public function test($id)
00202 {
00203 clearstatcache();
00204 return $this->_test($id, false);
00205 }
00206
00219 public function save($data, $id, $tags = array(), $specificLifetime = false)
00220 {
00221 clearstatcache();
00222 $file = $this->_file($id);
00223 $path = $this->_path($id);
00224 if ($this->_options['hashed_directory_level'] > 0) {
00225 if (!is_writable($path)) {
00226
00227 $this->_recursiveMkdirAndChmod($id);
00228 }
00229 if (!is_writable($path)) {
00230 return false;
00231 }
00232 }
00233 if ($this->_options['read_control']) {
00234 $hash = $this->_hash($data, $this->_options['read_control_type']);
00235 } else {
00236 $hash = '';
00237 }
00238 $metadatas = array(
00239 'hash' => $hash,
00240 'mtime' => time(),
00241 'expire' => $this->_expireTime($this->getLifetime($specificLifetime)),
00242 'tags' => $tags
00243 );
00244 $res = $this->_setMetadatas($id, $metadatas);
00245 if (!$res) {
00246 $this->_log('Zend_Cache_Backend_File::save() / error on saving metadata');
00247 return false;
00248 }
00249 $res = $this->_filePutContents($file, $data);
00250 return $res;
00251 }
00252
00259 public function remove($id)
00260 {
00261 $file = $this->_file($id);
00262 $boolRemove = $this->_remove($file);
00263 $boolMetadata = $this->_delMetadatas($id);
00264 return $boolMetadata && $boolRemove;
00265 }
00266
00284 public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
00285 {
00286
00287 clearstatcache();
00288 return $this->_clean($this->_options['cache_dir'], $mode, $tags);
00289 }
00290
00296 public function getIds()
00297 {
00298 return $this->_get($this->_options['cache_dir'], 'ids', array());
00299 }
00300
00306 public function getTags()
00307 {
00308 return $this->_get($this->_options['cache_dir'], 'tags', array());
00309 }
00310
00319 public function getIdsMatchingTags($tags = array())
00320 {
00321 return $this->_get($this->_options['cache_dir'], 'matching', $tags);
00322 }
00323
00332 public function getIdsNotMatchingTags($tags = array())
00333 {
00334 return $this->_get($this->_options['cache_dir'], 'notMatching', $tags);
00335 }
00336
00345 public function getIdsMatchingAnyTags($tags = array())
00346 {
00347 return $this->_get($this->_options['cache_dir'], 'matchingAny', $tags);
00348 }
00349
00356 public function getFillingPercentage()
00357 {
00358 $free = disk_free_space($this->_options['cache_dir']);
00359 $total = disk_total_space($this->_options['cache_dir']);
00360 if ($total == 0) {
00361 Zend_Cache::throwException('can\'t get disk_total_space');
00362 } else {
00363 if ($free >= $total) {
00364 return 100;
00365 }
00366 return ((int) (100. * ($total - $free) / $total));
00367 }
00368 }
00369
00381 public function getMetadatas($id)
00382 {
00383 $metadatas = $this->_getMetadatas($id);
00384 if (!$metadatas) {
00385 return false;
00386 }
00387 if (time() > $metadatas['expire']) {
00388 return false;
00389 }
00390 return array(
00391 'expire' => $metadatas['expire'],
00392 'tags' => $metadatas['tags'],
00393 'mtime' => $metadatas['mtime']
00394 );
00395 }
00396
00404 public function touch($id, $extraLifetime)
00405 {
00406 $metadatas = $this->_getMetadatas($id);
00407 if (!$metadatas) {
00408 return false;
00409 }
00410 if (time() > $metadatas['expire']) {
00411 return false;
00412 }
00413 $newMetadatas = array(
00414 'hash' => $metadatas['hash'],
00415 'mtime' => time(),
00416 'expire' => $metadatas['expire'] + $extraLifetime,
00417 'tags' => $metadatas['tags']
00418 );
00419 $res = $this->_setMetadatas($id, $newMetadatas);
00420 if (!$res) {
00421 return false;
00422 }
00423 return true;
00424 }
00425
00440 public function getCapabilities()
00441 {
00442 return array(
00443 'automatic_cleaning' => true,
00444 'tags' => true,
00445 'expired_read' => true,
00446 'priority' => false,
00447 'infinite_lifetime' => true,
00448 'get_list' => true
00449 );
00450 }
00451
00459 public function ___expire($id)
00460 {
00461 $metadatas = $this->_getMetadatas($id);
00462 if ($metadatas) {
00463 $metadatas['expire'] = 1;
00464 $this->_setMetadatas($id, $metadatas);
00465 }
00466 }
00467
00474 protected function _getMetadatas($id)
00475 {
00476 if (isset($this->_metadatasArray[$id])) {
00477 return $this->_metadatasArray[$id];
00478 } else {
00479 $metadatas = $this->_loadMetadatas($id);
00480 if (!$metadatas) {
00481 return false;
00482 }
00483 $this->_setMetadatas($id, $metadatas, false);
00484 return $metadatas;
00485 }
00486 }
00487
00496 protected function _setMetadatas($id, $metadatas, $save = true)
00497 {
00498 if (count($this->_metadatasArray) >= $this->_options['metadatas_array_max_size']) {
00499 $n = (int) ($this->_options['metadatas_array_max_size'] / 10);
00500 $this->_metadatasArray = array_slice($this->_metadatasArray, $n);
00501 }
00502 if ($save) {
00503 $result = $this->_saveMetadatas($id, $metadatas);
00504 if (!$result) {
00505 return false;
00506 }
00507 }
00508 $this->_metadatasArray[$id] = $metadatas;
00509 return true;
00510 }
00511
00518 protected function _delMetadatas($id)
00519 {
00520 if (isset($this->_metadatasArray[$id])) {
00521 unset($this->_metadatasArray[$id]);
00522 }
00523 $file = $this->_metadatasFile($id);
00524 return $this->_remove($file);
00525 }
00526
00532 protected function _cleanMetadatas()
00533 {
00534 $this->_metadatasArray = array();
00535 }
00536
00543 protected function _loadMetadatas($id)
00544 {
00545 $file = $this->_metadatasFile($id);
00546 $result = $this->_fileGetContents($file);
00547 if (!$result) {
00548 return false;
00549 }
00550 $tmp = @unserialize($result);
00551 return $tmp;
00552 }
00553
00561 protected function _saveMetadatas($id, $metadatas)
00562 {
00563 $file = $this->_metadatasFile($id);
00564 $result = $this->_filePutContents($file, serialize($metadatas));
00565 if (!$result) {
00566 return false;
00567 }
00568 return true;
00569 }
00570
00577 protected function _metadatasFile($id)
00578 {
00579 $path = $this->_path($id);
00580 $fileName = $this->_idToFileName('internal-metadatas---' . $id);
00581 return $path . $fileName;
00582 }
00583
00590 protected function _isMetadatasFile($fileName)
00591 {
00592 $id = $this->_fileNameToId($fileName);
00593 if (substr($id, 0, 21) == 'internal-metadatas---') {
00594 return true;
00595 } else {
00596 return false;
00597 }
00598 }
00599
00609 protected function _remove($file)
00610 {
00611 if (!is_file($file)) {
00612 return false;
00613 }
00614 if (!@unlink($file)) {
00615 # we can't remove the file (because of locks or any problem)
00616 $this->_log("Zend_Cache_Backend_File::_remove() : we can't remove $file");
00617 return false;
00618 }
00619 return true;
00620 }
00621
00641 protected function _clean($dir, $mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
00642 {
00643 if (!is_dir($dir)) {
00644 return false;
00645 }
00646 $result = true;
00647 $prefix = $this->_options['file_name_prefix'];
00648 $glob = @glob($dir . $prefix . '--*');
00649 if ($glob === false) {
00650 return true;
00651 }
00652 foreach ($glob as $file) {
00653 if (is_file($file)) {
00654 $fileName = basename($file);
00655 if ($this->_isMetadatasFile($fileName)) {
00656
00657 if ($mode != Zend_Cache::CLEANING_MODE_ALL) {
00658 continue;
00659 }
00660 }
00661 $id = $this->_fileNameToId($fileName);
00662 $metadatas = $this->_getMetadatas($id);
00663 if ($metadatas === FALSE) {
00664 $metadatas = array('expire' => 1, 'tags' => array());
00665 }
00666 switch ($mode) {
00667 case Zend_Cache::CLEANING_MODE_ALL:
00668 $res = $this->remove($id);
00669 if (!$res) {
00670
00671 $res = $this->_remove($file);
00672 }
00673 $result = $result && $res;
00674 break;
00675 case Zend_Cache::CLEANING_MODE_OLD:
00676 if (time() > $metadatas['expire']) {
00677 $result = $this->remove($id) && $result;
00678 }
00679 break;
00680 case Zend_Cache::CLEANING_MODE_MATCHING_TAG:
00681 $matching = true;
00682 foreach ($tags as $tag) {
00683 if (!in_array($tag, $metadatas['tags'])) {
00684 $matching = false;
00685 break;
00686 }
00687 }
00688 if ($matching) {
00689 $result = $this->remove($id) && $result;
00690 }
00691 break;
00692 case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG:
00693 $matching = false;
00694 foreach ($tags as $tag) {
00695 if (in_array($tag, $metadatas['tags'])) {
00696 $matching = true;
00697 break;
00698 }
00699 }
00700 if (!$matching) {
00701 $result = $this->remove($id) && $result;
00702 }
00703 break;
00704 case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG:
00705 $matching = false;
00706 foreach ($tags as $tag) {
00707 if (in_array($tag, $metadatas['tags'])) {
00708 $matching = true;
00709 break;
00710 }
00711 }
00712 if ($matching) {
00713 $result = $this->remove($id) && $result;
00714 }
00715 break;
00716 default:
00717 Zend_Cache::throwException('Invalid mode for clean() method');
00718 break;
00719 }
00720 }
00721 if ((is_dir($file)) and ($this->_options['hashed_directory_level']>0)) {
00722
00723 $result = $this->_clean($file . DIRECTORY_SEPARATOR, $mode, $tags) && $result;
00724 if ($mode=='all') {
00725
00726 @rmdir($file);
00727 }
00728 }
00729 }
00730 return $result;
00731 }
00732
00733 protected function _get($dir, $mode, $tags = array())
00734 {
00735 if (!is_dir($dir)) {
00736 return false;
00737 }
00738 $result = array();
00739 $prefix = $this->_options['file_name_prefix'];
00740 $glob = @glob($dir . $prefix . '--*');
00741 if ($glob === false) {
00742 return true;
00743 }
00744 foreach ($glob as $file) {
00745 if (is_file($file)) {
00746 $fileName = basename($file);
00747 $id = $this->_fileNameToId($fileName);
00748 $metadatas = $this->_getMetadatas($id);
00749 if ($metadatas === FALSE) {
00750 continue;
00751 }
00752 if (time() > $metadatas['expire']) {
00753 continue;
00754 }
00755 switch ($mode) {
00756 case 'ids':
00757 $result[] = $id;
00758 break;
00759 case 'tags':
00760 $result = array_unique(array_merge($result, $metadatas['tags']));
00761 break;
00762 case 'matching':
00763 $matching = true;
00764 foreach ($tags as $tag) {
00765 if (!in_array($tag, $metadatas['tags'])) {
00766 $matching = false;
00767 break;
00768 }
00769 }
00770 if ($matching) {
00771 $result[] = $id;
00772 }
00773 break;
00774 case 'notMatching':
00775 $matching = false;
00776 foreach ($tags as $tag) {
00777 if (in_array($tag, $metadatas['tags'])) {
00778 $matching = true;
00779 break;
00780 }
00781 }
00782 if (!$matching) {
00783 $result[] = $id;
00784 }
00785 break;
00786 case 'matchingAny':
00787 $matching = false;
00788 foreach ($tags as $tag) {
00789 if (in_array($tag, $metadatas['tags'])) {
00790 $matching = true;
00791 break;
00792 }
00793 }
00794 if ($matching) {
00795 $result[] = $id;
00796 }
00797 break;
00798 default:
00799 Zend_Cache::throwException('Invalid mode for _get() method');
00800 break;
00801 }
00802 }
00803 if ((is_dir($file)) and ($this->_options['hashed_directory_level']>0)) {
00804
00805 $result = array_unique(array_merge($result, $this->_get($file . DIRECTORY_SEPARATOR, $mode, $tags)));
00806 }
00807 }
00808 return array_unique($result);
00809 }
00810
00816 protected function _expireTime($lifetime)
00817 {
00818 if ($lifetime === null) {
00819 return 9999999999;
00820 }
00821 return time() + $lifetime;
00822 }
00823
00832 protected function _hash($data, $controlType)
00833 {
00834 switch ($controlType) {
00835 case 'md5':
00836 return md5($data);
00837 case 'crc32':
00838 return crc32($data);
00839 case 'strlen':
00840 return strlen($data);
00841 case 'adler32':
00842 return hash('adler32', $data);
00843 default:
00844 Zend_Cache::throwException("Incorrect hash function : $controlType");
00845 }
00846 }
00847
00854 protected function _idToFileName($id)
00855 {
00856 $prefix = $this->_options['file_name_prefix'];
00857 $result = $prefix . '---' . $id;
00858 return $result;
00859 }
00860
00867 protected function _file($id)
00868 {
00869 $path = $this->_path($id);
00870 $fileName = $this->_idToFileName($id);
00871 return $path . $fileName;
00872 }
00873
00881 protected function _path($id, $parts = false)
00882 {
00883 $partsArray = array();
00884 $root = $this->_options['cache_dir'];
00885 $prefix = $this->_options['file_name_prefix'];
00886 if ($this->_options['hashed_directory_level']>0) {
00887 $hash = hash('adler32', $id);
00888 for ($i=0 ; $i < $this->_options['hashed_directory_level'] ; $i++) {
00889 $root = $root . $prefix . '--' . substr($hash, 0, $i + 1) . DIRECTORY_SEPARATOR;
00890 $partsArray[] = $root;
00891 }
00892 }
00893 if ($parts) {
00894 return $partsArray;
00895 } else {
00896 return $root;
00897 }
00898 }
00899
00906 protected function _recursiveMkdirAndChmod($id)
00907 {
00908 if ($this->_options['hashed_directory_level'] <=0) {
00909 return true;
00910 }
00911 $partsArray = $this->_path($id, true);
00912 foreach ($partsArray as $part) {
00913 if (!is_dir($part)) {
00914 @mkdir($part, $this->_options['hashed_directory_umask']);
00915 @chmod($part, $this->_options['hashed_directory_umask']);
00916 }
00917 }
00918 return true;
00919 }
00920
00928 protected function _test($id, $doNotTestCacheValidity)
00929 {
00930 $metadatas = $this->_getMetadatas($id);
00931 if (!$metadatas) {
00932 return false;
00933 }
00934 if ($doNotTestCacheValidity || (time() <= $metadatas['expire'])) {
00935 return $metadatas['mtime'];
00936 }
00937 return false;
00938 }
00939
00946 protected function _fileGetContents($file)
00947 {
00948 $result = false;
00949 if (!is_file($file)) {
00950 return false;
00951 }
00952 $f = @fopen($file, 'rb');
00953 if ($f) {
00954 if ($this->_options['file_locking']) @flock($f, LOCK_SH);
00955 $result = stream_get_contents($f);
00956 if ($this->_options['file_locking']) @flock($f, LOCK_UN);
00957 @fclose($f);
00958 }
00959 return $result;
00960 }
00961
00969 protected function _filePutContents($file, $string)
00970 {
00971 $result = false;
00972 $f = @fopen($file, 'ab+');
00973 if ($f) {
00974 if ($this->_options['file_locking']) @flock($f, LOCK_EX);
00975 fseek($f, 0);
00976 ftruncate($f, 0);
00977 $tmp = @fwrite($f, $string);
00978 if (!($tmp === FALSE)) {
00979 $result = true;
00980 }
00981 @fclose($f);
00982 }
00983 @chmod($file, $this->_options['cache_file_umask']);
00984 return $result;
00985 }
00986
00993 protected function _fileNameToId($fileName)
00994 {
00995 $prefix = $this->_options['file_name_prefix'];
00996 return preg_replace('~^' . $prefix . '---(.*)$~', '$1', $fileName);
00997 }
00998
00999 }