Subversion-Projekte lars-tiefland.prado

Revision

Blame | Letzte Änderung | Log anzeigen | RSS feed

<?php
/**
 * TCache and cache dependency classes.
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @link http://www.pradosoft.com/
 * @copyright Copyright &copy; 2005-2008 PradoSoft
 * @license http://www.pradosoft.com/license/
 * @version $Id: TCache.php 2513 2008-10-13 10:46:23Z carl $
 * @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>
 * @version $Id: TCache.php 2513 2008-10-13 10:46:23Z carl $
 * @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(($value=$this->getValue($this->generateUniqueKey($id)))!==false)
                {
                        $data=unserialize($value);
                        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),serialize($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),serialize($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>
 * @version $Id: TCache.php 2513 2008-10-13 10:46:23Z carl $
 * @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>
 * @version $Id: TCache.php 2513 2008-10-13 10:46:23Z carl $
 * @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>
 * @version $Id: TCache.php 2513 2008-10-13 10:46:23Z carl $
 * @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>
 * @version $Id: TCache.php 2513 2008-10-13 10:46:23Z carl $
 * @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>
 * @version $Id: TCache.php 2513 2008-10-13 10:46:23Z carl $
 * @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>
 * @version $Id: TCache.php 2513 2008-10-13 10:46:23Z carl $
 * @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>
 * @version $Id: TCache.php 2513 2008-10-13 10:46:23Z carl $
 * @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');
        }
}

?>