* @link http://www.yiiframework.com/ * @copyright 2008-2013 Yii Software LLC * @license http://www.yiiframework.com/license/ */ /** * COutputCache enables caching the output generated by an action or a view fragment. * * If the output to be displayed is found valid in cache, the cached * version will be displayed instead, which saves the time for generating * the original output. * * Since COutputCache extends from {@link CFilterWidget}, it can be used * as either a filter (for action caching) or a widget (for fragment caching). * For the latter, the shortcuts {@link CBaseController::beginCache()} and {@link CBaseController::endCache()} * are often used instead, like the following in a view file: *
* if($this->beginCache('cacheName',array('property1'=>'value1',...)) * { * // ... display the content to be cached here * $this->endCache(); * } ** * COutputCache must work with a cache application component specified via {@link cacheID}. * If the cache application component is not available, COutputCache will be disabled. * * The validity of the cached content is determined based on two factors: * the {@link duration} and the cache {@link dependency}. * The former specifies the number of seconds that the data can remain * valid in cache (defaults to 60s), while the latter specifies conditions * that the cached data depends on. If a dependency changes, * (e.g. relevant data in DB are updated), the cached data will be invalidated. * For more details about cache dependency, see {@link CCacheDependency}. * * Sometimes, it is necessary to turn off output caching only for certain request types. * For example, we only want to cache a form when it is initially requested; * any subsequent display of the form should not be cached because it contains user input. * We can set {@link requestTypes} to be
array('GET')
to accomplish this task.
*
* The content fetched from cache may be variated with respect to
* some parameters. COutputCache supports four kinds of variations:
* * function foo($cache) { ... } ** where $cache refers to the output cache component. * * The PHP expression will be evaluated using {@link evaluateExpression}. * * A PHP expression can be any PHP code that has a value. To learn more about what an expression is, * please refer to the {@link http://www.php.net/manual/en/language.expressions.php php manual}. */ public $varyByExpression; /** * @var boolean whether the content being cached should be differentiated according to user's language. * A language is retrieved via Yii::app()->language. * Defaults to false. * @since 1.1.14 */ public $varyByLanguage=false; /** * @var array list of request types (e.g. GET, POST) for which the cache should be enabled only. * Defaults to null, meaning all request types. */ public $requestTypes; /** * @var string the ID of the cache application component. Defaults to 'cache' (the primary cache application component.) */ public $cacheID='cache'; /** * @var mixed the dependency that the cached content depends on. * This can be either an object implementing {@link ICacheDependency} interface or an array * specifying the configuration of the dependency object. For example, *
* array( * 'class'=>'CDbCacheDependency', * 'sql'=>'SELECT MAX(lastModified) FROM Post', * ) ** would make the output cache depends on the last modified time of all posts. * If any post has its modification time changed, the cached content would be invalidated. */ public $dependency; private $_key; private $_cache; private $_contentCached; private $_content; private $_actions; /** * Performs filtering before the action is executed. * This method is meant to be overridden by child classes if begin-filtering is needed. * @param CFilterChain $filterChain list of filters being applied to an action * @return boolean whether the filtering process should stop after this filter. Defaults to false. */ public function filter($filterChain) { if(!$this->getIsContentCached()) $filterChain->run(); $this->run(); } /** * Marks the start of content to be cached. * Content displayed after this method call and before {@link endCache()} * will be captured and saved in cache. * This method does nothing if valid content is already found in cache. */ public function init() { if($this->getIsContentCached()) $this->replayActions(); elseif($this->_cache!==null) { $this->getController()->getCachingStack()->push($this); ob_start(); ob_implicit_flush(false); } } /** * Marks the end of content to be cached. * Content displayed before this method call and after {@link init()} * will be captured and saved in cache. * This method does nothing if valid content is already found in cache. */ public function run() { if($this->getIsContentCached()) { if($this->getController()->isCachingStackEmpty()) echo $this->getController()->processDynamicOutput($this->_content); else echo $this->_content; } elseif($this->_cache!==null) { $this->_content=ob_get_clean(); $this->getController()->getCachingStack()->pop(); $data=array($this->_content,$this->_actions); if(is_array($this->dependency)) $this->dependency=Yii::createComponent($this->dependency); $this->_cache->set($this->getCacheKey(),$data,$this->duration,$this->dependency); if($this->getController()->isCachingStackEmpty()) echo $this->getController()->processDynamicOutput($this->_content); else echo $this->_content; } } /** * @return boolean whether the content can be found from cache */ public function getIsContentCached() { if($this->_contentCached!==null) return $this->_contentCached; else return $this->_contentCached=$this->checkContentCache(); } /** * Looks for content in cache. * @return boolean whether the content is found in cache. */ protected function checkContentCache() { if((empty($this->requestTypes) || in_array(Yii::app()->getRequest()->getRequestType(),$this->requestTypes)) && ($this->_cache=$this->getCache())!==null) { if($this->duration>0 && ($data=$this->_cache->get($this->getCacheKey()))!==false) { $this->_content=$data[0]; $this->_actions=$data[1]; return true; } if($this->duration==0) $this->_cache->delete($this->getCacheKey()); if($this->duration<=0) $this->_cache=null; } return false; } /** * @return ICache the cache used for caching the content. */ protected function getCache() { return Yii::app()->getComponent($this->cacheID); } /** * Caclulates the base cache key. * The calculated key will be further variated in {@link getCacheKey}. * Derived classes may override this method if more variations are needed. * @return string basic cache key without variations */ protected function getBaseCacheKey() { return self::CACHE_KEY_PREFIX.$this->getId().'.'; } /** * Calculates the cache key. * The key is calculated based on {@link getBaseCacheKey} and other factors, including * {@link varyByRoute}, {@link varyByParam}, {@link varyBySession} and {@link varyByLanguage}. * @return string cache key */ protected function getCacheKey() { if($this->_key!==null) return $this->_key; else { $key=$this->getBaseCacheKey().'.'; if($this->varyByRoute) { $controller=$this->getController(); $key.=$controller->getUniqueId().'/'; if(($action=$controller->getAction())!==null) $key.=$action->getId(); } $key.='.'; if($this->varyBySession) $key.=Yii::app()->getSession()->getSessionID(); $key.='.'; if(is_array($this->varyByParam) && isset($this->varyByParam[0])) { $params=array(); foreach($this->varyByParam as $name) { if(isset($_GET[$name])) $params[$name]=$_GET[$name]; else $params[$name]=''; } $key.=serialize($params); } $key.='.'; if($this->varyByExpression!==null) $key.=$this->evaluateExpression($this->varyByExpression); $key.='.'; if($this->varyByLanguage) $key.=Yii::app()->language; $key.='.'; return $this->_key=$key; } } /** * Records a method call when this output cache is in effect. * When the content is served from the output cache, the recorded * method will be re-invoked. * @param string $context a property name of the controller. The property should refer to an object * whose method is being recorded. If empty it means the controller itself. * @param string $method the method name * @param array $params parameters passed to the method */ public function recordAction($context,$method,$params) { $this->_actions[]=array($context,$method,$params); } /** * Replays the recorded method calls. */ protected function replayActions() { if(empty($this->_actions)) return; $controller=$this->getController(); $cs=Yii::app()->getClientScript(); foreach($this->_actions as $action) { if($action[0]==='clientScript') $object=$cs; elseif($action[0]==='') $object=$controller; else $object=$controller->{$action[0]}; if(method_exists($object,$action[1])) call_user_func_array(array($object,$action[1]),$action[2]); elseif($action[0]==='' && function_exists($action[1])) call_user_func_array($action[1],$action[2]); else throw new CException(Yii::t('yii','Unable to replay the action "{object}.{method}". The method does not exist.', array('object'=>$action[0], 'method'=>$action[1]))); } } }