PageRenderTime 170ms CodeModel.GetById 80ms app.highlight 54ms RepoModel.GetById 27ms app.codeStats 0ms

/Zend/Cache/Backend/File.php

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