COutputCache.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. <?php
  2. /**
  3. * COutputCache class file.
  4. *
  5. * @author Qiang Xue <qiang.xue@gmail.com>
  6. * @link http://www.yiiframework.com/
  7. * @copyright 2008-2013 Yii Software LLC
  8. * @license http://www.yiiframework.com/license/
  9. */
  10. /**
  11. * COutputCache enables caching the output generated by an action or a view fragment.
  12. *
  13. * If the output to be displayed is found valid in cache, the cached
  14. * version will be displayed instead, which saves the time for generating
  15. * the original output.
  16. *
  17. * Since COutputCache extends from {@link CFilterWidget}, it can be used
  18. * as either a filter (for action caching) or a widget (for fragment caching).
  19. * For the latter, the shortcuts {@link CBaseController::beginCache()} and {@link CBaseController::endCache()}
  20. * are often used instead, like the following in a view file:
  21. * <pre>
  22. * if($this->beginCache('cacheName',array('property1'=>'value1',...))
  23. * {
  24. * // ... display the content to be cached here
  25. * $this->endCache();
  26. * }
  27. * </pre>
  28. *
  29. * COutputCache must work with a cache application component specified via {@link cacheID}.
  30. * If the cache application component is not available, COutputCache will be disabled.
  31. *
  32. * The validity of the cached content is determined based on two factors:
  33. * the {@link duration} and the cache {@link dependency}.
  34. * The former specifies the number of seconds that the data can remain
  35. * valid in cache (defaults to 60s), while the latter specifies conditions
  36. * that the cached data depends on. If a dependency changes,
  37. * (e.g. relevant data in DB are updated), the cached data will be invalidated.
  38. * For more details about cache dependency, see {@link CCacheDependency}.
  39. *
  40. * Sometimes, it is necessary to turn off output caching only for certain request types.
  41. * For example, we only want to cache a form when it is initially requested;
  42. * any subsequent display of the form should not be cached because it contains user input.
  43. * We can set {@link requestTypes} to be <code>array('GET')</code> to accomplish this task.
  44. *
  45. * The content fetched from cache may be variated with respect to
  46. * some parameters. COutputCache supports four kinds of variations:
  47. * <ul>
  48. * <li>{@link varyByRoute}: this specifies whether the cached content
  49. * should be varied with the requested route (controller and action)</li>
  50. * <li>{@link varyByParam}: this specifies a list of GET parameter names
  51. * and uses the corresponding values to determine the version of the cached content.</li>
  52. * <li>{@link varyBySession}: this specifies whether the cached content
  53. * should be varied with the user session.</li>
  54. * <li>{@link varyByExpression}: this specifies whether the cached content
  55. * should be varied with the result of the specified PHP expression.</li>
  56. * <li>{@link varyByLanguage}: this specifies whether the cached content
  57. * should by varied with the user's language. Available since 1.1.14.</li>
  58. * </ul>
  59. * For more advanced variation, override {@link getBaseCacheKey()} method.
  60. *
  61. * @property boolean $isContentCached Whether the content can be found from cache.
  62. *
  63. * @author Qiang Xue <qiang.xue@gmail.com>
  64. * @package system.web.widgets
  65. * @since 1.0
  66. */
  67. class COutputCache extends CFilterWidget
  68. {
  69. /**
  70. * Prefix to the keys for storing cached data
  71. */
  72. const CACHE_KEY_PREFIX='Yii.COutputCache.';
  73. /**
  74. * @var integer number of seconds that the data can remain in cache. Defaults to 60 seconds.
  75. * If it is 0, existing cached content would be removed from the cache.
  76. * If it is a negative value, the cache will be disabled (any existing cached content will
  77. * remain in the cache.)
  78. *
  79. * Note, if cache dependency changes or cache space is limited,
  80. * the data may be purged out of cache earlier.
  81. */
  82. public $duration=60;
  83. /**
  84. * @var boolean whether the content being cached should be differentiated according to route.
  85. * A route consists of the requested controller ID and action ID.
  86. * Defaults to true.
  87. */
  88. public $varyByRoute=true;
  89. /**
  90. * @var boolean whether the content being cached should be differentiated according to user sessions. Defaults to false.
  91. */
  92. public $varyBySession=false;
  93. /**
  94. * @var array list of GET parameters that should participate in cache key calculation.
  95. * By setting this property, the output cache will use different cached data
  96. * for each different set of GET parameter values.
  97. */
  98. public $varyByParam;
  99. /**
  100. * @var string a PHP expression whose result is used in the cache key calculation.
  101. * By setting this property, the output cache will use different cached data
  102. * for each different expression result.
  103. * The expression can also be a valid PHP callback,
  104. * including class method name (array(ClassName/Object, MethodName)),
  105. * or anonymous function (PHP 5.3.0+). The function/method signature should be as follows:
  106. * <pre>
  107. * function foo($cache) { ... }
  108. * </pre>
  109. * where $cache refers to the output cache component.
  110. *
  111. * The PHP expression will be evaluated using {@link evaluateExpression}.
  112. *
  113. * A PHP expression can be any PHP code that has a value. To learn more about what an expression is,
  114. * please refer to the {@link http://www.php.net/manual/en/language.expressions.php php manual}.
  115. */
  116. public $varyByExpression;
  117. /**
  118. * @var boolean whether the content being cached should be differentiated according to user's language.
  119. * A language is retrieved via Yii::app()->language.
  120. * Defaults to false.
  121. * @since 1.1.14
  122. */
  123. public $varyByLanguage=false;
  124. /**
  125. * @var array list of request types (e.g. GET, POST) for which the cache should be enabled only.
  126. * Defaults to null, meaning all request types.
  127. */
  128. public $requestTypes;
  129. /**
  130. * @var string the ID of the cache application component. Defaults to 'cache' (the primary cache application component.)
  131. */
  132. public $cacheID='cache';
  133. /**
  134. * @var mixed the dependency that the cached content depends on.
  135. * This can be either an object implementing {@link ICacheDependency} interface or an array
  136. * specifying the configuration of the dependency object. For example,
  137. * <pre>
  138. * array(
  139. * 'class'=>'CDbCacheDependency',
  140. * 'sql'=>'SELECT MAX(lastModified) FROM Post',
  141. * )
  142. * </pre>
  143. * would make the output cache depends on the last modified time of all posts.
  144. * If any post has its modification time changed, the cached content would be invalidated.
  145. */
  146. public $dependency;
  147. private $_key;
  148. private $_cache;
  149. private $_contentCached;
  150. private $_content;
  151. private $_actions;
  152. /**
  153. * Performs filtering before the action is executed.
  154. * This method is meant to be overridden by child classes if begin-filtering is needed.
  155. * @param CFilterChain $filterChain list of filters being applied to an action
  156. * @return boolean whether the filtering process should stop after this filter. Defaults to false.
  157. */
  158. public function filter($filterChain)
  159. {
  160. if(!$this->getIsContentCached())
  161. $filterChain->run();
  162. $this->run();
  163. }
  164. /**
  165. * Marks the start of content to be cached.
  166. * Content displayed after this method call and before {@link endCache()}
  167. * will be captured and saved in cache.
  168. * This method does nothing if valid content is already found in cache.
  169. */
  170. public function init()
  171. {
  172. if($this->getIsContentCached())
  173. $this->replayActions();
  174. elseif($this->_cache!==null)
  175. {
  176. $this->getController()->getCachingStack()->push($this);
  177. ob_start();
  178. ob_implicit_flush(false);
  179. }
  180. }
  181. /**
  182. * Marks the end of content to be cached.
  183. * Content displayed before this method call and after {@link init()}
  184. * will be captured and saved in cache.
  185. * This method does nothing if valid content is already found in cache.
  186. */
  187. public function run()
  188. {
  189. if($this->getIsContentCached())
  190. {
  191. if($this->getController()->isCachingStackEmpty())
  192. echo $this->getController()->processDynamicOutput($this->_content);
  193. else
  194. echo $this->_content;
  195. }
  196. elseif($this->_cache!==null)
  197. {
  198. $this->_content=ob_get_clean();
  199. $this->getController()->getCachingStack()->pop();
  200. $data=array($this->_content,$this->_actions);
  201. if(is_array($this->dependency))
  202. $this->dependency=Yii::createComponent($this->dependency);
  203. $this->_cache->set($this->getCacheKey(),$data,$this->duration,$this->dependency);
  204. if($this->getController()->isCachingStackEmpty())
  205. echo $this->getController()->processDynamicOutput($this->_content);
  206. else
  207. echo $this->_content;
  208. }
  209. }
  210. /**
  211. * @return boolean whether the content can be found from cache
  212. */
  213. public function getIsContentCached()
  214. {
  215. if($this->_contentCached!==null)
  216. return $this->_contentCached;
  217. else
  218. return $this->_contentCached=$this->checkContentCache();
  219. }
  220. /**
  221. * Looks for content in cache.
  222. * @return boolean whether the content is found in cache.
  223. */
  224. protected function checkContentCache()
  225. {
  226. if((empty($this->requestTypes) || in_array(Yii::app()->getRequest()->getRequestType(),$this->requestTypes))
  227. && ($this->_cache=$this->getCache())!==null)
  228. {
  229. if($this->duration>0 && ($data=$this->_cache->get($this->getCacheKey()))!==false)
  230. {
  231. $this->_content=$data[0];
  232. $this->_actions=$data[1];
  233. return true;
  234. }
  235. if($this->duration==0)
  236. $this->_cache->delete($this->getCacheKey());
  237. if($this->duration<=0)
  238. $this->_cache=null;
  239. }
  240. return false;
  241. }
  242. /**
  243. * @return ICache the cache used for caching the content.
  244. */
  245. protected function getCache()
  246. {
  247. return Yii::app()->getComponent($this->cacheID);
  248. }
  249. /**
  250. * Caclulates the base cache key.
  251. * The calculated key will be further variated in {@link getCacheKey}.
  252. * Derived classes may override this method if more variations are needed.
  253. * @return string basic cache key without variations
  254. */
  255. protected function getBaseCacheKey()
  256. {
  257. return self::CACHE_KEY_PREFIX.$this->getId().'.';
  258. }
  259. /**
  260. * Calculates the cache key.
  261. * The key is calculated based on {@link getBaseCacheKey} and other factors, including
  262. * {@link varyByRoute}, {@link varyByParam}, {@link varyBySession} and {@link varyByLanguage}.
  263. * @return string cache key
  264. */
  265. protected function getCacheKey()
  266. {
  267. if($this->_key!==null)
  268. return $this->_key;
  269. else
  270. {
  271. $key=$this->getBaseCacheKey().'.';
  272. if($this->varyByRoute)
  273. {
  274. $controller=$this->getController();
  275. $key.=$controller->getUniqueId().'/';
  276. if(($action=$controller->getAction())!==null)
  277. $key.=$action->getId();
  278. }
  279. $key.='.';
  280. if($this->varyBySession)
  281. $key.=Yii::app()->getSession()->getSessionID();
  282. $key.='.';
  283. if(is_array($this->varyByParam) && isset($this->varyByParam[0]))
  284. {
  285. $params=array();
  286. foreach($this->varyByParam as $name)
  287. {
  288. if(isset($_GET[$name]))
  289. $params[$name]=$_GET[$name];
  290. else
  291. $params[$name]='';
  292. }
  293. $key.=serialize($params);
  294. }
  295. $key.='.';
  296. if($this->varyByExpression!==null)
  297. $key.=$this->evaluateExpression($this->varyByExpression);
  298. $key.='.';
  299. if($this->varyByLanguage)
  300. $key.=Yii::app()->language;
  301. $key.='.';
  302. return $this->_key=$key;
  303. }
  304. }
  305. /**
  306. * Records a method call when this output cache is in effect.
  307. * When the content is served from the output cache, the recorded
  308. * method will be re-invoked.
  309. * @param string $context a property name of the controller. The property should refer to an object
  310. * whose method is being recorded. If empty it means the controller itself.
  311. * @param string $method the method name
  312. * @param array $params parameters passed to the method
  313. */
  314. public function recordAction($context,$method,$params)
  315. {
  316. $this->_actions[]=array($context,$method,$params);
  317. }
  318. /**
  319. * Replays the recorded method calls.
  320. */
  321. protected function replayActions()
  322. {
  323. if(empty($this->_actions))
  324. return;
  325. $controller=$this->getController();
  326. $cs=Yii::app()->getClientScript();
  327. foreach($this->_actions as $action)
  328. {
  329. if($action[0]==='clientScript')
  330. $object=$cs;
  331. elseif($action[0]==='')
  332. $object=$controller;
  333. else
  334. $object=$controller->{$action[0]};
  335. if(method_exists($object,$action[1]))
  336. call_user_func_array(array($object,$action[1]),$action[2]);
  337. elseif($action[0]==='' && function_exists($action[1]))
  338. call_user_func_array($action[1],$action[2]);
  339. else
  340. throw new CException(Yii::t('yii','Unable to replay the action "{object}.{method}". The method does not exist.',
  341. array('object'=>$action[0],
  342. 'method'=>$action[1])));
  343. }
  344. }
  345. }