* @link http://www.yiiframework.com/ * @copyright 2008-2013 Yii Software LLC * @license http://www.yiiframework.com/license/ */ /** * CFileCache provides a file-based caching mechanism. * * For each data value being cached, CFileCache will use store it in a separate file * under {@link cachePath} which defaults to 'protected/runtime/cache'. * CFileCache will perform garbage collection automatically to remove expired cache files. * * See {@link CCache} manual for common cache operations that are supported by CFileCache. * * @property integer $gCProbability The probability (parts per million) that garbage collection (GC) should be performed * when storing a piece of data in the cache. Defaults to 100, meaning 0.01% chance. * * @author Qiang Xue * @package system.caching */ class CFileCache extends CCache { /** * @var string the directory to store cache files. Defaults to null, meaning * using 'protected/runtime/cache' as the directory. */ public $cachePath; /** * @var string cache file suffix. Defaults to '.bin'. */ public $cacheFileSuffix='.bin'; /** * @var integer the level of sub-directories to store cache files. Defaults to 0, * meaning no sub-directories. If the system has huge number of cache files (e.g. 10K+), * you may want to set this value to be 1 or 2 so that the file system is not over burdened. * The value of this property should not exceed 16 (less than 3 is recommended). */ public $directoryLevel=0; /** * @var boolean whether cache entry expiration time should be embedded into a physical file. * Defaults to false meaning that the file modification time will be used to store expire value. * True value means that first ten bytes of the file would be reserved and used to store expiration time. * On some systems PHP is not allowed to change file modification time to be in future even with 777 * permissions, so this property could be useful in this case. * @since 1.1.14 */ public $embedExpiry=false; private $_gcProbability=100; private $_gced=false; /** * Initializes this application component. * This method is required by the {@link IApplicationComponent} interface. */ public function init() { parent::init(); if($this->cachePath===null) $this->cachePath=Yii::app()->getRuntimePath().DIRECTORY_SEPARATOR.'cache'; if(!is_dir($this->cachePath)) mkdir($this->cachePath,0777,true); } /** * @return integer the probability (parts per million) that garbage collection (GC) should be performed * when storing a piece of data in the cache. Defaults to 100, meaning 0.01% chance. */ public function getGCProbability() { return $this->_gcProbability; } /** * @param integer $value the probability (parts per million) that garbage collection (GC) should be performed * when storing a piece of data in the cache. Defaults to 100, meaning 0.01% chance. * This number should be between 0 and 1000000. A value 0 meaning no GC will be performed at all. */ public function setGCProbability($value) { $value=(int)$value; if($value<0) $value=0; if($value>1000000) $value=1000000; $this->_gcProbability=$value; } /** * Deletes all values from cache. * This is the implementation of the method declared in the parent class. * @return boolean whether the flush operation was successful. * @since 1.1.5 */ protected function flushValues() { $this->gc(false); return true; } /** * Retrieves a value from cache with a specified key. * This is the implementation of the method declared in the parent class. * @param string $key a unique key identifying the cached value * @return string|boolean the value stored in cache, false if the value is not in the cache or expired. */ protected function getValue($key) { $cacheFile=$this->getCacheFile($key); if(($time=$this->filemtime($cacheFile))>time()) return @file_get_contents($cacheFile,false,null,$this->embedExpiry ? 10 : -1); elseif($time>0) @unlink($cacheFile); return false; } /** * Stores a value identified by a key in cache. * This is the implementation of the method declared in the parent class. * * @param string $key the key identifying the value to be cached * @param string $value the value to be cached * @param integer $expire 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 */ protected function setValue($key,$value,$expire) { if(!$this->_gced && mt_rand(0,1000000)<$this->_gcProbability) { $this->gc(); $this->_gced=true; } if($expire<=0) $expire=31536000; // 1 year $expire+=time(); $cacheFile=$this->getCacheFile($key); if($this->directoryLevel>0) @mkdir(dirname($cacheFile),0777,true); if(@file_put_contents($cacheFile,$this->embedExpiry ? $expire.$value : $value,LOCK_EX)!==false) { @chmod($cacheFile,0777); return $this->embedExpiry ? true : @touch($cacheFile,$expire); } else return false; } /** * Stores a value identified by a key into cache if the cache does not contain this key. * This is the implementation of the method declared in the parent class. * * @param string $key the key identifying the value to be cached * @param string $value the value to be cached * @param integer $expire 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 */ protected function addValue($key,$value,$expire) { $cacheFile=$this->getCacheFile($key); if($this->filemtime($cacheFile)>time()) return false; return $this->setValue($key,$value,$expire); } /** * Deletes a value with the specified key from cache * This is the implementation of the method declared in the parent class. * @param string $key the key of the value to be deleted * @return boolean if no error happens during deletion */ protected function deleteValue($key) { $cacheFile=$this->getCacheFile($key); return @unlink($cacheFile); } /** * Returns the cache file path given the cache key. * @param string $key cache key * @return string the cache file path */ protected function getCacheFile($key) { if($this->directoryLevel>0) { $base=$this->cachePath; for($i=0;$i<$this->directoryLevel;++$i) { if(($prefix=substr($key,$i+$i,2))!==false) $base.=DIRECTORY_SEPARATOR.$prefix; } return $base.DIRECTORY_SEPARATOR.$key.$this->cacheFileSuffix; } else return $this->cachePath.DIRECTORY_SEPARATOR.$key.$this->cacheFileSuffix; } /** * Removes expired cache files. * @param boolean $expiredOnly whether only expired cache files should be removed. * If false, all cache files under {@link cachePath} will be removed. * @param string $path the path to clean with. If null, it will be {@link cachePath}. */ public function gc($expiredOnly=true,$path=null) { if($path===null) $path=$this->cachePath; if(($handle=opendir($path))===false) return; while(($file=readdir($handle))!==false) { if($file[0]==='.') continue; $fullPath=$path.DIRECTORY_SEPARATOR.$file; if(is_dir($fullPath)) $this->gc($expiredOnly,$fullPath); elseif($expiredOnly && $this->filemtime($fullPath)embedExpiry) return (int)@file_get_contents($path,false,null,0,10); else return @filemtime($path); } }