web/lib/Zend/Cache/Backend/File.php
changeset 64 162c1de6545a
parent 19 1c2f13fd785c
child 68 ecaf28ffe26e
equal deleted inserted replaced
63:5b37998e522e 64:162c1de6545a
       
     1 <?php
       
     2 /**
       
     3  * Zend Framework
       
     4  *
       
     5  * LICENSE
       
     6  *
       
     7  * This source file is subject to the new BSD license that is bundled
       
     8  * with this package in the file LICENSE.txt.
       
     9  * It is also available through the world-wide-web at this URL:
       
    10  * http://framework.zend.com/license/new-bsd
       
    11  * If you did not receive a copy of the license and are unable to
       
    12  * obtain it through the world-wide-web, please send an email
       
    13  * to license@zend.com so we can send you a copy immediately.
       
    14  *
       
    15  * @category   Zend
       
    16  * @package    Zend_Cache
       
    17  * @subpackage Zend_Cache_Backend
       
    18  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    19  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    20  * @version    $Id: File.php 21636 2010-03-24 17:10:23Z mabe $
       
    21  */
       
    22 
       
    23 /**
       
    24  * @see Zend_Cache_Backend_Interface
       
    25  */
       
    26 require_once 'Zend/Cache/Backend/ExtendedInterface.php';
       
    27 
       
    28 /**
       
    29  * @see Zend_Cache_Backend
       
    30  */
       
    31 require_once 'Zend/Cache/Backend.php';
       
    32 
       
    33 
       
    34 /**
       
    35  * @package    Zend_Cache
       
    36  * @subpackage Zend_Cache_Backend
       
    37  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    38  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    39  */
       
    40 class Zend_Cache_Backend_File extends Zend_Cache_Backend implements Zend_Cache_Backend_ExtendedInterface
       
    41 {
       
    42     /**
       
    43      * Available options
       
    44      *
       
    45      * =====> (string) cache_dir :
       
    46      * - Directory where to put the cache files
       
    47      *
       
    48      * =====> (boolean) file_locking :
       
    49      * - Enable / disable file_locking
       
    50      * - Can avoid cache corruption under bad circumstances but it doesn't work on multithread
       
    51      * webservers and on NFS filesystems for example
       
    52      *
       
    53      * =====> (boolean) read_control :
       
    54      * - Enable / disable read control
       
    55      * - If enabled, a control key is embeded in cache file and this key is compared with the one
       
    56      * calculated after the reading.
       
    57      *
       
    58      * =====> (string) read_control_type :
       
    59      * - Type of read control (only if read control is enabled). Available values are :
       
    60      *   'md5' for a md5 hash control (best but slowest)
       
    61      *   'crc32' for a crc32 hash control (lightly less safe but faster, better choice)
       
    62      *   'adler32' for an adler32 hash control (excellent choice too, faster than crc32)
       
    63      *   'strlen' for a length only test (fastest)
       
    64      *
       
    65      * =====> (int) hashed_directory_level :
       
    66      * - Hashed directory level
       
    67      * - Set the hashed directory structure level. 0 means "no hashed directory
       
    68      * structure", 1 means "one level of directory", 2 means "two levels"...
       
    69      * This option can speed up the cache only when you have many thousands of
       
    70      * cache file. Only specific benchs can help you to choose the perfect value
       
    71      * for you. Maybe, 1 or 2 is a good start.
       
    72      *
       
    73      * =====> (int) hashed_directory_umask :
       
    74      * - Umask for hashed directory structure
       
    75      *
       
    76      * =====> (string) file_name_prefix :
       
    77      * - prefix for cache files
       
    78      * - be really carefull with this option because a too generic value in a system cache dir
       
    79      *   (like /tmp) can cause disasters when cleaning the cache
       
    80      *
       
    81      * =====> (int) cache_file_umask :
       
    82      * - Umask for cache files
       
    83      *
       
    84      * =====> (int) metatadatas_array_max_size :
       
    85      * - max size for the metadatas array (don't change this value unless you
       
    86      *   know what you are doing)
       
    87      *
       
    88      * @var array available options
       
    89      */
       
    90     protected $_options = array(
       
    91         'cache_dir' => null,
       
    92         'file_locking' => true,
       
    93         'read_control' => true,
       
    94         'read_control_type' => 'crc32',
       
    95         'hashed_directory_level' => 0,
       
    96         'hashed_directory_umask' => 0700,
       
    97         'file_name_prefix' => 'zend_cache',
       
    98         'cache_file_umask' => 0600,
       
    99         'metadatas_array_max_size' => 100
       
   100     );
       
   101 
       
   102     /**
       
   103      * Array of metadatas (each item is an associative array)
       
   104      *
       
   105      * @var array
       
   106      */
       
   107     protected $_metadatasArray = array();
       
   108 
       
   109 
       
   110     /**
       
   111      * Constructor
       
   112      *
       
   113      * @param  array $options associative array of options
       
   114      * @throws Zend_Cache_Exception
       
   115      * @return void
       
   116      */
       
   117     public function __construct(array $options = array())
       
   118     {
       
   119         parent::__construct($options);
       
   120         if ($this->_options['cache_dir'] !== null) { // particular case for this option
       
   121             $this->setCacheDir($this->_options['cache_dir']);
       
   122         } else {
       
   123             $this->setCacheDir(self::getTmpDir() . DIRECTORY_SEPARATOR, false);
       
   124         }
       
   125         if (isset($this->_options['file_name_prefix'])) { // particular case for this option
       
   126             if (!preg_match('~^[a-zA-Z0-9_]+$~D', $this->_options['file_name_prefix'])) {
       
   127                 Zend_Cache::throwException('Invalid file_name_prefix : must use only [a-zA-Z0-9_]');
       
   128             }
       
   129         }
       
   130         if ($this->_options['metadatas_array_max_size'] < 10) {
       
   131             Zend_Cache::throwException('Invalid metadatas_array_max_size, must be > 10');
       
   132         }
       
   133         if (isset($options['hashed_directory_umask']) && is_string($options['hashed_directory_umask'])) {
       
   134             // See #ZF-4422
       
   135             $this->_options['hashed_directory_umask'] = octdec($this->_options['hashed_directory_umask']);
       
   136         }
       
   137         if (isset($options['cache_file_umask']) && is_string($options['cache_file_umask'])) {
       
   138             // See #ZF-4422
       
   139             $this->_options['cache_file_umask'] = octdec($this->_options['cache_file_umask']);
       
   140         }
       
   141     }
       
   142 
       
   143     /**
       
   144      * Set the cache_dir (particular case of setOption() method)
       
   145      *
       
   146      * @param  string  $value
       
   147      * @param  boolean $trailingSeparator If true, add a trailing separator is necessary
       
   148      * @throws Zend_Cache_Exception
       
   149      * @return void
       
   150      */
       
   151     public function setCacheDir($value, $trailingSeparator = true)
       
   152     {
       
   153         if (!is_dir($value)) {
       
   154             Zend_Cache::throwException('cache_dir must be a directory');
       
   155         }
       
   156         if (!is_writable($value)) {
       
   157             Zend_Cache::throwException('cache_dir is not writable');
       
   158         }
       
   159         if ($trailingSeparator) {
       
   160             // add a trailing DIRECTORY_SEPARATOR if necessary
       
   161             $value = rtrim(realpath($value), '\\/') . DIRECTORY_SEPARATOR;
       
   162         }
       
   163         $this->_options['cache_dir'] = $value;
       
   164     }
       
   165 
       
   166     /**
       
   167      * Test if a cache is available for the given id and (if yes) return it (false else)
       
   168      *
       
   169      * @param string $id cache id
       
   170      * @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested
       
   171      * @return string|false cached datas
       
   172      */
       
   173     public function load($id, $doNotTestCacheValidity = false)
       
   174     {
       
   175         if (!($this->_test($id, $doNotTestCacheValidity))) {
       
   176             // The cache is not hit !
       
   177             return false;
       
   178         }
       
   179         $metadatas = $this->_getMetadatas($id);
       
   180         $file = $this->_file($id);
       
   181         $data = $this->_fileGetContents($file);
       
   182         if ($this->_options['read_control']) {
       
   183             $hashData = $this->_hash($data, $this->_options['read_control_type']);
       
   184             $hashControl = $metadatas['hash'];
       
   185             if ($hashData != $hashControl) {
       
   186                 // Problem detected by the read control !
       
   187                 $this->_log('Zend_Cache_Backend_File::load() / read_control : stored hash and computed hash do not match');
       
   188                 $this->remove($id);
       
   189                 return false;
       
   190             }
       
   191         }
       
   192         return $data;
       
   193     }
       
   194 
       
   195     /**
       
   196      * Test if a cache is available or not (for the given id)
       
   197      *
       
   198      * @param string $id cache id
       
   199      * @return mixed false (a cache is not available) or "last modified" timestamp (int) of the available cache record
       
   200      */
       
   201     public function test($id)
       
   202     {
       
   203         clearstatcache();
       
   204         return $this->_test($id, false);
       
   205     }
       
   206 
       
   207     /**
       
   208      * Save some string datas into a cache record
       
   209      *
       
   210      * Note : $data is always "string" (serialization is done by the
       
   211      * core not by the backend)
       
   212      *
       
   213      * @param  string $data             Datas to cache
       
   214      * @param  string $id               Cache id
       
   215      * @param  array  $tags             Array of strings, the cache record will be tagged by each string entry
       
   216      * @param  int    $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
       
   217      * @return boolean true if no problem
       
   218      */
       
   219     public function save($data, $id, $tags = array(), $specificLifetime = false)
       
   220     {
       
   221         clearstatcache();
       
   222         $file = $this->_file($id);
       
   223         $path = $this->_path($id);
       
   224         if ($this->_options['hashed_directory_level'] > 0) {
       
   225             if (!is_writable($path)) {
       
   226                 // maybe, we just have to build the directory structure
       
   227                 $this->_recursiveMkdirAndChmod($id);
       
   228             }
       
   229             if (!is_writable($path)) {
       
   230                 return false;
       
   231             }
       
   232         }
       
   233         if ($this->_options['read_control']) {
       
   234             $hash = $this->_hash($data, $this->_options['read_control_type']);
       
   235         } else {
       
   236             $hash = '';
       
   237         }
       
   238         $metadatas = array(
       
   239             'hash' => $hash,
       
   240             'mtime' => time(),
       
   241             'expire' => $this->_expireTime($this->getLifetime($specificLifetime)),
       
   242             'tags' => $tags
       
   243         );
       
   244         $res = $this->_setMetadatas($id, $metadatas);
       
   245         if (!$res) {
       
   246             $this->_log('Zend_Cache_Backend_File::save() / error on saving metadata');
       
   247             return false;
       
   248         }
       
   249         $res = $this->_filePutContents($file, $data);
       
   250         return $res;
       
   251     }
       
   252 
       
   253     /**
       
   254      * Remove a cache record
       
   255      *
       
   256      * @param  string $id cache id
       
   257      * @return boolean true if no problem
       
   258      */
       
   259     public function remove($id)
       
   260     {
       
   261         $file = $this->_file($id);
       
   262         $boolRemove   = $this->_remove($file);
       
   263         $boolMetadata = $this->_delMetadatas($id);
       
   264         return $boolMetadata && $boolRemove;
       
   265     }
       
   266 
       
   267     /**
       
   268      * Clean some cache records
       
   269      *
       
   270      * Available modes are :
       
   271      * 'all' (default)  => remove all cache entries ($tags is not used)
       
   272      * 'old'            => remove too old cache entries ($tags is not used)
       
   273      * 'matchingTag'    => remove cache entries matching all given tags
       
   274      *                     ($tags can be an array of strings or a single string)
       
   275      * 'notMatchingTag' => remove cache entries not matching one of the given tags
       
   276      *                     ($tags can be an array of strings or a single string)
       
   277      * 'matchingAnyTag' => remove cache entries matching any given tags
       
   278      *                     ($tags can be an array of strings or a single string)
       
   279      *
       
   280      * @param string $mode clean mode
       
   281      * @param tags array $tags array of tags
       
   282      * @return boolean true if no problem
       
   283      */
       
   284     public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
       
   285     {
       
   286         // We use this protected method to hide the recursive stuff
       
   287         clearstatcache();
       
   288         return $this->_clean($this->_options['cache_dir'], $mode, $tags);
       
   289     }
       
   290 
       
   291     /**
       
   292      * Return an array of stored cache ids
       
   293      *
       
   294      * @return array array of stored cache ids (string)
       
   295      */
       
   296     public function getIds()
       
   297     {
       
   298         return $this->_get($this->_options['cache_dir'], 'ids', array());
       
   299     }
       
   300 
       
   301     /**
       
   302      * Return an array of stored tags
       
   303      *
       
   304      * @return array array of stored tags (string)
       
   305      */
       
   306     public function getTags()
       
   307     {
       
   308         return $this->_get($this->_options['cache_dir'], 'tags', array());
       
   309     }
       
   310 
       
   311     /**
       
   312      * Return an array of stored cache ids which match given tags
       
   313      *
       
   314      * In case of multiple tags, a logical AND is made between tags
       
   315      *
       
   316      * @param array $tags array of tags
       
   317      * @return array array of matching cache ids (string)
       
   318      */
       
   319     public function getIdsMatchingTags($tags = array())
       
   320     {
       
   321         return $this->_get($this->_options['cache_dir'], 'matching', $tags);
       
   322     }
       
   323 
       
   324     /**
       
   325      * Return an array of stored cache ids which don't match given tags
       
   326      *
       
   327      * In case of multiple tags, a logical OR is made between tags
       
   328      *
       
   329      * @param array $tags array of tags
       
   330      * @return array array of not matching cache ids (string)
       
   331      */
       
   332     public function getIdsNotMatchingTags($tags = array())
       
   333     {
       
   334         return $this->_get($this->_options['cache_dir'], 'notMatching', $tags);
       
   335     }
       
   336 
       
   337     /**
       
   338      * Return an array of stored cache ids which match any given tags
       
   339      *
       
   340      * In case of multiple tags, a logical AND is made between tags
       
   341      *
       
   342      * @param array $tags array of tags
       
   343      * @return array array of any matching cache ids (string)
       
   344      */
       
   345     public function getIdsMatchingAnyTags($tags = array())
       
   346     {
       
   347         return $this->_get($this->_options['cache_dir'], 'matchingAny', $tags);
       
   348     }
       
   349 
       
   350     /**
       
   351      * Return the filling percentage of the backend storage
       
   352      *
       
   353      * @throws Zend_Cache_Exception
       
   354      * @return int integer between 0 and 100
       
   355      */
       
   356     public function getFillingPercentage()
       
   357     {
       
   358         $free = disk_free_space($this->_options['cache_dir']);
       
   359         $total = disk_total_space($this->_options['cache_dir']);
       
   360         if ($total == 0) {
       
   361             Zend_Cache::throwException('can\'t get disk_total_space');
       
   362         } else {
       
   363             if ($free >= $total) {
       
   364                 return 100;
       
   365             }
       
   366             return ((int) (100. * ($total - $free) / $total));
       
   367         }
       
   368     }
       
   369 
       
   370     /**
       
   371      * Return an array of metadatas for the given cache id
       
   372      *
       
   373      * The array must include these keys :
       
   374      * - expire : the expire timestamp
       
   375      * - tags : a string array of tags
       
   376      * - mtime : timestamp of last modification time
       
   377      *
       
   378      * @param string $id cache id
       
   379      * @return array array of metadatas (false if the cache id is not found)
       
   380      */
       
   381     public function getMetadatas($id)
       
   382     {
       
   383         $metadatas = $this->_getMetadatas($id);
       
   384         if (!$metadatas) {
       
   385             return false;
       
   386         }
       
   387         if (time() > $metadatas['expire']) {
       
   388             return false;
       
   389         }
       
   390         return array(
       
   391             'expire' => $metadatas['expire'],
       
   392             'tags' => $metadatas['tags'],
       
   393             'mtime' => $metadatas['mtime']
       
   394         );
       
   395     }
       
   396 
       
   397     /**
       
   398      * Give (if possible) an extra lifetime to the given cache id
       
   399      *
       
   400      * @param string $id cache id
       
   401      * @param int $extraLifetime
       
   402      * @return boolean true if ok
       
   403      */
       
   404     public function touch($id, $extraLifetime)
       
   405     {
       
   406         $metadatas = $this->_getMetadatas($id);
       
   407         if (!$metadatas) {
       
   408             return false;
       
   409         }
       
   410         if (time() > $metadatas['expire']) {
       
   411             return false;
       
   412         }
       
   413         $newMetadatas = array(
       
   414             'hash' => $metadatas['hash'],
       
   415             'mtime' => time(),
       
   416             'expire' => $metadatas['expire'] + $extraLifetime,
       
   417             'tags' => $metadatas['tags']
       
   418         );
       
   419         $res = $this->_setMetadatas($id, $newMetadatas);
       
   420         if (!$res) {
       
   421             return false;
       
   422         }
       
   423         return true;
       
   424     }
       
   425 
       
   426     /**
       
   427      * Return an associative array of capabilities (booleans) of the backend
       
   428      *
       
   429      * The array must include these keys :
       
   430      * - automatic_cleaning (is automating cleaning necessary)
       
   431      * - tags (are tags supported)
       
   432      * - expired_read (is it possible to read expired cache records
       
   433      *                 (for doNotTestCacheValidity option for example))
       
   434      * - priority does the backend deal with priority when saving
       
   435      * - infinite_lifetime (is infinite lifetime can work with this backend)
       
   436      * - get_list (is it possible to get the list of cache ids and the complete list of tags)
       
   437      *
       
   438      * @return array associative of with capabilities
       
   439      */
       
   440     public function getCapabilities()
       
   441     {
       
   442         return array(
       
   443             'automatic_cleaning' => true,
       
   444             'tags' => true,
       
   445             'expired_read' => true,
       
   446             'priority' => false,
       
   447             'infinite_lifetime' => true,
       
   448             'get_list' => true
       
   449         );
       
   450     }
       
   451 
       
   452     /**
       
   453      * PUBLIC METHOD FOR UNIT TESTING ONLY !
       
   454      *
       
   455      * Force a cache record to expire
       
   456      *
       
   457      * @param string $id cache id
       
   458      */
       
   459     public function ___expire($id)
       
   460     {
       
   461         $metadatas = $this->_getMetadatas($id);
       
   462         if ($metadatas) {
       
   463             $metadatas['expire'] = 1;
       
   464             $this->_setMetadatas($id, $metadatas);
       
   465         }
       
   466     }
       
   467 
       
   468     /**
       
   469      * Get a metadatas record
       
   470      *
       
   471      * @param  string $id  Cache id
       
   472      * @return array|false Associative array of metadatas
       
   473      */
       
   474     protected function _getMetadatas($id)
       
   475     {
       
   476         if (isset($this->_metadatasArray[$id])) {
       
   477             return $this->_metadatasArray[$id];
       
   478         } else {
       
   479             $metadatas = $this->_loadMetadatas($id);
       
   480             if (!$metadatas) {
       
   481                 return false;
       
   482             }
       
   483             $this->_setMetadatas($id, $metadatas, false);
       
   484             return $metadatas;
       
   485         }
       
   486     }
       
   487 
       
   488     /**
       
   489      * Set a metadatas record
       
   490      *
       
   491      * @param  string $id        Cache id
       
   492      * @param  array  $metadatas Associative array of metadatas
       
   493      * @param  boolean $save     optional pass false to disable saving to file
       
   494      * @return boolean True if no problem
       
   495      */
       
   496     protected function _setMetadatas($id, $metadatas, $save = true)
       
   497     {
       
   498         if (count($this->_metadatasArray) >= $this->_options['metadatas_array_max_size']) {
       
   499             $n = (int) ($this->_options['metadatas_array_max_size'] / 10);
       
   500             $this->_metadatasArray = array_slice($this->_metadatasArray, $n);
       
   501         }
       
   502         if ($save) {
       
   503             $result = $this->_saveMetadatas($id, $metadatas);
       
   504             if (!$result) {
       
   505                 return false;
       
   506             }
       
   507         }
       
   508         $this->_metadatasArray[$id] = $metadatas;
       
   509         return true;
       
   510     }
       
   511 
       
   512     /**
       
   513      * Drop a metadata record
       
   514      *
       
   515      * @param  string $id Cache id
       
   516      * @return boolean True if no problem
       
   517      */
       
   518     protected function _delMetadatas($id)
       
   519     {
       
   520         if (isset($this->_metadatasArray[$id])) {
       
   521             unset($this->_metadatasArray[$id]);
       
   522         }
       
   523         $file = $this->_metadatasFile($id);
       
   524         return $this->_remove($file);
       
   525     }
       
   526 
       
   527     /**
       
   528      * Clear the metadatas array
       
   529      *
       
   530      * @return void
       
   531      */
       
   532     protected function _cleanMetadatas()
       
   533     {
       
   534         $this->_metadatasArray = array();
       
   535     }
       
   536 
       
   537     /**
       
   538      * Load metadatas from disk
       
   539      *
       
   540      * @param  string $id Cache id
       
   541      * @return array|false Metadatas associative array
       
   542      */
       
   543     protected function _loadMetadatas($id)
       
   544     {
       
   545         $file = $this->_metadatasFile($id);
       
   546         $result = $this->_fileGetContents($file);
       
   547         if (!$result) {
       
   548             return false;
       
   549         }
       
   550         $tmp = @unserialize($result);
       
   551         return $tmp;
       
   552     }
       
   553 
       
   554     /**
       
   555      * Save metadatas to disk
       
   556      *
       
   557      * @param  string $id        Cache id
       
   558      * @param  array  $metadatas Associative array
       
   559      * @return boolean True if no problem
       
   560      */
       
   561     protected function _saveMetadatas($id, $metadatas)
       
   562     {
       
   563         $file = $this->_metadatasFile($id);
       
   564         $result = $this->_filePutContents($file, serialize($metadatas));
       
   565         if (!$result) {
       
   566             return false;
       
   567         }
       
   568         return true;
       
   569     }
       
   570 
       
   571     /**
       
   572      * Make and return a file name (with path) for metadatas
       
   573      *
       
   574      * @param  string $id Cache id
       
   575      * @return string Metadatas file name (with path)
       
   576      */
       
   577     protected function _metadatasFile($id)
       
   578     {
       
   579         $path = $this->_path($id);
       
   580         $fileName = $this->_idToFileName('internal-metadatas---' . $id);
       
   581         return $path . $fileName;
       
   582     }
       
   583 
       
   584     /**
       
   585      * Check if the given filename is a metadatas one
       
   586      *
       
   587      * @param  string $fileName File name
       
   588      * @return boolean True if it's a metadatas one
       
   589      */
       
   590     protected function _isMetadatasFile($fileName)
       
   591     {
       
   592         $id = $this->_fileNameToId($fileName);
       
   593         if (substr($id, 0, 21) == 'internal-metadatas---') {
       
   594             return true;
       
   595         } else {
       
   596             return false;
       
   597         }
       
   598     }
       
   599 
       
   600     /**
       
   601      * Remove a file
       
   602      *
       
   603      * If we can't remove the file (because of locks or any problem), we will touch
       
   604      * the file to invalidate it
       
   605      *
       
   606      * @param  string $file Complete file path
       
   607      * @return boolean True if ok
       
   608      */
       
   609     protected function _remove($file)
       
   610     {
       
   611         if (!is_file($file)) {
       
   612             return false;
       
   613         }
       
   614         if (!@unlink($file)) {
       
   615             # we can't remove the file (because of locks or any problem)
       
   616             $this->_log("Zend_Cache_Backend_File::_remove() : we can't remove $file");
       
   617             return false;
       
   618         }
       
   619         return true;
       
   620     }
       
   621 
       
   622     /**
       
   623      * Clean some cache records (protected method used for recursive stuff)
       
   624      *
       
   625      * Available modes are :
       
   626      * Zend_Cache::CLEANING_MODE_ALL (default)    => remove all cache entries ($tags is not used)
       
   627      * Zend_Cache::CLEANING_MODE_OLD              => remove too old cache entries ($tags is not used)
       
   628      * Zend_Cache::CLEANING_MODE_MATCHING_TAG     => remove cache entries matching all given tags
       
   629      *                                               ($tags can be an array of strings or a single string)
       
   630      * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags}
       
   631      *                                               ($tags can be an array of strings or a single string)
       
   632      * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags
       
   633      *                                               ($tags can be an array of strings or a single string)
       
   634      *
       
   635      * @param  string $dir  Directory to clean
       
   636      * @param  string $mode Clean mode
       
   637      * @param  array  $tags Array of tags
       
   638      * @throws Zend_Cache_Exception
       
   639      * @return boolean True if no problem
       
   640      */
       
   641     protected function _clean($dir, $mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
       
   642     {
       
   643         if (!is_dir($dir)) {
       
   644             return false;
       
   645         }
       
   646         $result = true;
       
   647         $prefix = $this->_options['file_name_prefix'];
       
   648         $glob = @glob($dir . $prefix . '--*');
       
   649         if ($glob === false) {
       
   650             // On some systems it is impossible to distinguish between empty match and an error.
       
   651             return true;
       
   652         }
       
   653         foreach ($glob as $file)  {
       
   654             if (is_file($file)) {
       
   655                 $fileName = basename($file);
       
   656                 if ($this->_isMetadatasFile($fileName)) {
       
   657                     // in CLEANING_MODE_ALL, we drop anything, even remainings old metadatas files
       
   658                     if ($mode != Zend_Cache::CLEANING_MODE_ALL) {
       
   659                         continue;
       
   660                     }
       
   661                 }
       
   662                 $id = $this->_fileNameToId($fileName);
       
   663                 $metadatas = $this->_getMetadatas($id);
       
   664                 if ($metadatas === FALSE) {
       
   665                     $metadatas = array('expire' => 1, 'tags' => array());
       
   666                 }
       
   667                 switch ($mode) {
       
   668                     case Zend_Cache::CLEANING_MODE_ALL:
       
   669                         $res = $this->remove($id);
       
   670                         if (!$res) {
       
   671                             // in this case only, we accept a problem with the metadatas file drop
       
   672                             $res = $this->_remove($file);
       
   673                         }
       
   674                         $result = $result && $res;
       
   675                         break;
       
   676                     case Zend_Cache::CLEANING_MODE_OLD:
       
   677                         if (time() > $metadatas['expire']) {
       
   678                             $result = $this->remove($id) && $result;
       
   679                         }
       
   680                         break;
       
   681                     case Zend_Cache::CLEANING_MODE_MATCHING_TAG:
       
   682                         $matching = true;
       
   683                         foreach ($tags as $tag) {
       
   684                             if (!in_array($tag, $metadatas['tags'])) {
       
   685                                 $matching = false;
       
   686                                 break;
       
   687                             }
       
   688                         }
       
   689                         if ($matching) {
       
   690                             $result = $this->remove($id) && $result;
       
   691                         }
       
   692                         break;
       
   693                     case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG:
       
   694                         $matching = false;
       
   695                         foreach ($tags as $tag) {
       
   696                             if (in_array($tag, $metadatas['tags'])) {
       
   697                                 $matching = true;
       
   698                                 break;
       
   699                             }
       
   700                         }
       
   701                         if (!$matching) {
       
   702                             $result = $this->remove($id) && $result;
       
   703                         }
       
   704                         break;
       
   705                     case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG:
       
   706                         $matching = false;
       
   707                         foreach ($tags as $tag) {
       
   708                             if (in_array($tag, $metadatas['tags'])) {
       
   709                                 $matching = true;
       
   710                                 break;
       
   711                             }
       
   712                         }
       
   713                         if ($matching) {
       
   714                             $result = $this->remove($id) && $result;
       
   715                         }
       
   716                         break;
       
   717                     default:
       
   718                         Zend_Cache::throwException('Invalid mode for clean() method');
       
   719                         break;
       
   720                 }
       
   721             }
       
   722             if ((is_dir($file)) and ($this->_options['hashed_directory_level']>0)) {
       
   723                 // Recursive call
       
   724                 $result = $this->_clean($file . DIRECTORY_SEPARATOR, $mode, $tags) && $result;
       
   725                 if ($mode=='all') {
       
   726                     // if mode=='all', we try to drop the structure too
       
   727                     @rmdir($file);
       
   728                 }
       
   729             }
       
   730         }
       
   731         return $result;
       
   732     }
       
   733 
       
   734     protected function _get($dir, $mode, $tags = array())
       
   735     {
       
   736         if (!is_dir($dir)) {
       
   737             return false;
       
   738         }
       
   739         $result = array();
       
   740         $prefix = $this->_options['file_name_prefix'];
       
   741         $glob = @glob($dir . $prefix . '--*');
       
   742         if ($glob === false) {
       
   743             // On some systems it is impossible to distinguish between empty match and an error.
       
   744             return array();
       
   745         }
       
   746         foreach ($glob as $file)  {
       
   747             if (is_file($file)) {
       
   748                 $fileName = basename($file);
       
   749                 $id = $this->_fileNameToId($fileName);
       
   750                 $metadatas = $this->_getMetadatas($id);
       
   751                 if ($metadatas === FALSE) {
       
   752                     continue;
       
   753                 }
       
   754                 if (time() > $metadatas['expire']) {
       
   755                     continue;
       
   756                 }
       
   757                 switch ($mode) {
       
   758                     case 'ids':
       
   759                         $result[] = $id;
       
   760                         break;
       
   761                     case 'tags':
       
   762                         $result = array_unique(array_merge($result, $metadatas['tags']));
       
   763                         break;
       
   764                     case 'matching':
       
   765                         $matching = true;
       
   766                         foreach ($tags as $tag) {
       
   767                             if (!in_array($tag, $metadatas['tags'])) {
       
   768                                 $matching = false;
       
   769                                 break;
       
   770                             }
       
   771                         }
       
   772                         if ($matching) {
       
   773                             $result[] = $id;
       
   774                         }
       
   775                         break;
       
   776                     case 'notMatching':
       
   777                         $matching = false;
       
   778                         foreach ($tags as $tag) {
       
   779                             if (in_array($tag, $metadatas['tags'])) {
       
   780                                 $matching = true;
       
   781                                 break;
       
   782                             }
       
   783                         }
       
   784                         if (!$matching) {
       
   785                             $result[] = $id;
       
   786                         }
       
   787                         break;
       
   788                     case 'matchingAny':
       
   789                         $matching = false;
       
   790                         foreach ($tags as $tag) {
       
   791                             if (in_array($tag, $metadatas['tags'])) {
       
   792                                 $matching = true;
       
   793                                 break;
       
   794                             }
       
   795                         }
       
   796                         if ($matching) {
       
   797                             $result[] = $id;
       
   798                         }
       
   799                         break;
       
   800                     default:
       
   801                         Zend_Cache::throwException('Invalid mode for _get() method');
       
   802                         break;
       
   803                 }
       
   804             }
       
   805             if ((is_dir($file)) and ($this->_options['hashed_directory_level']>0)) {
       
   806                 // Recursive call
       
   807                 $recursiveRs =  $this->_get($file . DIRECTORY_SEPARATOR, $mode, $tags);
       
   808                 if ($recursiveRs === false) {
       
   809                     $this->_log('Zend_Cache_Backend_File::_get() / recursive call : can\'t list entries of "'.$file.'"');
       
   810                 } else {
       
   811                     $result = array_unique(array_merge($result, $recursiveRs));
       
   812                 }
       
   813             }
       
   814         }
       
   815         return array_unique($result);
       
   816     }
       
   817 
       
   818     /**
       
   819      * Compute & return the expire time
       
   820      *
       
   821      * @return int expire time (unix timestamp)
       
   822      */
       
   823     protected function _expireTime($lifetime)
       
   824     {
       
   825         if ($lifetime === null) {
       
   826             return 9999999999;
       
   827         }
       
   828         return time() + $lifetime;
       
   829     }
       
   830 
       
   831     /**
       
   832      * Make a control key with the string containing datas
       
   833      *
       
   834      * @param  string $data        Data
       
   835      * @param  string $controlType Type of control 'md5', 'crc32' or 'strlen'
       
   836      * @throws Zend_Cache_Exception
       
   837      * @return string Control key
       
   838      */
       
   839     protected function _hash($data, $controlType)
       
   840     {
       
   841         switch ($controlType) {
       
   842         case 'md5':
       
   843             return md5($data);
       
   844         case 'crc32':
       
   845             return crc32($data);
       
   846         case 'strlen':
       
   847             return strlen($data);
       
   848         case 'adler32':
       
   849             return hash('adler32', $data);
       
   850         default:
       
   851             Zend_Cache::throwException("Incorrect hash function : $controlType");
       
   852         }
       
   853     }
       
   854 
       
   855     /**
       
   856      * Transform a cache id into a file name and return it
       
   857      *
       
   858      * @param  string $id Cache id
       
   859      * @return string File name
       
   860      */
       
   861     protected function _idToFileName($id)
       
   862     {
       
   863         $prefix = $this->_options['file_name_prefix'];
       
   864         $result = $prefix . '---' . $id;
       
   865         return $result;
       
   866     }
       
   867 
       
   868     /**
       
   869      * Make and return a file name (with path)
       
   870      *
       
   871      * @param  string $id Cache id
       
   872      * @return string File name (with path)
       
   873      */
       
   874     protected function _file($id)
       
   875     {
       
   876         $path = $this->_path($id);
       
   877         $fileName = $this->_idToFileName($id);
       
   878         return $path . $fileName;
       
   879     }
       
   880 
       
   881     /**
       
   882      * Return the complete directory path of a filename (including hashedDirectoryStructure)
       
   883      *
       
   884      * @param  string $id Cache id
       
   885      * @param  boolean $parts if true, returns array of directory parts instead of single string
       
   886      * @return string Complete directory path
       
   887      */
       
   888     protected function _path($id, $parts = false)
       
   889     {
       
   890         $partsArray = array();
       
   891         $root = $this->_options['cache_dir'];
       
   892         $prefix = $this->_options['file_name_prefix'];
       
   893         if ($this->_options['hashed_directory_level']>0) {
       
   894             $hash = hash('adler32', $id);
       
   895             for ($i=0 ; $i < $this->_options['hashed_directory_level'] ; $i++) {
       
   896                 $root = $root . $prefix . '--' . substr($hash, 0, $i + 1) . DIRECTORY_SEPARATOR;
       
   897                 $partsArray[] = $root;
       
   898             }
       
   899         }
       
   900         if ($parts) {
       
   901             return $partsArray;
       
   902         } else {
       
   903             return $root;
       
   904         }
       
   905     }
       
   906 
       
   907     /**
       
   908      * Make the directory strucuture for the given id
       
   909      *
       
   910      * @param string $id cache id
       
   911      * @return boolean true
       
   912      */
       
   913     protected function _recursiveMkdirAndChmod($id)
       
   914     {
       
   915         if ($this->_options['hashed_directory_level'] <=0) {
       
   916             return true;
       
   917         }
       
   918         $partsArray = $this->_path($id, true);
       
   919         foreach ($partsArray as $part) {
       
   920             if (!is_dir($part)) {
       
   921                 @mkdir($part, $this->_options['hashed_directory_umask']);
       
   922                 @chmod($part, $this->_options['hashed_directory_umask']); // see #ZF-320 (this line is required in some configurations)
       
   923             }
       
   924         }
       
   925         return true;
       
   926     }
       
   927 
       
   928     /**
       
   929      * Test if the given cache id is available (and still valid as a cache record)
       
   930      *
       
   931      * @param  string  $id                     Cache id
       
   932      * @param  boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested
       
   933      * @return boolean|mixed false (a cache is not available) or "last modified" timestamp (int) of the available cache record
       
   934      */
       
   935     protected function _test($id, $doNotTestCacheValidity)
       
   936     {
       
   937         $metadatas = $this->_getMetadatas($id);
       
   938         if (!$metadatas) {
       
   939             return false;
       
   940         }
       
   941         if ($doNotTestCacheValidity || (time() <= $metadatas['expire'])) {
       
   942             return $metadatas['mtime'];
       
   943         }
       
   944         return false;
       
   945     }
       
   946 
       
   947     /**
       
   948      * Return the file content of the given file
       
   949      *
       
   950      * @param  string $file File complete path
       
   951      * @return string File content (or false if problem)
       
   952      */
       
   953     protected function _fileGetContents($file)
       
   954     {
       
   955         $result = false;
       
   956         if (!is_file($file)) {
       
   957             return false;
       
   958         }
       
   959         $f = @fopen($file, 'rb');
       
   960         if ($f) {
       
   961             if ($this->_options['file_locking']) @flock($f, LOCK_SH);
       
   962             $result = stream_get_contents($f);
       
   963             if ($this->_options['file_locking']) @flock($f, LOCK_UN);
       
   964             @fclose($f);
       
   965         }
       
   966         return $result;
       
   967     }
       
   968 
       
   969     /**
       
   970      * Put the given string into the given file
       
   971      *
       
   972      * @param  string $file   File complete path
       
   973      * @param  string $string String to put in file
       
   974      * @return boolean true if no problem
       
   975      */
       
   976     protected function _filePutContents($file, $string)
       
   977     {
       
   978         $result = false;
       
   979         $f = @fopen($file, 'ab+');
       
   980         if ($f) {
       
   981             if ($this->_options['file_locking']) @flock($f, LOCK_EX);
       
   982             fseek($f, 0);
       
   983             ftruncate($f, 0);
       
   984             $tmp = @fwrite($f, $string);
       
   985             if (!($tmp === FALSE)) {
       
   986                 $result = true;
       
   987             }
       
   988             @fclose($f);
       
   989         }
       
   990         @chmod($file, $this->_options['cache_file_umask']);
       
   991         return $result;
       
   992     }
       
   993 
       
   994     /**
       
   995      * Transform a file name into cache id and return it
       
   996      *
       
   997      * @param  string $fileName File name
       
   998      * @return string Cache id
       
   999      */
       
  1000     protected function _fileNameToId($fileName)
       
  1001     {
       
  1002         $prefix = $this->_options['file_name_prefix'];
       
  1003         return preg_replace('~^' . $prefix . '---(.*)$~', '$1', $fileName);
       
  1004     }
       
  1005 
       
  1006 }