Inheritances
Files
Overview
FRAMES
NO FRAMES

File.php source code

Contents of file Cache/Backend/File.php
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-2009 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 17029 2009-07-24 11:57:49Z matthew $
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-2009 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_SEPARATORfalse);
124         }
125         if (isset(
$this->_options['file_name_prefix'])) { // particular case for this option
126             
if (!preg_match('~^[\w]+$~'$this->_options['file_name_prefix'])) {
127                 
Zend_Cache::throwException('Invalid file_name_prefix : must use only [a-zA-A0-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($idfalse);
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         return (
$this->_delMetadatas($id) && $this->_remove($file));
263     }
264
265     
/**
266      * Clean some cache records
267      *
268      * Available modes are :
269      * 'all' (default)  => remove all cache entries ($tags is not used)
270      * 'old'            => remove too old cache entries ($tags is not used)
271      * 'matchingTag'    => remove cache entries matching all given tags
272      *                     ($tags can be an array of strings or a single string)
273      * 'notMatchingTag' => remove cache entries not matching one of the given tags
274      *                     ($tags can be an array of strings or a single string)
275      * 'matchingAnyTag' => remove cache entries matching any given tags
276      *                     ($tags can be an array of strings or a single string)
277      *
278      * @param string $mode clean mode
279      * @param tags array $tags array of tags
280      * @return boolean true if no problem
281      */
282     
public function clean($mode Zend_Cache::CLEANING_MODE_ALL$tags = array())
283     {
284         
// We use this protected method to hide the recursive stuff
285         
clearstatcache();
286         return 
$this->_clean($this->_options['cache_dir'], $mode$tags);
287     }
288
289     
/**
290      * Return an array of stored cache ids
291      *
292      * @return array array of stored cache ids (string)
293      */
294     
public function getIds()
295     {
296         return 
$this->_get($this->_options['cache_dir'], 'ids', array());
297     }
298
299     
/**
300      * Return an array of stored tags
301      *
302      * @return array array of stored tags (string)
303      */
304     
public function getTags()
305     {
306         return 
$this->_get($this->_options['cache_dir'], 'tags', array());
307     }
308
309     
/**
310      * Return an array of stored cache ids which match given tags
311      *
312      * In case of multiple tags, a logical AND is made between tags
313      *
314      * @param array $tags array of tags
315      * @return array array of matching cache ids (string)
316      */
317     
public function getIdsMatchingTags($tags = array())
318     {
319         return 
$this->_get($this->_options['cache_dir'], 'matching'$tags);
320     }
321
322     
/**
323      * Return an array of stored cache ids which don't match given tags
324      *
325      * In case of multiple tags, a logical OR is made between tags
326      *
327      * @param array $tags array of tags
328      * @return array array of not matching cache ids (string)
329      */
330     
public function getIdsNotMatchingTags($tags = array())
331     {
332         return 
$this->_get($this->_options['cache_dir'], 'notMatching'$tags);
333     }
334
335     
/**
336      * Return an array of stored cache ids which match any given tags
337      *
338      * In case of multiple tags, a logical AND is made between tags
339      *
340      * @param array $tags array of tags
341      * @return array array of any matching cache ids (string)
342      */
343     
public function getIdsMatchingAnyTags($tags = array())
344     {
345         return 
$this->_get($this->_options['cache_dir'], 'matchingAny'$tags);
346     }
347
348     
/**
349      * Return the filling percentage of the backend storage
350      *
351      * @throws Zend_Cache_Exception
352      * @return int integer between 0 and 100
353      */
354     
public function getFillingPercentage()
355     {
356         
$free disk_free_space($this->_options['cache_dir']);
357         
$total disk_total_space($this->_options['cache_dir']);
358         if (
$total == 0) {
359             
Zend_Cache::throwException('can\'t get disk_total_space');
360         } else {
361             if (
$free >= $total) {
362                 return 
100;
363             }
364             return ((int) (
100. * ($total $free) / $total));
365         }
366     }
367
368     
/**
369      * Return an array of metadatas for the given cache id
370      *
371      * The array must include these keys :
372      * - expire : the expire timestamp
373      * - tags : a string array of tags
374      * - mtime : timestamp of last modification time
375      *
376      * @param string $id cache id
377      * @return array array of metadatas (false if the cache id is not found)
378      */
379     
public function getMetadatas($id)
380     {
381         
$metadatas $this->_getMetadatas($id);
382         if (!
$metadatas) {
383             return 
false;
384         }
385         if (
time() > $metadatas['expire']) {
386             return 
false;
387         }
388         return array(
389             
'expire' => $metadatas['expire'],
390             
'tags' => $metadatas['tags'],
391             
'mtime' => $metadatas['mtime']
392         );
393     }
394
395     
/**
396      * Give (if possible) an extra lifetime to the given cache id
397      *
398      * @param string $id cache id
399      * @param int $extraLifetime
400      * @return boolean true if ok
401      */
402     
public function touch($id$extraLifetime)
403     {
404         
$metadatas $this->_getMetadatas($id);
405         if (!
$metadatas) {
406             return 
false;
407         }
408         if (
time() > $metadatas['expire']) {
409             return 
false;
410         }
411         
$newMetadatas = array(
412             
'hash' => $metadatas['hash'],
413             
'mtime' => time(),
414             
'expire' => $metadatas['expire'] + $extraLifetime,
415             
'tags' => $metadatas['tags']
416         );
417         
$res $this->_setMetadatas($id$newMetadatas);
418         if (!
$res) {
419             return 
false;
420         }
421         return 
true;
422     }
423
424     
/**
425      * Return an associative array of capabilities (booleans) of the backend
426      *
427      * The array must include these keys :
428      * - automatic_cleaning (is automating cleaning necessary)
429      * - tags (are tags supported)
430      * - expired_read (is it possible to read expired cache records
431      *                 (for doNotTestCacheValidity option for example))
432      * - priority does the backend deal with priority when saving
433      * - infinite_lifetime (is infinite lifetime can work with this backend)
434      * - get_list (is it possible to get the list of cache ids and the complete list of tags)
435      *
436      * @return array associative of with capabilities
437      */
438     
public function getCapabilities()
439     {
440         return array(
441             
'automatic_cleaning' => true,
442             
'tags' => true,
443             
'expired_read' => true,
444             
'priority' => false,
445             
'infinite_lifetime' => true,
446             
'get_list' => true
447         
);
448     }
449
450     
/**
451      * PUBLIC METHOD FOR UNIT TESTING ONLY !
452      *
453      * Force a cache record to expire
454      *
455      * @param string $id cache id
456      */
457     
public function ___expire($id)
458     {
459         
$metadatas $this->_getMetadatas($id);
460         if (
$metadatas) {
461             
$metadatas['expire'] = 1;
462             
$this->_setMetadatas($id$metadatas);
463         }
464     }
465
466     
/**
467      * Get a metadatas record
468      *
469      * @param  string $id  Cache id
470      * @return array|false Associative array of metadatas
471      */
472     
protected function _getMetadatas($id)
473     {
474         if (isset(
$this->_metadatasArray[$id])) {
475             return 
$this->_metadatasArray[$id];
476         } else {
477             
$metadatas $this->_loadMetadatas($id);
478             if (!
$metadatas) {
479                 return 
false;
480             }
481             
$this->_setMetadatas($id$metadatasfalse);
482             return 
$metadatas;
483         }
484     }
485
486     
/**
487      * Set a metadatas record
488      *
489      * @param  string $id        Cache id
490      * @param  array  $metadatas Associative array of metadatas
491      * @param  boolean $save     optional pass false to disable saving to file
492      * @return boolean True if no problem
493      */
494     
protected function _setMetadatas($id$metadatas$save true)
495     {
496         if (
count($this->_metadatasArray) >= $this->_options['metadatas_array_max_size']) {
497             
$n = (int) ($this->_options['metadatas_array_max_size'] / 10);
498             
$this->_metadatasArray array_slice($this->_metadatasArray$n);
499         }
500         if (
$save) {
501             
$result $this->_saveMetadatas($id$metadatas);
502             if (!
$result) {
503                 return 
false;
504             }
505         }
506         
$this->_metadatasArray[$id] = $metadatas;
507         return 
true;
508     }
509
510     
/**
511      * Drop a metadata record
512      *
513      * @param  string $id Cache id
514      * @return boolean True if no problem
515      */
516     
protected function _delMetadatas($id)
517     {
518         if (isset(
$this->_metadatasArray[$id])) {
519             unset(
$this->_metadatasArray[$id]);
520         }
521         
$file $this->_metadatasFile($id);
522         return 
$this->_remove($file);
523     }
524
525     
/**
526      * Clear the metadatas array
527      *
528      * @return void
529      */
530     
protected function _cleanMetadatas()
531     {
532         
$this->_metadatasArray = array();
533     }
534
535     
/**
536      * Load metadatas from disk
537      *
538      * @param  string $id Cache id
539      * @return array|false Metadatas associative array
540      */
541     
protected function _loadMetadatas($id)
542     {
543         
$file $this->_metadatasFile($id);
544         
$result $this->_fileGetContents($file);
545         if (!
$result) {
546             return 
false;
547         }
548         
$tmp = @unserialize($result);
549         return 
$tmp;
550     }
551
552     
/**
553      * Save metadatas to disk
554      *
555      * @param  string $id        Cache id
556      * @param  array  $metadatas Associative array
557      * @return boolean True if no problem
558      */
559     
protected function _saveMetadatas($id$metadatas)
560     {
561         
$file $this->_metadatasFile($id);
562         
$result $this->_filePutContents($fileserialize($metadatas));
563         if (!
$result) {
564             return 
false;
565         }
566         return 
true;
567     }
568
569     
/**
570      * Make and return a file name (with path) for metadatas
571      *
572      * @param  string $id Cache id
573      * @return string Metadatas file name (with path)
574      */
575     
protected function _metadatasFile($id)
576     {
577         
$path $this->_path($id);
578         
$fileName $this->_idToFileName('internal-metadatas---' $id);
579         return 
$path $fileName;
580     }
581
582     
/**
583      * Check if the given filename is a metadatas one
584      *
585      * @param  string $fileName File name
586      * @return boolean True if it's a metadatas one
587      */
588     
protected function _isMetadatasFile($fileName)
589     {
590         
$id $this->_fileNameToId($fileName);
591         if (
substr($id021) == 'internal-metadatas---') {
592             return 
true;
593         } else {
594             return 
false;
595         }
596     }
597
598     
/**
599      * Remove a file
600      *
601      * If we can't remove the file (because of locks or any problem), we will touch
602      * the file to invalidate it
603      *
604      * @param  string $file Complete file path
605      * @return boolean True if ok
606      */
607     
protected function _remove($file)
608     {
609         if (!
is_file($file)) {
610             return 
false;
611         }
612         if (!@
unlink($file)) {
613             
# we can't remove the file (because of locks or any problem)
614             
$this->_log("Zend_Cache_Backend_File::_remove() : we can't remove $file");
615             return 
false;
616         }
617         return 
true;
618     }
619
620     
/**
621      * Clean some cache records (protected method used for recursive stuff)
622      *
623      * Available modes are :
624      * Zend_Cache::CLEANING_MODE_ALL (default)    => remove all cache entries ($tags is not used)
625      * Zend_Cache::CLEANING_MODE_OLD              => remove too old cache entries ($tags is not used)
626      * Zend_Cache::CLEANING_MODE_MATCHING_TAG     => remove cache entries matching all given tags
627      *                                               ($tags can be an array of strings or a single string)
628      * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags}
629      *                                               ($tags can be an array of strings or a single string)
630      * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags
631      *                                               ($tags can be an array of strings or a single string)
632      *
633      * @param  string $dir  Directory to clean
634      * @param  string $mode Clean mode
635      * @param  array  $tags Array of tags
636      * @throws Zend_Cache_Exception
637      * @return boolean True if no problem
638      */
639     
protected function _clean($dir$mode Zend_Cache::CLEANING_MODE_ALL$tags = array())
640     {
641         if (!
is_dir($dir)) {
642             return 
false;
643         }
644         
$result true;
645         
$prefix $this->_options['file_name_prefix'];
646         
$glob = @glob($dir $prefix '--*');
647         if (
$glob === false) {
648             return 
true;
649         }
650         foreach (
$glob as $file)  {
651             if (
is_file($file)) {
652                 
$fileName basename($file);
653                 if (
$this->_isMetadatasFile($fileName)) {
654                     
// in CLEANING_MODE_ALL, we drop anything, even remainings old metadatas files
655                     
if ($mode != Zend_Cache::CLEANING_MODE_ALL) {
656                         continue;
657                     }
658                 }
659                 
$id $this->_fileNameToId($fileName);
660                 
$metadatas $this->_getMetadatas($id);
661                 if (
$metadatas === FALSE) {
662                     
$metadatas = array('expire' => 1'tags' => array());
663                 }
664                 switch (
$mode) {
665                     case 
Zend_Cache::CLEANING_MODE_ALL:
666                         
$res $this->remove($id);
667                         if (!
$res) {
668                             
// in this case only, we accept a problem with the metadatas file drop
669                             
$res $this->_remove($file);
670                         }
671                         
$result $result && $res;
672                         break;
673                     case 
Zend_Cache::CLEANING_MODE_OLD:
674                         if (
time() > $metadatas['expire']) {
675                             
$result = ($result) && ($this->remove($id));
676                         }
677                         break;
678                     case 
Zend_Cache::CLEANING_MODE_MATCHING_TAG:
679                         
$matching true;
680                         foreach (
$tags as $tag) {
681                             if (!
in_array($tag$metadatas['tags'])) {
682                                 
$matching false;
683                                 break;
684                             }
685                         }
686                         if (
$matching) {
687                             
$result = ($result) && ($this->remove($id));
688                         }
689                         break;
690                     case 
Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG:
691                         
$matching false;
692                         foreach (
$tags as $tag) {
693                             if (
in_array($tag$metadatas['tags'])) {
694                                 
$matching true;
695                                 break;
696                             }
697                         }
698                         if (!
$matching) {
699                             
$result = ($result) && $this->remove($id);
700                         }
701                         break;
702                     case 
Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG:
703                         
$matching false;
704                         foreach (
$tags as $tag) {
705                             if (
in_array($tag$metadatas['tags'])) {
706                                 
$matching true;
707                                 break;
708                             }
709                         }
710                         if (
$matching) {
711                             
$result = ($result) && ($this->remove($id));
712                         }
713                         break;
714                     default:
715                         
Zend_Cache::throwException('Invalid mode for clean() method');
716                         break;
717                 }
718             }
719             if ((
is_dir($file)) and ($this->_options['hashed_directory_level']>0)) {
720                 
// Recursive call
721                 
$result = ($result) && ($this->_clean($file DIRECTORY_SEPARATOR$mode$tags));
722                 if (
$mode=='all') {
723                     
// if mode=='all', we try to drop the structure too
724                     
@rmdir($file);
725                 }
726             }
727         }
728         return 
$result;
729     }
730
731     protected function 
_get($dir$mode$tags = array())
732     {
733         if (!
is_dir($dir)) {
734             return 
false;
735         }
736         
$result = array();
737         
$prefix $this->_options['file_name_prefix'];
738         
$glob = @glob($dir $prefix '--*');
739         if (
$glob === false) {
740             return 
true;
741         }
742         foreach (
$glob as $file)  {
743             if (
is_file($file)) {
744                 
$fileName basename($file);
745                 
$id $this->_fileNameToId($fileName);
746                 
$metadatas $this->_getMetadatas($id);
747                 if (
$metadatas === FALSE) {
748                     continue;
749                 }
750                 if (
time() > $metadatas['expire']) {
751                     continue;
752                 }
753                 switch (
$mode) {
754                     case 
'ids':
755                         
$result[] = $id;
756                         break;
757                     case 
'tags':
758                         
$result array_unique(array_merge($result$metadatas['tags']));
759                         break;
760                     case 
'matching':
761                         
$matching true;
762                         foreach (
$tags as $tag) {
763                             if (!
in_array($tag$metadatas['tags'])) {
764                                 
$matching false;
765                                 break;
766                             }
767                         }
768                         if (
$matching) {
769                             
$result[] = $id;
770                         }
771                         break;
772                     case 
'notMatching':
773                         
$matching false;
774                         foreach (
$tags as $tag) {
775                             if (
in_array($tag$metadatas['tags'])) {
776                                 
$matching true;
777                                 break;
778                             }
779                         }
780                         if (!
$matching) {
781                             
$result[] = $id;
782                         }
783                         break;
784                     case 
'matchingAny':
785                         
$matching false;
786                         foreach (
$tags as $tag) {
787                             if (
in_array($tag$metadatas['tags'])) {
788                                 
$matching true;
789                                 break;
790                             }
791                         }
792                         if (
$matching) {
793                             
$result[] = $id;
794                         }
795                         break;
796                     default:
797                         
Zend_Cache::throwException('Invalid mode for _get() method');
798                         break;
799                 }
800             }
801             if ((
is_dir($file)) and ($this->_options['hashed_directory_level']>0)) {
802                 
// Recursive call
803                 
$result array_unique(array_merge($result$this->_get($file DIRECTORY_SEPARATOR$mode$tags)));
804             }
805         }
806         return 
array_unique($result);
807     }
808
809     
/**
810      * Compute & return the expire time
811      *
812      * @return int expire time (unix timestamp)
813      */
814     
protected function _expireTime($lifetime)
815     {
816         if (
$lifetime === null) {
817             return 
9999999999;
818         }
819         return 
time() + $lifetime;
820     }
821
822     
/**
823      * Make a control key with the string containing datas
824      *
825      * @param  string $data        Data
826      * @param  string $controlType Type of control 'md5', 'crc32' or 'strlen'
827      * @throws Zend_Cache_Exception
828      * @return string Control key
829      */
830     
protected function _hash($data$controlType)
831     {
832         switch (
$controlType) {
833         case 
'md5':
834             return 
md5($data);
835         case 
'crc32':
836             return 
crc32($data);
837         case 
'strlen':
838             return 
strlen($data);
839         case 
'adler32':
840             return 
hash('adler32'$data);
841         default:
842             
Zend_Cache::throwException("Incorrect hash function : $controlType");
843         }
844     }
845
846     
/**
847      * Transform a cache id into a file name and return it
848      *
849      * @param  string $id Cache id
850      * @return string File name
851      */
852     
protected function _idToFileName($id)
853     {
854         
$prefix $this->_options['file_name_prefix'];
855         
$result $prefix '---' $id;
856         return 
$result;
857     }
858
859     
/**
860      * Make and return a file name (with path)
861      *
862      * @param  string $id Cache id
863      * @return string File name (with path)
864      */
865     
protected function _file($id)
866     {
867         
$path $this->_path($id);
868         
$fileName $this->_idToFileName($id);
869         return 
$path $fileName;
870     }
871
872     
/**
873      * Return the complete directory path of a filename (including hashedDirectoryStructure)
874      *
875      * @param  string $id Cache id
876      * @param  boolean $parts if true, returns array of directory parts instead of single string
877      * @return string Complete directory path
878      */
879     
protected function _path($id$parts false)
880     {
881         
$partsArray = array();
882         
$root $this->_options['cache_dir'];
883         
$prefix $this->_options['file_name_prefix'];
884         if (
$this->_options['hashed_directory_level']>0) {
885             
$hash hash('adler32'$id);
886             for (
$i=$i $this->_options['hashed_directory_level'] ; $i++) {
887                 
$root $root $prefix '--' substr($hash0$i 1) . DIRECTORY_SEPARATOR;
888                 
$partsArray[] = $root;
889             }
890         }
891         if (
$parts) {
892             return 
$partsArray;
893         } else {
894             return 
$root;
895         }
896     }
897
898     
/**
899      * Make the directory strucuture for the given id
900      *
901      * @param string $id cache id
902      * @return boolean true
903      */
904     
protected function _recursiveMkdirAndChmod($id)
905     {
906         if (
$this->_options['hashed_directory_level'] <=0) {
907             return 
true;
908         }
909         
$partsArray $this->_path($idtrue);
910         foreach (
$partsArray as $part) {
911             if (!
is_dir($part)) {
912                 @
mkdir($part$this->_options['hashed_directory_umask']);
913                 @
chmod($part$this->_options['hashed_directory_umask']); // see #ZF-320 (this line is required in some configurations)
914             
}
915         }
916         return 
true;
917     }
918
919     
/**
920      * Test if the given cache id is available (and still valid as a cache record)
921      *
922      * @param  string  $id                     Cache id
923      * @param  boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested
924      * @return boolean|mixed false (a cache is not available) or "last modified" timestamp (int) of the available cache record
925      */
926     
protected function _test($id$doNotTestCacheValidity)
927     {
928         
$metadatas $this->_getMetadatas($id);
929         if (!
$metadatas) {
930             return 
false;
931         }
932         if (
$doNotTestCacheValidity || (time() <= $metadatas['expire'])) {
933             return 
$metadatas['mtime'];
934         }
935         return 
false;
936     }
937
938     
/**
939      * Return the file content of the given file
940      *
941      * @param  string $file File complete path
942      * @return string File content (or false if problem)
943      */
944     
protected function _fileGetContents($file)
945     {
946         
$result false;
947         if (!
is_file($file)) {
948             return 
false;
949         }
950         
$f = @fopen($file'rb');
951         if (
$f) {
952             if (
$this->_options['file_locking']) @flock($fLOCK_SH);
953             
$result stream_get_contents($f);
954             if (
$this->_options['file_locking']) @flock($fLOCK_UN);
955             @
fclose($f);
956         }
957         return 
$result;
958     }
959
960     
/**
961      * Put the given string into the given file
962      *
963      * @param  string $file   File complete path
964      * @param  string $string String to put in file
965      * @return boolean true if no problem
966      */
967     
protected function _filePutContents($file$string)
968     {
969         
$result false;
970         
$f = @fopen($file'ab+');
971         if (
$f) {
972             if (
$this->_options['file_locking']) @flock($fLOCK_EX);
973             
fseek($f0);
974             
ftruncate($f0);
975             
$tmp = @fwrite($f$string);
976             if (!(
$tmp === FALSE)) {
977                 
$result true;
978             }
979             @
fclose($f);
980         }
981         @
chmod($file$this->_options['cache_file_umask']);
982         return 
$result;
983     }
984
985     
/**
986      * Transform a file name into cache id and return it
987      *
988      * @param  string $fileName File name
989      * @return string Cache id
990      */
991     
protected function _fileNameToId($fileName)
992     {
993         
$prefix $this->_options['file_name_prefix'];
994         return 
preg_replace('~^' $prefix '---(.*)$~''$1'$fileName);
995     }
996
997 }
998