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_SEPARATOR, false);
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($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 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, $metadatas, false);
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($file, serialize($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($id, 0, 21) == '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=0 ; $i < $this->_options['hashed_directory_level'] ; $i++) {
887 $root = $root . $prefix . '--' . substr($hash, 0, $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($id, true);
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($f, LOCK_SH);
953 $result = stream_get_contents($f);
954 if ($this->_options['file_locking']) @flock($f, LOCK_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($f, LOCK_EX);
973 fseek($f, 0);
974 ftruncate($f, 0);
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