diff options
Diffstat (limited to 'framework/web/widgets/COutputCache.php')
| -rw-r--r-- | framework/web/widgets/COutputCache.php | 347 |
1 files changed, 347 insertions, 0 deletions
diff --git a/framework/web/widgets/COutputCache.php b/framework/web/widgets/COutputCache.php new file mode 100644 index 0000000..1878ba2 --- /dev/null +++ b/framework/web/widgets/COutputCache.php @@ -0,0 +1,347 @@ +<?php +/** + * COutputCache class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 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: + * <pre> + * if($this->beginCache('cacheName',array('property1'=>'value1',...)) + * { + * // ... display the content to be cached here + * $this->endCache(); + * } + * </pre> + * + * 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 <code>array('GET')</code> to accomplish this task. + * + * The content fetched from cache may be variated with respect to + * some parameters. COutputCache supports four kinds of variations: + * <ul> + * <li>{@link varyByRoute}: this specifies whether the cached content + * should be varied with the requested route (controller and action)</li> + * <li>{@link varyByParam}: this specifies a list of GET parameter names + * and uses the corresponding values to determine the version of the cached content.</li> + * <li>{@link varyBySession}: this specifies whether the cached content + * should be varied with the user session.</li> + * <li>{@link varyByExpression}: this specifies whether the cached content + * should be varied with the result of the specified PHP expression.</li> + * </ul> + * For more advanced variation, override {@link getBaseCacheKey()} method. + * + * @property boolean $isContentCached Whether the content can be found from cache. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: COutputCache.php 3515 2011-12-28 12:29:24Z mdomba $ + * @package system.web.widgets + * @since 1.0 + */ +class COutputCache extends CFilterWidget +{ + /** + * Prefix to the keys for storing cached data + */ + const CACHE_KEY_PREFIX='Yii.COutputCache.'; + + /** + * @var integer number of seconds that the data can remain in cache. Defaults to 60 seconds. + * If it is 0, existing cached content would be removed from the cache. + * If it is a negative value, the cache will be disabled (any existing cached content will + * remain in the cache.) + * + * Note, if cache dependency changes or cache space is limited, + * the data may be purged out of cache earlier. + */ + public $duration=60; + /** + * @var boolean whether the content being cached should be differentiated according to route. + * A route consists of the requested controller ID and action ID. + * Defaults to true. + */ + public $varyByRoute=true; + /** + * @var boolean whether the content being cached should be differentiated according to user sessions. Defaults to false. + */ + public $varyBySession=false; + /** + * @var array list of GET parameters that should participate in cache key calculation. + * By setting this property, the output cache will use different cached data + * for each different set of GET parameter values. + */ + public $varyByParam; + /** + * @var string a PHP expression whose result is used in the cache key calculation. + * By setting this property, the output cache will use different cached data + * for each different expression result. + * The expression can also be a valid PHP callback, + * including class method name (array(ClassName/Object, MethodName)), + * or anonymous function (PHP 5.3.0+). The function/method signature should be as follows: + * <pre> + * function foo($cache) { ... } + * </pre> + * where $cache refers to the output cache component. + */ + public $varyByExpression; + /** + * @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, + * <pre> + * array( + * 'class'=>'CDbCacheDependency', + * 'sql'=>'SELECT MAX(lastModified) FROM Post', + * ) + * </pre> + * 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(); + else if($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; + } + else if($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} and {@link varyBySession}. + * @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.='.'; + + 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; + else if($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]); + else if($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]))); + } + } +} |
