4 * Abstract special page class with scaffolding for caching the HTML output.
6 * To enable the caching functionality, the cacheExpiry field should be set
7 * before parent::execute is called (and it should be called from execute).
9 * To add HTML that should be cached, use addCachedHTML like this:
10 * $this->addCachedHTML( array( $this, 'displayCachedContent' ) );
12 * Before the first addCachedHTML call, you should call $this->startCache();
13 * After adding the last HTML that should be cached, call $this->saveCache();
17 * @file SpecialCachedPage.php
18 * @ingroup SpecialPage
20 * @licence GNU GPL v2 or later
21 * @author Jeroen De Dauw < jeroendedauw@gmail.com >
23 abstract class SpecialCachedPage
extends SpecialPage
{
26 * The time to live for the cache, in seconds or a unix timestamp indicating the point of expiry.
31 protected $cacheExpiry = 3600;
34 * List of HTML chunks to be cached (if !hasCached) or that where cashed (of hasCached).
35 * If no cached already, then the newly computed chunks are added here,
36 * if it as cached already, chunks are removed from this list as they are needed.
41 protected $cachedChunks;
44 * Indicates if the to be cached content was already cached.
45 * Null if this information is not available yet.
50 protected $hasCached = null;
53 * If the cache is enabled or not.
58 protected $cacheEnabled = true;
61 * Sets if the cache should be enabled or not.
64 * @param boolean $cacheEnabled
66 public function setCacheEnabled( $cacheEnabled ) {
67 $this->cacheEnabled
= $cacheEnabled;
71 * Initializes the caching.
72 * Should be called before the first time anything is added via addCachedHTML.
76 * @param integer|null $cacheExpiry Sets the cache expirty, either ttl in seconds or unix timestamp.
77 * @param boolean|null $cacheEnabled Sets if the cache should be enabled or not.
79 public function startCache( $cacheExpiry = null, $cacheEnabled = null ) {
80 if ( !is_null( $cacheExpiry ) ) {
81 $this->cacheExpiry
= $cacheExpiry;
84 if ( !is_null( $cacheEnabled ) ) {
85 $this->setCacheEnabled( $cacheEnabled );
88 if ( $this->getRequest()->getText( 'action' ) === 'purge' ) {
89 $this->hasCached
= false;
96 * Returns a message that notifies the user he/she is looking at
97 * a cached version of the page, including a refresh link.
103 protected function getCachedNotice() {
104 $refreshArgs = $this->getRequest()->getQueryValues();
105 unset( $refreshArgs['title'] );
106 $refreshArgs['action'] = 'purge';
108 $refreshLink = Linker
::link(
109 $this->getTitle( $this->getTitle()->getSubpageText() ),
110 $this->msg( 'cachedspecial-refresh-now' )->escaped(),
115 if ( $this->cacheExpiry
< 86400 * 3650 ) {
116 $message = $this->msg(
117 'cachedspecial-viewing-cached-ttl',
118 $this->getLanguage()->formatDuration( $this->cacheExpiry
)
122 $message = $this->msg(
123 'cachedspecial-viewing-cached-ts'
127 return $message . ' ' . $refreshLink;
131 * Initializes the caching if not already done so.
132 * Should be called before any of the caching functionality is used.
136 protected function initCaching() {
137 if ( is_null( $this->hasCached
) ) {
138 $cachedChunks = wfGetCache( CACHE_ANYTHING
)->get( $this->getCacheKeyString() );
140 $this->hasCached
= is_array( $cachedChunks );
141 $this->cachedChunks
= $this->hasCached ?
$cachedChunks : array();
143 $this->onCacheInitialized();
147 protected function onCacheInitialized() {
148 if ( $this->hasCached
) {
149 $this->getOutput()->setSubtitle( $this->getCachedNotice() );
154 * Add some HTML to be cached.
155 * This is done by providing a callback function that should
156 * return the HTML to be added. It will only be called if the
157 * item is not in the cache yet or when the cache has been invalidated.
161 * @param {function} $callback
163 * @param string|null $key
165 public function addCachedHTML( $callback, $args = array(), $key = null ) {
166 $this->initCaching();
168 if ( $this->cacheEnabled
&& $this->hasCached
) {
171 if ( is_null( $key ) ) {
172 $itemKey = array_keys( array_slice( $this->cachedChunks
, 0, 1 ) );
173 $itemKey = array_shift( $itemKey );
175 if ( !is_integer( $itemKey ) ) {
176 wfWarn( "Attempted to get item with non-numeric key while the next item in the queue has a key ($itemKey) in " . __METHOD__
);
178 elseif ( is_null( $itemKey ) ) {
179 wfWarn( "Attempted to get an item while the queue is empty in " . __METHOD__
);
182 $html = array_shift( $this->cachedChunks
);
186 if ( array_key_exists( $key, $this->cachedChunks
) ) {
187 $html = $this->cachedChunks
[$key];
188 unset( $this->cachedChunks
[$key] );
191 wfWarn( "There is no item with key '$key' in this->cachedChunks in " . __METHOD__
);
196 $html = call_user_func_array( $callback, $args );
198 if ( $this->cacheEnabled
) {
199 if ( is_null( $key ) ) {
200 $this->cachedChunks
[] = $html;
203 $this->cachedChunks
[$key] = $html;
208 $this->getOutput()->addHTML( $html );
212 * Saves the HTML to the cache in case it got recomputed.
213 * Should be called after the last time anything is added via addCachedHTML.
217 public function saveCache() {
218 if ( $this->cacheEnabled
&& $this->hasCached
=== false && !empty( $this->cachedChunks
) ) {
219 wfGetCache( CACHE_ANYTHING
)->set( $this->getCacheKeyString(), $this->cachedChunks
, $this->cacheExpiry
);
224 * Sets the time to live for the cache, in seconds or a unix timestamp indicating the point of expiry..
228 * @param integer $cacheExpiry
230 protected function setExpirey( $cacheExpiry ) {
231 $this->cacheExpiry
= $cacheExpiry;
235 * Returns the cache key to use to cache this page's HTML output.
236 * Is constructed from the special page name and language code.
242 protected function getCacheKeyString() {
243 return call_user_func_array( 'wfMemcKey', $this->getCacheKey() );
247 * Returns the variables used to constructed the cache key in an array.
253 protected function getCacheKey() {
256 $this->getLanguage()->getCode()