3 * TCache and cache dependency classes.
5 * @author Qiang Xue <qiang.xue@gmail.com>
6 * @link http://www.pradosoft.com/
7 * @copyright Copyright © 2005-2014 PradoSoft
8 * @license http://www.pradosoft.com/license/
9 * @package System.Caching
12 Prado::using('System.Collections.TList');
17 * TCache is the base class for cache classes with different cache storage implementation.
19 * TCache implements the interface {@link ICache} with the following methods,
20 * - {@link get} : retrieve the value with a key (if any) from cache
21 * - {@link set} : store the value with a key into cache
22 * - {@link add} : store the value only if cache does not have this key
23 * - {@link delete} : delete the value with the specified key from cache
24 * - {@link flush} : delete all values from cache
26 * Each value is associated with an expiration time. The {@link get} operation
27 * ensures that any expired value will not be returned. The expiration time by
28 * the number of seconds. A expiration time 0 represents never expire.
30 * By definition, cache does not ensure the existence of a value
31 * even if it never expires. Cache is not meant to be an persistent storage.
33 * Child classes must implement the following methods:
37 * - {@link deleteValue}
38 * and optionally {@link flush}
40 * Since version 3.1.2, TCache implements the ArrayAccess interface such that
41 * the cache acts as an array.
43 * @author Qiang Xue <qiang.xue@gmail.com>
44 * @package System.Caching
47 abstract class TCache extends TModule implements ICache, ArrayAccess
49 private $_prefix=null;
50 private $_primary=true;
53 * Initializes the cache module.
54 * This method initializes the cache key prefix and registers the cache module
55 * with the application if the cache is primary.
56 * @param TXmlElement the module configuration
58 public function init($config)
60 if($this->_prefix===null)
61 $this->_prefix=$this->getApplication()->getUniqueID();
64 if($this->getApplication()->getCache()===null)
65 $this->getApplication()->setCache($this);
67 throw new TConfigurationException('cache_primary_duplicated',get_class($this));
72 * @return boolean whether this cache module is used as primary/system cache.
73 * A primary cache is used by PRADO core framework to cache data such as
74 * parsed templates, themes, etc.
76 public function getPrimaryCache()
78 return $this->_primary;
82 * @param boolean whether this cache module is used as primary/system cache. Defaults to false.
83 * @see getPrimaryCache
85 public function setPrimaryCache($value)
87 $this->_primary=TPropertyValue::ensureBoolean($value);
91 * @return string a unique prefix for the keys of cached values.
92 * If it is not explicitly set, it will take the value of {@link TApplication::getUniqueID}.
94 public function getKeyPrefix()
96 return $this->_prefix;
100 * @param string a unique prefix for the keys of cached values
102 public function setKeyPrefix($value)
104 $this->_prefix=$value;
108 * @param string a key identifying a value to be cached
109 * @return sring a key generated from the provided key which ensures the uniqueness across applications
111 protected function generateUniqueKey($key)
113 return md5($this->_prefix.$key);
117 * Retrieves a value from cache with a specified key.
118 * @param string a key identifying the cached value
119 * @return mixed the value stored in cache, false if the value is not in the cache or expired.
121 public function get($id)
123 if(($data=$this->getValue($this->generateUniqueKey($id)))!==false)
127 if(!($data[1] instanceof ICacheDependency) || !$data[1]->getHasChanged())
134 * Stores a value identified by a key into cache.
135 * If the cache already contains such a key, the existing value and
136 * expiration time will be replaced with the new ones. If the value is
137 * empty, the cache key will be deleted.
139 * @param string the key identifying the value to be cached
140 * @param mixed the value to be cached
141 * @param integer the number of seconds in which the cached value will expire. 0 means never expire.
142 * @param ICacheDependency dependency of the cached item. If the dependency changes, the item is labeled invalid.
143 * @return boolean true if the value is successfully stored into cache, false otherwise
145 public function set($id,$value,$expire=0,$dependency=null)
147 if(empty($value) && $expire === 0)
151 $data=array($value,$dependency);
152 return $this->setValue($this->generateUniqueKey($id),$data,$expire);
157 * Stores a value identified by a key into cache if the cache does not contain this key.
158 * Nothing will be done if the cache already contains the key or if value is empty.
159 * @param string the key identifying the value to be cached
160 * @param mixed the value to be cached
161 * @param integer the number of seconds in which the cached value will expire. 0 means never expire.
162 * @param ICacheDependency dependency of the cached item. If the dependency changes, the item is labeled invalid.
163 * @return boolean true if the value is successfully stored into cache, false otherwise
165 public function add($id,$value,$expire=0,$dependency=null)
167 if(empty($value) && $expire === 0)
169 $data=array($value,$dependency);
170 return $this->addValue($this->generateUniqueKey($id),$data,$expire);
174 * Deletes a value with the specified key from cache
175 * @param string the key of the value to be deleted
176 * @return boolean if no error happens during deletion
178 public function delete($id)
180 return $this->deleteValue($this->generateUniqueKey($id));
184 * Deletes all values from cache.
185 * Be careful of performing this operation if the cache is shared by multiple applications.
186 * Child classes may implement this method to realize the flush operation.
187 * @throws TNotSupportedException if this method is not overridden by child classes
189 public function flush()
191 throw new TNotSupportedException('cache_flush_unsupported');
195 * Retrieves a value from cache with a specified key.
196 * This method should be implemented by child classes to store the data
197 * in specific cache storage. The uniqueness and dependency are handled
198 * in {@link get()} already. So only the implementation of data retrieval
200 * @param string a unique key identifying the cached value
201 * @return string the value stored in cache, false if the value is not in the cache or expired.
203 abstract protected function getValue($key);
206 * Stores a value identified by a key in cache.
207 * This method should be implemented by child classes to store the data
208 * in specific cache storage. The uniqueness and dependency are handled
209 * in {@link set()} already. So only the implementation of data storage
212 * @param string the key identifying the value to be cached
213 * @param string the value to be cached
214 * @param integer the number of seconds in which the cached value will expire. 0 means never expire.
215 * @return boolean true if the value is successfully stored into cache, false otherwise
217 abstract protected function setValue($key,$value,$expire);
220 * Stores a value identified by a key into cache if the cache does not contain this key.
221 * This method should be implemented by child classes to store the data
222 * in specific cache storage. The uniqueness and dependency are handled
223 * in {@link add()} already. So only the implementation of data storage
226 * @param string the key identifying the value to be cached
227 * @param string the value to be cached
228 * @param integer the number of seconds in which the cached value will expire. 0 means never expire.
229 * @return boolean true if the value is successfully stored into cache, false otherwise
231 abstract protected function addValue($key,$value,$expire);
234 * Deletes a value with the specified key from cache
235 * This method should be implemented by child classes to delete the data from actual cache storage.
236 * @param string the key of the value to be deleted
237 * @return boolean if no error happens during deletion
239 abstract protected function deleteValue($key);
242 * Returns whether there is a cache entry with a specified key.
243 * This method is required by the interface ArrayAccess.
244 * @param string a key identifying the cached value
247 public function offsetExists($id)
249 return $this->get($id) !== false;
253 * Retrieves the value from cache with a specified key.
254 * This method is required by the interface ArrayAccess.
255 * @param string a key identifying the cached value
256 * @return mixed the value stored in cache, false if the value is not in the cache or expired.
258 public function offsetGet($id)
260 return $this->get($id);
264 * Stores the value identified by a key into cache.
265 * If the cache already contains such a key, the existing value will be
266 * replaced with the new ones. To add expiration and dependencies, use the set() method.
267 * This method is required by the interface ArrayAccess.
268 * @param string the key identifying the value to be cached
269 * @param mixed the value to be cached
271 public function offsetSet($id, $value)
273 $this->set($id, $value);
277 * Deletes the value with the specified key from cache
278 * This method is required by the interface ArrayAccess.
279 * @param string the key of the value to be deleted
280 * @return boolean if no error happens during deletion
282 public function offsetUnset($id)
290 * TCacheDependency class.
292 * TCacheDependency is the base class implementing {@link ICacheDependency} interface.
293 * Descendant classes must implement {@link getHasChanged()} to provide
294 * actual dependency checking logic.
296 * The property value of {@link getHasChanged HasChanged} tells whether
297 * the dependency is changed or not.
299 * You may disable the dependency checking by setting {@link setEnabled Enabled}
302 * Note, since the dependency objects often need to be serialized so that
303 * they can persist across requests, you may need to implement __sleep() and
304 * __wakeup() if the dependency objects contain resource handles which are
307 * Currently, the following dependency classes are provided in the PRADO release:
308 * - {@link TFileCacheDependency}: checks whether a file is changed or not
309 * - {@link TDirectoryCacheDependency}: checks whether a directory is changed or not
310 * - {@link TGlobalStateCacheDependency}: checks whether a global state is changed or not
311 * - {@link TChainedCacheDependency}: checks whether any of a list of dependencies is changed or not
313 * @author Qiang Xue <qiang.xue@gmail.com>
314 * @package System.Caching
317 abstract class TCacheDependency extends TComponent implements ICacheDependency
323 * TFileCacheDependency class.
325 * TFileCacheDependency performs dependency checking based on the
326 * last modification time of the file specified via {@link setFileName FileName}.
327 * The dependency is reported as unchanged if and only if the file's
328 * last modification time remains unchanged.
330 * @author Qiang Xue <qiang.xue@gmail.com>
331 * @package System.Caching
334 class TFileCacheDependency extends TCacheDependency
341 * @param string name of the file whose change is to be checked.
343 public function __construct($fileName)
345 $this->setFileName($fileName);
349 * @return string the name of the file whose change is to be checked
351 public function getFileName()
353 return $this->_fileName;
357 * @param string the name of the file whose change is to be checked
359 public function setFileName($value)
361 $this->_fileName=$value;
362 $this->_timestamp=@filemtime($value);
366 * @return int the last modification time of the file
368 public function getTimestamp()
370 return $this->_timestamp;
374 * Performs the actual dependency checking.
375 * This method returns true if the last modification time of the file is changed.
376 * @return boolean whether the dependency is changed or not.
378 public function getHasChanged()
380 return @filemtime($this->_fileName)!==$this->_timestamp;
385 * TDirectoryCacheDependency class.
387 * TDirectoryCacheDependency performs dependency checking based on the
388 * modification time of the files contained in the specified directory.
389 * The directory being checked is specified via {@link setDirectory Directory}.
391 * By default, all files under the specified directory and subdirectories
392 * will be checked. If the last modification time of any of them is changed
393 * or if different number of files are contained in a directory, the dependency
394 * is reported as changed. By specifying {@link setRecursiveCheck RecursiveCheck}
395 * and {@link setRecursiveLevel RecursiveLevel}, one can limit the checking
396 * to a certain depth of the subdirectories.
398 * @author Qiang Xue <qiang.xue@gmail.com>
399 * @package System.Caching
402 class TDirectoryCacheDependency extends TCacheDependency
404 private $_recursiveCheck=true;
405 private $_recursiveLevel=-1;
406 private $_timestamps;
411 * @param string the directory to be checked
413 public function __construct($directory)
415 $this->setDirectory($directory);
419 * @return string the directory to be checked
421 public function getDirectory()
423 return $this->_directory;
427 * @param string the directory to be checked
428 * @throws TInvalidDataValueException if the directory does not exist
430 public function setDirectory($directory)
432 if(($path=realpath($directory))===false || !is_dir($path))
433 throw new TInvalidDataValueException('directorycachedependency_directory_invalid',$directory);
434 $this->_directory=$path;
435 $this->_timestamps=$this->generateTimestamps($path);
439 * @return boolean whether the subdirectories of the directory will also be checked.
440 * It defaults to true.
442 public function getRecursiveCheck()
444 return $this->_recursiveCheck;
448 * @param boolean whether the subdirectories of the directory will also be checked.
450 public function setRecursiveCheck($value)
452 $this->_recursiveCheck=TPropertyValue::ensureBoolean($value);
456 * @return int the depth of the subdirectories to be checked.
457 * It defaults to -1, meaning unlimited depth.
459 public function getRecursiveLevel()
461 return $this->_recursiveLevel;
465 * Sets a value indicating the depth of the subdirectories to be checked.
466 * This is meaningful only when {@link getRecursiveCheck RecursiveCheck}
468 * @param int the depth of the subdirectories to be checked.
469 * If the value is less than 0, it means unlimited depth.
470 * If the value is 0, it means checking the files directly under the specified directory.
472 public function setRecursiveLevel($value)
474 $this->_recursiveLevel=TPropertyValue::ensureInteger($value);
478 * Performs the actual dependency checking.
479 * This method returns true if the directory is changed.
480 * @return boolean whether the dependency is changed or not.
482 public function getHasChanged()
484 return $this->generateTimestamps($this->_directory)!=$this->_timestamps;
488 * Checks to see if the file should be checked for dependency.
489 * This method is invoked when dependency of the whole directory is being checked.
490 * By default, it always returns true, meaning the file should be checked.
491 * You may override this method to check only certain files.
492 * @param string the name of the file that may be checked for dependency.
493 * @return boolean whether this file should be checked.
495 protected function validateFile($fileName)
501 * Checks to see if the specified subdirectory should be checked for dependency.
502 * This method is invoked when dependency of the whole directory is being checked.
503 * By default, it always returns true, meaning the subdirectory should be checked.
504 * You may override this method to check only certain subdirectories.
505 * @param string the name of the subdirectory that may be checked for dependency.
506 * @return boolean whether this subdirectory should be checked.
508 protected function validateDirectory($directory)
514 * Determines the last modification time for files under the directory.
515 * This method may go recursively into subdirectories if
516 * {@link setRecursiveCheck RecursiveCheck} is set true.
517 * @param string the directory name
518 * @param int level of the recursion
519 * @return array list of file modification time indexed by the file path
521 protected function generateTimestamps($directory,$level=0)
523 if(($dir=opendir($directory))===false)
524 throw new TIOException('directorycachedependency_directory_invalid',$directory);
526 while(($file=readdir($dir))!==false)
528 $path=$directory.DIRECTORY_SEPARATOR.$file;
529 if($file==='.' || $file==='..')
531 else if(is_dir($path))
533 if(($this->_recursiveLevel<0 || $level<$this->_recursiveLevel) && $this->validateDirectory($path))
534 $timestamps=array_merge($this->generateTimestamps($path,$level+1));
536 else if($this->validateFile($path))
537 $timestamps[$path]=filemtime($path);
546 * TGlobalStateCacheDependency class.
548 * TGlobalStateCacheDependency checks if a global state is changed or not.
549 * If the global state is changed, the dependency is reported as changed.
550 * To specify which global state this dependency should check with,
551 * set {@link setStateName StateName} to the name of the global state.
553 * @author Qiang Xue <qiang.xue@gmail.com>
554 * @package System.Caching
557 class TGlobalStateCacheDependency extends TCacheDependency
560 private $_stateValue;
564 * @param string the name of the global state
566 public function __construct($name)
568 $this->setStateName($name);
572 * @return string the name of the global state
574 public function getStateName()
576 return $this->_stateName;
580 * @param string the name of the global state
581 * @see TApplication::setGlobalState
583 public function setStateName($value)
585 $this->_stateName=$value;
586 $this->_stateValue=Prado::getApplication()->getGlobalState($value);
590 * Performs the actual dependency checking.
591 * This method returns true if the specified global state is changed.
592 * @return boolean whether the dependency is changed or not.
594 public function getHasChanged()
596 return $this->_stateValue!==Prado::getApplication()->getGlobalState($this->_stateName);
602 * TChainedCacheDependency class.
604 * TChainedCacheDependency represents a list of cache dependency objects
605 * and performs the dependency checking based on the checking results of
606 * these objects. If any of them reports a dependency change, TChainedCacheDependency
607 * will return true for the checking.
609 * To add dependencies to TChainedCacheDependency, use {@link getDependencies Dependencies}
610 * which gives a {@link TCacheDependencyList} instance and can be used like an array
611 * (see {@link TList} for more details}).
613 * @author Qiang Xue <qiang.xue@gmail.com>
614 * @package System.Caching
617 class TChainedCacheDependency extends TCacheDependency
619 private $_dependencies=null;
622 * @return TCacheDependencyList list of dependency objects
624 public function getDependencies()
626 if($this->_dependencies===null)
627 $this->_dependencies=new TCacheDependencyList;
628 return $this->_dependencies;
632 * Performs the actual dependency checking.
633 * This method returns true if any of the dependency objects
634 * reports a dependency change.
635 * @return boolean whether the dependency is changed or not.
637 public function getHasChanged()
639 if($this->_dependencies!==null)
641 foreach($this->_dependencies as $dependency)
642 if($dependency->getHasChanged())
651 * TApplicationStateCacheDependency class.
653 * TApplicationStateCacheDependency performs dependency checking based on
654 * the mode of the currently running PRADO application.
655 * The dependency is reportedly as unchanged if and only if the application
656 * is running in performance mode.
658 * You may chain this dependency together with other dependencies
659 * so that only when the application is not in performance mode the other dependencies
662 * @author Qiang Xue <qiang.xue@gmail.com>
663 * @package System.Caching
666 class TApplicationStateCacheDependency extends TCacheDependency
669 * Performs the actual dependency checking.
670 * This method returns true if the currently running application is not in performance mode.
671 * @return boolean whether the dependency is changed or not.
673 public function getHasChanged()
675 return Prado::getApplication()->getMode()!==TApplicationMode::Performance;
680 * TCacheDependencyList class.
682 * TCacheDependencyList represents a list of cache dependency objects.
683 * Only objects implementing {@link ICacheDependency} can be added into this list.
685 * TCacheDependencyList can be used like an array. See {@link TList}
688 * @author Qiang Xue <qiang.xue@gmail.com>
689 * @package System.Caching
692 class TCacheDependencyList extends TList
695 * Inserts an item at the specified position.
696 * This overrides the parent implementation by performing additional type checking
697 * for each newly added item.
698 * @param integer the specified position.
699 * @param mixed new item
700 * @throws TInvalidDataTypeException if the item to be inserted is not a dependency instance
702 public function insertAt($index,$item)
704 if($item instanceof ICacheDependency)
705 parent::insertAt($index,$item);
707 throw new TInvalidDataTypeException('cachedependencylist_cachedependency_required');