summaryrefslogtreecommitdiff
path: root/framework/web/widgets/COutputCache.php
diff options
context:
space:
mode:
Diffstat (limited to 'framework/web/widgets/COutputCache.php')
-rw-r--r--framework/web/widgets/COutputCache.php347
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 &copy; 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])));
+ }
+ }
+}