* @link http://www.pradosoft.com/ * @copyright Copyright © 2006 PradoSoft * @license http://www.pradosoft.com/license/ * @version $Revision: $ $Date: $ * @package System.Web.UI.WebControls */ /** * TOutputCache class. * * TOutputCache enables caching a portion of a Web page, also known as * partial caching. The content being cached can be either static or * dynamic. * * To use TOutputCache, simply enclose the content to be cached * within the TOutputCache component tag on a template, e.g., * * * content to be cached * * * where content to be cached can be static text and/or component tags. * * The validity of the cached content is determined based on two factors: * the {@link setDuration Duration} and the {@link getCacheDependency CacheDependency}. * The former specifies the number of seconds that the data can remain * valid in cache (defaults to 60s), while the latter specifies a dependency * that the data depends on. If the dependency changes, the cached content * is invalidated. By default, TOutputCache doesn't specify a dependency. * Derived classes may override {@link getCacheDependency()} method to * enforce a dependency (such as system state change, etc.) * * The content fetched from cache may be variated with respect to * some parameters. It supports variation with respect to request parameters, * which is specified by {@link setVaryByParam VaryByParam} property. * If a specified request parameter is different, a different version of * cached content is used. This is extremely useful if a page's content * may be variated according to some GET parameters. To variate the cached * content by other factors, override {@link calculateCacheKey()} method. * * Output caches can be nested. An outer cache takes precedence over an * inner cache. This means, if the content cached by the inner cache expires * or is invalidated, while that by the outer cache not, the outer cached * content will be used. * * Note, TOutputCache is effective only for non-postback page requests * and when cache module is enabled. * * Do not attempt to address child controls of TOutputCache when the cached * content is to be used. Use {@link getContentCached ContentCached} property * to determine whether the content is cached or not. * * @author Qiang Xue * @version $Revision: $ $Date: $ * @package System.Web.UI.WebControls * @since 3.1 */ class TOutputCache extends TControl implements INamingContainer { const CACHE_ID_PREFIX='prado:outputcache'; private $_dataCached=false; private $_cacheAvailable=false; private $_cacheChecked=false; private $_cacheKey=null; private $_duration=60; private $_cache=null; private $_contents; private $_state; private $_actions=array(); private $_varyByParam=''; private $_keyPrefix=''; /** * Returns a value indicating whether body contents are allowed for this control. * This method overrides the parent implementation by checking if cached * content is available or not. If yes, it returns false, otherwise true. * @param boolean whether body contents are allowed for this control. */ public function getAllowChildControls() { $this->determineCacheability(); return !$this->_dataCached; } private function determineCacheability() { if(!$this->_cacheChecked) { $this->_cacheChecked=true; if(!$this->getPage()->getIsPostBack() && ($this->_cache=$this->getApplication()->getCache())!==null && $this->_duration>0) { $this->_cacheAvailable=true; $data=$this->_cache->get($this->getCacheKey()); if(($this->_dataCached=($data!==false))) list($this->_contents,$this->_state,$this->_actions)=$data; } } } /** * Performs the Init step for the control and all its child controls. * This method overrides the parent implementation by setting up * the stack of the output cache in the page. * Only framework developers should use this method. * @param TControl the naming container control */ protected function initRecursive($namingContainer=null) { if($this->_cacheAvailable && !$this->_dataCached) { $stack=$this->getPage()->getCachingStack(); $stack->push($this); parent::initRecursive($namingContainer); $stack->pop(); } else parent::initRecursive($namingContainer); } /** * Performs the Load step for the control and all its child controls. * This method overrides the parent implementation by setting up * the stack of the output cache in the page. If the data is restored * from cache, it also recovers the actions associated with the cached data. * Only framework developers should use this method. * @param TControl the naming container control */ protected function loadRecursive() { if($this->_cacheAvailable && !$this->_dataCached) { $stack=$this->getPage()->getCachingStack(); $stack->push($this); parent::loadRecursive(); $stack->pop(); } else { if($this->_dataCached) $this->performActions(); parent::loadRecursive(); } } private function performActions() { $page=$this->getPage(); $cs=$page->getClientScript(); foreach($this->_actions as $action) { if($action[0]==='Page.ClientScript') call_user_func_array(array($cs,$action[1]),$action[2]); else if($action[0]==='Page') call_user_func_array(array($page,$action[1]),$action[2]); else call_user_func_array(array($this->getSubProperty($action[0]),$action[1]),$action[2]); } } /** * Performs the PreRender step for the control and all its child controls. * This method overrides the parent implementation by setting up * the stack of the output cache in the page. * Only framework developers should use this method. * @param TControl the naming container control */ protected function preRenderRecursive() { if($this->_cacheAvailable && !$this->_dataCached) { $stack=$this->getPage()->getCachingStack(); $stack->push($this); parent::preRenderRecursive(); $stack->pop(); } else parent::preRenderRecursive(); } /** * Loads state (viewstate and controlstate) into a control and its children. * This method overrides the parent implementation by loading * cached state if available. * This method should only be used by framework developers. * @param array the collection of the state * @param boolean whether the viewstate should be loaded */ protected function loadStateRecursive(&$state,$needViewState=true) { $st=unserialize($state); parent::loadStateRecursive($st,$needViewState); } /** * Saves all control state (viewstate and controlstate) as a collection. * This method overrides the parent implementation by saving state * into cache if needed. * This method should only be used by framework developers. * @param boolean whether the viewstate should be saved * @return array the collection of the control state (including its children's state). */ protected function &saveStateRecursive($needViewState=true) { if($this->_dataCached) return $this->_state; else { $st=parent::saveStateRecursive($needViewState); // serialization is needed to avoid undefined classes when loading state $this->_state=serialize($st); return $this->_state; } } /** * Registers an action associated with the content being cached. * The registered action will be replayed if the content stored * in the cache is served to end-users. * @param string context of the action method. This is a property-path * referring to the context object (e.g. Page, Page.ClientScript) * @param string method name of the context object * @param array list of parameters to be passed to the action method */ public function registerAction($context,$funcName,$funcParams) { $this->_actions[]=array($context,$funcName,$funcParams); } private function getCacheKey() { if($this->_cacheKey===null) $this->_cacheKey=$this->calculateCacheKey(); return $this->_cacheKey; } /** * Calculates the cache key. * The key is calculated based on the unique ID of this control * and the request parameters specified via {@link setVaryByParam VaryByParam}. * This method may be overriden to support other variations in * the calculated cache key. * @return string cache key */ protected function calculateCacheKey() { if($this->_varyByParam!=='') { $params=array(); $request=$this->getRequest(); foreach(explode(',',$this->_varyByParam) as $name) { $name=trim($name); $params[$name]=$request->itemAt($name); } return $this->getBaseCacheKey().serialize($params); } else return $this->getBaseCacheKey(); } /** * @return string basic cache key without variations */ protected function getBaseCacheKey() { return self::CACHE_ID_PREFIX.$this->_keyPrefix.$this->getPage()->getPagePath().$this->getUniqueID(); } /** * Sets the prefix of the cache key. * This method is used internally by {@link TTemplate}. * @param string key prefix */ public function setCacheKeyPrefix($value) { $this->_keyPrefix=$value; } /** * Returns the dependency of the data to be cached. * The default implementation simply returns null, meaning no specific dependency. * This method may be overriden to associate the data to be cached * with additional dependencies. * @return ICacheDependency */ protected function getCacheDependency() { return null; } /** * @return boolean whether content enclosed is cached or not */ public function getContentCached() { return $this->_dataCached; } /** * @return integer number of seconds that the data can remain in cache. Defaults to 60 seconds. * Note, if cache dependency changes or cache space is limited, * the data may be purged out of cache earlier. */ public function getDuration() { return $this->_duration; } /** * @param integer number of seconds that the data can remain in cache. If 0, it means data is not cached. * @throws TInvalidDataValueException if the value is smaller than 0. */ public function setDuration($value) { if(($value=TPropertyValue::ensureInteger($value))<0) throw new TInvalidDataValueException('outputcache_duration_invalid',get_class($this)); $this->_duration=$value; } /** * @return string a semicolon-separated list of strings used to vary the output cache. Defaults to ''. */ public function getVaryByParam() { return $this->_varyByParam; } /** * Sets the names of the request parameters that should be used in calculating the cache key. * The names should be concatenated by semicolons. * By setting this value, the output cache will use different cached data * for each different set of request parameter values. * @return string a semicolon-separated list of strings used to vary the output cache. */ public function setVaryByParam($value) { $this->_varyByParam=trim($value); } /** * Renders the output cache control. * This method overrides the parent implementation by capturing the output * from its child controls and saving it into cache, if output cache is needed. * @param THtmlWriter */ public function render($writer) { if($this->_dataCached) $writer->write($this->_contents); else if($this->_cacheAvailable) { $textWriter=new TTextWriter; $stack=$this->getPage()->getCachingStack(); $stack->push($this); parent::render(new THtmlWriter($textWriter)); $stack->pop(); $content=$textWriter->flush(); $data=array($content,$this->_state,$this->_actions); $this->_cache->set($this->getCacheKey(),$data,$this->getDuration(),$this->getCacheDependency()); $writer->write($content); } else parent::render($writer); } } ?>