summaryrefslogtreecommitdiff
path: root/lib/prado/framework/Caching/TCache.php
diff options
context:
space:
mode:
Diffstat (limited to 'lib/prado/framework/Caching/TCache.php')
-rw-r--r--lib/prado/framework/Caching/TCache.php710
1 files changed, 710 insertions, 0 deletions
diff --git a/lib/prado/framework/Caching/TCache.php b/lib/prado/framework/Caching/TCache.php
new file mode 100644
index 0000000..4a66dd0
--- /dev/null
+++ b/lib/prado/framework/Caching/TCache.php
@@ -0,0 +1,710 @@
+<?php
+/**
+ * TCache and cache dependency classes.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link https://github.com/pradosoft/prado
+ * @copyright Copyright &copy; 2005-2015 The PRADO Group
+ * @license https://github.com/pradosoft/prado/blob/master/COPYRIGHT
+ * @package System.Caching
+ */
+
+Prado::using('System.Collections.TList');
+
+/**
+ * TCache class
+ *
+ * TCache is the base class for cache classes with different cache storage implementation.
+ *
+ * TCache implements the interface {@link ICache} with the following methods,
+ * - {@link get} : retrieve the value with a key (if any) from cache
+ * - {@link set} : store the value with a key into cache
+ * - {@link add} : store the value only if cache does not have this key
+ * - {@link delete} : delete the value with the specified key from cache
+ * - {@link flush} : delete all values from cache
+ *
+ * Each value is associated with an expiration time. The {@link get} operation
+ * ensures that any expired value will not be returned. The expiration time by
+ * the number of seconds. A expiration time 0 represents never expire.
+ *
+ * By definition, cache does not ensure the existence of a value
+ * even if it never expires. Cache is not meant to be an persistent storage.
+ *
+ * Child classes must implement the following methods:
+ * - {@link getValue}
+ * - {@link setValue}
+ * - {@link addValue}
+ * - {@link deleteValue}
+ * and optionally {@link flush}
+ *
+ * Since version 3.1.2, TCache implements the ArrayAccess interface such that
+ * the cache acts as an array.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @package System.Caching
+ * @since 3.0
+ */
+abstract class TCache extends TModule implements ICache, ArrayAccess
+{
+ private $_prefix=null;
+ private $_primary=true;
+
+ /**
+ * Initializes the cache module.
+ * This method initializes the cache key prefix and registers the cache module
+ * with the application if the cache is primary.
+ * @param TXmlElement the module configuration
+ */
+ public function init($config)
+ {
+ if($this->_prefix===null)
+ $this->_prefix=$this->getApplication()->getUniqueID();
+ if($this->_primary)
+ {
+ if($this->getApplication()->getCache()===null)
+ $this->getApplication()->setCache($this);
+ else
+ throw new TConfigurationException('cache_primary_duplicated',get_class($this));
+ }
+ }
+
+ /**
+ * @return boolean whether this cache module is used as primary/system cache.
+ * A primary cache is used by PRADO core framework to cache data such as
+ * parsed templates, themes, etc.
+ */
+ public function getPrimaryCache()
+ {
+ return $this->_primary;
+ }
+
+ /**
+ * @param boolean whether this cache module is used as primary/system cache. Defaults to false.
+ * @see getPrimaryCache
+ */
+ public function setPrimaryCache($value)
+ {
+ $this->_primary=TPropertyValue::ensureBoolean($value);
+ }
+
+ /**
+ * @return string a unique prefix for the keys of cached values.
+ * If it is not explicitly set, it will take the value of {@link TApplication::getUniqueID}.
+ */
+ public function getKeyPrefix()
+ {
+ return $this->_prefix;
+ }
+
+ /**
+ * @param string a unique prefix for the keys of cached values
+ */
+ public function setKeyPrefix($value)
+ {
+ $this->_prefix=$value;
+ }
+
+ /**
+ * @param string a key identifying a value to be cached
+ * @return sring a key generated from the provided key which ensures the uniqueness across applications
+ */
+ protected function generateUniqueKey($key)
+ {
+ return md5($this->_prefix.$key);
+ }
+
+ /**
+ * Retrieves a value from cache with a specified key.
+ * @param string a key identifying the cached value
+ * @return mixed the value stored in cache, false if the value is not in the cache or expired.
+ */
+ public function get($id)
+ {
+ if(($data=$this->getValue($this->generateUniqueKey($id)))!==false)
+ {
+ if(!is_array($data))
+ return false;
+ if(!($data[1] instanceof ICacheDependency) || !$data[1]->getHasChanged())
+ return $data[0];
+ }
+ return false;
+ }
+
+ /**
+ * Stores a value identified by a key into cache.
+ * If the cache already contains such a key, the existing value and
+ * expiration time will be replaced with the new ones. If the value is
+ * empty, the cache key will be deleted.
+ *
+ * @param string the key identifying the value to be cached
+ * @param mixed the value to be cached
+ * @param integer the number of seconds in which the cached value will expire. 0 means never expire.
+ * @param ICacheDependency dependency of the cached item. If the dependency changes, the item is labeled invalid.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ public function set($id,$value,$expire=0,$dependency=null)
+ {
+ if(empty($value) && $expire === 0)
+ $this->delete($id);
+ else
+ {
+ $data=array($value,$dependency);
+ return $this->setValue($this->generateUniqueKey($id),$data,$expire);
+ }
+ }
+
+ /**
+ * Stores a value identified by a key into cache if the cache does not contain this key.
+ * Nothing will be done if the cache already contains the key or if value is empty.
+ * @param string the key identifying the value to be cached
+ * @param mixed the value to be cached
+ * @param integer the number of seconds in which the cached value will expire. 0 means never expire.
+ * @param ICacheDependency dependency of the cached item. If the dependency changes, the item is labeled invalid.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ public function add($id,$value,$expire=0,$dependency=null)
+ {
+ if(empty($value) && $expire === 0)
+ return false;
+ $data=array($value,$dependency);
+ return $this->addValue($this->generateUniqueKey($id),$data,$expire);
+ }
+
+ /**
+ * Deletes a value with the specified key from cache
+ * @param string the key of the value to be deleted
+ * @return boolean if no error happens during deletion
+ */
+ public function delete($id)
+ {
+ return $this->deleteValue($this->generateUniqueKey($id));
+ }
+
+ /**
+ * Deletes all values from cache.
+ * Be careful of performing this operation if the cache is shared by multiple applications.
+ * Child classes may implement this method to realize the flush operation.
+ * @throws TNotSupportedException if this method is not overridden by child classes
+ */
+ public function flush()
+ {
+ throw new TNotSupportedException('cache_flush_unsupported');
+ }
+
+ /**
+ * Retrieves a value from cache with a specified key.
+ * This method should be implemented by child classes to store the data
+ * in specific cache storage. The uniqueness and dependency are handled
+ * in {@link get()} already. So only the implementation of data retrieval
+ * is needed.
+ * @param string a unique key identifying the cached value
+ * @return string the value stored in cache, false if the value is not in the cache or expired.
+ */
+ abstract protected function getValue($key);
+
+ /**
+ * Stores a value identified by a key in cache.
+ * This method should be implemented by child classes to store the data
+ * in specific cache storage. The uniqueness and dependency are handled
+ * in {@link set()} already. So only the implementation of data storage
+ * is needed.
+ *
+ * @param string the key identifying the value to be cached
+ * @param string the value to be cached
+ * @param integer the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ abstract protected function setValue($key,$value,$expire);
+
+ /**
+ * Stores a value identified by a key into cache if the cache does not contain this key.
+ * This method should be implemented by child classes to store the data
+ * in specific cache storage. The uniqueness and dependency are handled
+ * in {@link add()} already. So only the implementation of data storage
+ * is needed.
+ *
+ * @param string the key identifying the value to be cached
+ * @param string the value to be cached
+ * @param integer the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ abstract protected function addValue($key,$value,$expire);
+
+ /**
+ * Deletes a value with the specified key from cache
+ * This method should be implemented by child classes to delete the data from actual cache storage.
+ * @param string the key of the value to be deleted
+ * @return boolean if no error happens during deletion
+ */
+ abstract protected function deleteValue($key);
+
+ /**
+ * Returns whether there is a cache entry with a specified key.
+ * This method is required by the interface ArrayAccess.
+ * @param string a key identifying the cached value
+ * @return boolean
+ */
+ public function offsetExists($id)
+ {
+ return $this->get($id) !== false;
+ }
+
+ /**
+ * Retrieves the value from cache with a specified key.
+ * This method is required by the interface ArrayAccess.
+ * @param string a key identifying the cached value
+ * @return mixed the value stored in cache, false if the value is not in the cache or expired.
+ */
+ public function offsetGet($id)
+ {
+ return $this->get($id);
+ }
+
+ /**
+ * Stores the value identified by a key into cache.
+ * If the cache already contains such a key, the existing value will be
+ * replaced with the new ones. To add expiration and dependencies, use the set() method.
+ * This method is required by the interface ArrayAccess.
+ * @param string the key identifying the value to be cached
+ * @param mixed the value to be cached
+ */
+ public function offsetSet($id, $value)
+ {
+ $this->set($id, $value);
+ }
+
+ /**
+ * Deletes the value with the specified key from cache
+ * This method is required by the interface ArrayAccess.
+ * @param string the key of the value to be deleted
+ * @return boolean if no error happens during deletion
+ */
+ public function offsetUnset($id)
+ {
+ $this->delete($id);
+ }
+}
+
+
+/**
+ * TCacheDependency class.
+ *
+ * TCacheDependency is the base class implementing {@link ICacheDependency} interface.
+ * Descendant classes must implement {@link getHasChanged()} to provide
+ * actual dependency checking logic.
+ *
+ * The property value of {@link getHasChanged HasChanged} tells whether
+ * the dependency is changed or not.
+ *
+ * You may disable the dependency checking by setting {@link setEnabled Enabled}
+ * to false.
+ *
+ * Note, since the dependency objects often need to be serialized so that
+ * they can persist across requests, you may need to implement __sleep() and
+ * __wakeup() if the dependency objects contain resource handles which are
+ * not serializable.
+ *
+ * Currently, the following dependency classes are provided in the PRADO release:
+ * - {@link TFileCacheDependency}: checks whether a file is changed or not
+ * - {@link TDirectoryCacheDependency}: checks whether a directory is changed or not
+ * - {@link TGlobalStateCacheDependency}: checks whether a global state is changed or not
+ * - {@link TChainedCacheDependency}: checks whether any of a list of dependencies is changed or not
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @package System.Caching
+ * @since 3.1.0
+ */
+abstract class TCacheDependency extends TComponent implements ICacheDependency
+{
+}
+
+
+/**
+ * TFileCacheDependency class.
+ *
+ * TFileCacheDependency performs dependency checking based on the
+ * last modification time of the file specified via {@link setFileName FileName}.
+ * The dependency is reported as unchanged if and only if the file's
+ * last modification time remains unchanged.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @package System.Caching
+ * @since 3.1.0
+ */
+class TFileCacheDependency extends TCacheDependency
+{
+ private $_fileName;
+ private $_timestamp;
+
+ /**
+ * Constructor.
+ * @param string name of the file whose change is to be checked.
+ */
+ public function __construct($fileName)
+ {
+ $this->setFileName($fileName);
+ }
+
+ /**
+ * @return string the name of the file whose change is to be checked
+ */
+ public function getFileName()
+ {
+ return $this->_fileName;
+ }
+
+ /**
+ * @param string the name of the file whose change is to be checked
+ */
+ public function setFileName($value)
+ {
+ $this->_fileName=$value;
+ $this->_timestamp=@filemtime($value);
+ }
+
+ /**
+ * @return int the last modification time of the file
+ */
+ public function getTimestamp()
+ {
+ return $this->_timestamp;
+ }
+
+ /**
+ * Performs the actual dependency checking.
+ * This method returns true if the last modification time of the file is changed.
+ * @return boolean whether the dependency is changed or not.
+ */
+ public function getHasChanged()
+ {
+ return @filemtime($this->_fileName)!==$this->_timestamp;
+ }
+}
+
+/**
+ * TDirectoryCacheDependency class.
+ *
+ * TDirectoryCacheDependency performs dependency checking based on the
+ * modification time of the files contained in the specified directory.
+ * The directory being checked is specified via {@link setDirectory Directory}.
+ *
+ * By default, all files under the specified directory and subdirectories
+ * will be checked. If the last modification time of any of them is changed
+ * or if different number of files are contained in a directory, the dependency
+ * is reported as changed. By specifying {@link setRecursiveCheck RecursiveCheck}
+ * and {@link setRecursiveLevel RecursiveLevel}, one can limit the checking
+ * to a certain depth of the subdirectories.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @package System.Caching
+ * @since 3.1.0
+ */
+class TDirectoryCacheDependency extends TCacheDependency
+{
+ private $_recursiveCheck=true;
+ private $_recursiveLevel=-1;
+ private $_timestamps;
+ private $_directory;
+
+ /**
+ * Constructor.
+ * @param string the directory to be checked
+ */
+ public function __construct($directory)
+ {
+ $this->setDirectory($directory);
+ }
+
+ /**
+ * @return string the directory to be checked
+ */
+ public function getDirectory()
+ {
+ return $this->_directory;
+ }
+
+ /**
+ * @param string the directory to be checked
+ * @throws TInvalidDataValueException if the directory does not exist
+ */
+ public function setDirectory($directory)
+ {
+ if(($path=realpath($directory))===false || !is_dir($path))
+ throw new TInvalidDataValueException('directorycachedependency_directory_invalid',$directory);
+ $this->_directory=$path;
+ $this->_timestamps=$this->generateTimestamps($path);
+ }
+
+ /**
+ * @return boolean whether the subdirectories of the directory will also be checked.
+ * It defaults to true.
+ */
+ public function getRecursiveCheck()
+ {
+ return $this->_recursiveCheck;
+ }
+
+ /**
+ * @param boolean whether the subdirectories of the directory will also be checked.
+ */
+ public function setRecursiveCheck($value)
+ {
+ $this->_recursiveCheck=TPropertyValue::ensureBoolean($value);
+ }
+
+ /**
+ * @return int the depth of the subdirectories to be checked.
+ * It defaults to -1, meaning unlimited depth.
+ */
+ public function getRecursiveLevel()
+ {
+ return $this->_recursiveLevel;
+ }
+
+ /**
+ * Sets a value indicating the depth of the subdirectories to be checked.
+ * This is meaningful only when {@link getRecursiveCheck RecursiveCheck}
+ * is true.
+ * @param int the depth of the subdirectories to be checked.
+ * If the value is less than 0, it means unlimited depth.
+ * If the value is 0, it means checking the files directly under the specified directory.
+ */
+ public function setRecursiveLevel($value)
+ {
+ $this->_recursiveLevel=TPropertyValue::ensureInteger($value);
+ }
+
+ /**
+ * Performs the actual dependency checking.
+ * This method returns true if the directory is changed.
+ * @return boolean whether the dependency is changed or not.
+ */
+ public function getHasChanged()
+ {
+ return $this->generateTimestamps($this->_directory)!=$this->_timestamps;
+ }
+
+ /**
+ * Checks to see if the file should be checked for dependency.
+ * This method is invoked when dependency of the whole directory is being checked.
+ * By default, it always returns true, meaning the file should be checked.
+ * You may override this method to check only certain files.
+ * @param string the name of the file that may be checked for dependency.
+ * @return boolean whether this file should be checked.
+ */
+ protected function validateFile($fileName)
+ {
+ return true;
+ }
+
+ /**
+ * Checks to see if the specified subdirectory should be checked for dependency.
+ * This method is invoked when dependency of the whole directory is being checked.
+ * By default, it always returns true, meaning the subdirectory should be checked.
+ * You may override this method to check only certain subdirectories.
+ * @param string the name of the subdirectory that may be checked for dependency.
+ * @return boolean whether this subdirectory should be checked.
+ */
+ protected function validateDirectory($directory)
+ {
+ return true;
+ }
+
+ /**
+ * Determines the last modification time for files under the directory.
+ * This method may go recursively into subdirectories if
+ * {@link setRecursiveCheck RecursiveCheck} is set true.
+ * @param string the directory name
+ * @param int level of the recursion
+ * @return array list of file modification time indexed by the file path
+ */
+ protected function generateTimestamps($directory,$level=0)
+ {
+ if(($dir=opendir($directory))===false)
+ throw new TIOException('directorycachedependency_directory_invalid',$directory);
+ $timestamps=array();
+ while(($file=readdir($dir))!==false)
+ {
+ $path=$directory.DIRECTORY_SEPARATOR.$file;
+ if($file==='.' || $file==='..')
+ continue;
+ else if(is_dir($path))
+ {
+ if(($this->_recursiveLevel<0 || $level<$this->_recursiveLevel) && $this->validateDirectory($path))
+ $timestamps=array_merge($this->generateTimestamps($path,$level+1));
+ }
+ else if($this->validateFile($path))
+ $timestamps[$path]=filemtime($path);
+ }
+ closedir($dir);
+ return $timestamps;
+ }
+}
+
+
+/**
+ * TGlobalStateCacheDependency class.
+ *
+ * TGlobalStateCacheDependency checks if a global state is changed or not.
+ * If the global state is changed, the dependency is reported as changed.
+ * To specify which global state this dependency should check with,
+ * set {@link setStateName StateName} to the name of the global state.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @package System.Caching
+ * @since 3.1.0
+ */
+class TGlobalStateCacheDependency extends TCacheDependency
+{
+ private $_stateName;
+ private $_stateValue;
+
+ /**
+ * Constructor.
+ * @param string the name of the global state
+ */
+ public function __construct($name)
+ {
+ $this->setStateName($name);
+ }
+
+ /**
+ * @return string the name of the global state
+ */
+ public function getStateName()
+ {
+ return $this->_stateName;
+ }
+
+ /**
+ * @param string the name of the global state
+ * @see TApplication::setGlobalState
+ */
+ public function setStateName($value)
+ {
+ $this->_stateName=$value;
+ $this->_stateValue=Prado::getApplication()->getGlobalState($value);
+ }
+
+ /**
+ * Performs the actual dependency checking.
+ * This method returns true if the specified global state is changed.
+ * @return boolean whether the dependency is changed or not.
+ */
+ public function getHasChanged()
+ {
+ return $this->_stateValue!==Prado::getApplication()->getGlobalState($this->_stateName);
+ }
+}
+
+
+/**
+ * TChainedCacheDependency class.
+ *
+ * TChainedCacheDependency represents a list of cache dependency objects
+ * and performs the dependency checking based on the checking results of
+ * these objects. If any of them reports a dependency change, TChainedCacheDependency
+ * will return true for the checking.
+ *
+ * To add dependencies to TChainedCacheDependency, use {@link getDependencies Dependencies}
+ * which gives a {@link TCacheDependencyList} instance and can be used like an array
+ * (see {@link TList} for more details}).
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @package System.Caching
+ * @since 3.1.0
+ */
+class TChainedCacheDependency extends TCacheDependency
+{
+ private $_dependencies=null;
+
+ /**
+ * @return TCacheDependencyList list of dependency objects
+ */
+ public function getDependencies()
+ {
+ if($this->_dependencies===null)
+ $this->_dependencies=new TCacheDependencyList;
+ return $this->_dependencies;
+ }
+
+ /**
+ * Performs the actual dependency checking.
+ * This method returns true if any of the dependency objects
+ * reports a dependency change.
+ * @return boolean whether the dependency is changed or not.
+ */
+ public function getHasChanged()
+ {
+ if($this->_dependencies!==null)
+ {
+ foreach($this->_dependencies as $dependency)
+ if($dependency->getHasChanged())
+ return true;
+ }
+ return false;
+ }
+}
+
+
+/**
+ * TApplicationStateCacheDependency class.
+ *
+ * TApplicationStateCacheDependency performs dependency checking based on
+ * the mode of the currently running PRADO application.
+ * The dependency is reportedly as unchanged if and only if the application
+ * is running in performance mode.
+ *
+ * You may chain this dependency together with other dependencies
+ * so that only when the application is not in performance mode the other dependencies
+ * will be checked.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @package System.Caching
+ * @since 3.1.0
+ */
+class TApplicationStateCacheDependency extends TCacheDependency
+{
+ /**
+ * Performs the actual dependency checking.
+ * This method returns true if the currently running application is not in performance mode.
+ * @return boolean whether the dependency is changed or not.
+ */
+ public function getHasChanged()
+ {
+ return Prado::getApplication()->getMode()!==TApplicationMode::Performance;
+ }
+}
+
+/**
+ * TCacheDependencyList class.
+ *
+ * TCacheDependencyList represents a list of cache dependency objects.
+ * Only objects implementing {@link ICacheDependency} can be added into this list.
+ *
+ * TCacheDependencyList can be used like an array. See {@link TList}
+ * for more details.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @package System.Caching
+ * @since 3.1.0
+ */
+class TCacheDependencyList extends TList
+{
+ /**
+ * Inserts an item at the specified position.
+ * This overrides the parent implementation by performing additional type checking
+ * for each newly added item.
+ * @param integer the specified position.
+ * @param mixed new item
+ * @throws TInvalidDataTypeException if the item to be inserted is not a dependency instance
+ */
+ public function insertAt($index,$item)
+ {
+ if($item instanceof ICacheDependency)
+ parent::insertAt($index,$item);
+ else
+ throw new TInvalidDataTypeException('cachedependencylist_cachedependency_required');
+ }
+}
+