Merge "Use local context to get messages"
[lhc/web/wiklou.git] / includes / CacheHelper.php
1 <?php
2
3 /**
4 * Interface for all classes implementing CacheHelper functionality.
5 *
6 * @since 1.20
7 *
8 * @licence GNU GPL v2 or later
9 * @author Jeroen De Dauw < jeroendedauw@gmail.com >
10 */
11 interface ICacheHelper {
12
13 /**
14 * Sets if the cache should be enabled or not.
15 *
16 * @since 1.20
17 * @param boolean $cacheEnabled
18 */
19 function setCacheEnabled( $cacheEnabled );
20
21 /**
22 * Initializes the caching.
23 * Should be called before the first time anything is added via addCachedHTML.
24 *
25 * @since 1.20
26 *
27 * @param integer|null $cacheExpiry Sets the cache expiry, either ttl in seconds or unix timestamp.
28 * @param boolean|null $cacheEnabled Sets if the cache should be enabled or not.
29 */
30 function startCache( $cacheExpiry = null, $cacheEnabled = null );
31
32 /**
33 * Get a cached value if available or compute it if not and then cache it if possible.
34 * The provided $computeFunction is only called when the computation needs to happen
35 * and should return a result value. $args are arguments that will be passed to the
36 * compute function when called.
37 *
38 * @since 1.20
39 *
40 * @param {function} $computeFunction
41 * @param array|mixed $args
42 * @param string|null $key
43 *
44 * @return mixed
45 */
46 function getCachedValue( $computeFunction, $args = array(), $key = null );
47
48 /**
49 * Saves the HTML to the cache in case it got recomputed.
50 * Should be called after the last time anything is added via addCachedHTML.
51 *
52 * @since 1.20
53 */
54 function saveCache();
55
56 /**
57 * Sets the time to live for the cache, in seconds or a unix timestamp indicating the point of expiry..
58 *
59 * @since 1.20
60 *
61 * @param integer $cacheExpiry
62 */
63 function setExpiry( $cacheExpiry );
64
65 }
66
67 /**
68 * Helper class for caching various elements in a single cache entry.
69 *
70 * To get a cached value or compute it, use getCachedValue like this:
71 * $this->getCachedValue( $callback );
72 *
73 * To add HTML that should be cached, use addCachedHTML like this:
74 * $this->addCachedHTML( $callback );
75 *
76 * The callback function is only called when needed, so do all your expensive
77 * computations here. This function should returns the HTML to be cached.
78 * It should not add anything to the PageOutput object!
79 *
80 * Before the first addCachedHTML call, you should call $this->startCache();
81 * After adding the last HTML that should be cached, call $this->saveCache();
82 *
83 * @since 1.20
84 *
85 * @file CacheHelper.php
86 *
87 * @licence GNU GPL v2 or later
88 * @author Jeroen De Dauw < jeroendedauw@gmail.com >
89 */
90 class CacheHelper implements ICacheHelper {
91
92 /**
93 * The time to live for the cache, in seconds or a unix timestamp indicating the point of expiry.
94 *
95 * @since 1.20
96 * @var integer
97 */
98 protected $cacheExpiry = 3600;
99
100 /**
101 * List of HTML chunks to be cached (if !hasCached) or that where cached (of hasCached).
102 * If not cached already, then the newly computed chunks are added here,
103 * if it as cached already, chunks are removed from this list as they are needed.
104 *
105 * @since 1.20
106 * @var array
107 */
108 protected $cachedChunks;
109
110 /**
111 * Indicates if the to be cached content was already cached.
112 * Null if this information is not available yet.
113 *
114 * @since 1.20
115 * @var boolean|null
116 */
117 protected $hasCached = null;
118
119 /**
120 * If the cache is enabled or not.
121 *
122 * @since 1.20
123 * @var boolean
124 */
125 protected $cacheEnabled = true;
126
127 /**
128 * Function that gets called when initialization is done.
129 *
130 * @since 1.20
131 * @var function
132 */
133 protected $onInitHandler = false;
134
135 /**
136 * Sets if the cache should be enabled or not.
137 *
138 * @since 1.20
139 * @param boolean $cacheEnabled
140 */
141 public function setCacheEnabled( $cacheEnabled ) {
142 $this->cacheEnabled = $cacheEnabled;
143 }
144
145 /**
146 * Initializes the caching.
147 * Should be called before the first time anything is added via addCachedHTML.
148 *
149 * @since 1.20
150 *
151 * @param integer|null $cacheExpiry Sets the cache expiry, either ttl in seconds or unix timestamp.
152 * @param boolean|null $cacheEnabled Sets if the cache should be enabled or not.
153 */
154 public function startCache( $cacheExpiry = null, $cacheEnabled = null ) {
155 if ( is_null( $this->hasCached ) ) {
156 if ( !is_null( $cacheExpiry ) ) {
157 $this->cacheExpiry = $cacheExpiry;
158 }
159
160 if ( !is_null( $cacheEnabled ) ) {
161 $this->setCacheEnabled( $cacheEnabled );
162 }
163
164 $this->initCaching();
165 }
166 }
167
168 /**
169 * Returns a message that notifies the user he/she is looking at
170 * a cached version of the page, including a refresh link.
171 *
172 * @since 1.20
173 *
174 * @param IContextSource $context
175 * @param boolean $includePurgeLink
176 *
177 * @return string
178 */
179 public function getCachedNotice( IContextSource $context, $includePurgeLink = true ) {
180 if ( $this->cacheExpiry < 86400 * 3650 ) {
181 $message = $context->msg(
182 'cachedspecial-viewing-cached-ttl',
183 $context->getLanguage()->formatDuration( $this->cacheExpiry )
184 )->escaped();
185 }
186 else {
187 $message = $context->msg(
188 'cachedspecial-viewing-cached-ts'
189 )->escaped();
190 }
191
192 if ( $includePurgeLink ) {
193 $refreshArgs = $context->getRequest()->getQueryValues();
194 unset( $refreshArgs['title'] );
195 $refreshArgs['action'] = 'purge';
196
197 $subPage = $context->getTitle()->getFullText();
198 $subPage = explode( '/', $subPage, 2 );
199 $subPage = count( $subPage ) > 1 ? $subPage[1] : false;
200
201 $message .= ' ' . Linker::link(
202 $context->getTitle( $subPage ),
203 $context->msg( 'cachedspecial-refresh-now' )->escaped(),
204 array(),
205 $refreshArgs
206 );
207 }
208
209 return $message;
210 }
211
212 /**
213 * Initializes the caching if not already done so.
214 * Should be called before any of the caching functionality is used.
215 *
216 * @since 1.20
217 */
218 protected function initCaching() {
219 if ( $this->cacheEnabled && is_null( $this->hasCached ) ) {
220 $cachedChunks = wfGetCache( CACHE_ANYTHING )->get( $this->getCacheKeyString() );
221
222 $this->hasCached = is_array( $cachedChunks );
223 $this->cachedChunks = $this->hasCached ? $cachedChunks : array();
224
225 if ( $this->onInitHandler !== false ) {
226 call_user_func( $this->onInitHandler, $this->hasCached );
227 }
228 }
229 }
230
231 /**
232 * Get a cached value if available or compute it if not and then cache it if possible.
233 * The provided $computeFunction is only called when the computation needs to happen
234 * and should return a result value. $args are arguments that will be passed to the
235 * compute function when called.
236 *
237 * @since 1.20
238 *
239 * @param {function} $computeFunction
240 * @param array|mixed $args
241 * @param string|null $key
242 *
243 * @return mixed
244 */
245 public function getCachedValue( $computeFunction, $args = array(), $key = null ) {
246 $this->initCaching();
247
248 if ( $this->cacheEnabled && $this->hasCached ) {
249 $value = null;
250
251 if ( is_null( $key ) ) {
252 $itemKey = array_keys( array_slice( $this->cachedChunks, 0, 1 ) );
253 $itemKey = array_shift( $itemKey );
254
255 if ( !is_integer( $itemKey ) ) {
256 wfWarn( "Attempted to get item with non-numeric key while the next item in the queue has a key ($itemKey) in " . __METHOD__ );
257 }
258 elseif ( is_null( $itemKey ) ) {
259 wfWarn( "Attempted to get an item while the queue is empty in " . __METHOD__ );
260 }
261 else {
262 $value = array_shift( $this->cachedChunks );
263 }
264 }
265 else {
266 if ( array_key_exists( $key, $this->cachedChunks ) ) {
267 $value = $this->cachedChunks[$key];
268 unset( $this->cachedChunks[$key] );
269 }
270 else {
271 wfWarn( "There is no item with key '$key' in this->cachedChunks in " . __METHOD__ );
272 }
273 }
274 }
275 else {
276 if ( !is_array( $args ) ) {
277 $args = array( $args );
278 }
279
280 $value = call_user_func_array( $computeFunction, $args );
281
282 if ( $this->cacheEnabled ) {
283 if ( is_null( $key ) ) {
284 $this->cachedChunks[] = $value;
285 }
286 else {
287 $this->cachedChunks[$key] = $value;
288 }
289 }
290 }
291
292 return $value;
293 }
294
295 /**
296 * Saves the HTML to the cache in case it got recomputed.
297 * Should be called after the last time anything is added via addCachedHTML.
298 *
299 * @since 1.20
300 */
301 public function saveCache() {
302 if ( $this->cacheEnabled && $this->hasCached === false && !empty( $this->cachedChunks ) ) {
303 wfGetCache( CACHE_ANYTHING )->set( $this->getCacheKeyString(), $this->cachedChunks, $this->cacheExpiry );
304 }
305 }
306
307 /**
308 * Sets the time to live for the cache, in seconds or a unix timestamp indicating the point of expiry..
309 *
310 * @since 1.20
311 *
312 * @param integer $cacheExpiry
313 */
314 public function setExpiry( $cacheExpiry ) {
315 $this->cacheExpiry = $cacheExpiry;
316 }
317
318 /**
319 * Returns the cache key to use to cache this page's HTML output.
320 * Is constructed from the special page name and language code.
321 *
322 * @since 1.20
323 *
324 * @return string
325 */
326 protected function getCacheKeyString() {
327 return call_user_func_array( 'wfMemcKey', $this->cacheKey );
328 }
329
330 /**
331 * Sets the cache key that should be used.
332 *
333 * @since 1.20
334 *
335 * @param array $cacheKey
336 */
337 public function setCacheKey( array $cacheKey ) {
338 $this->cacheKey = $cacheKey;
339 }
340
341 /**
342 * Rebuild the content, even if it's already cached.
343 * This effectively has the same effect as purging the cache,
344 * since it will be overridden with the new value on the next request.
345 *
346 * @since 1.20
347 */
348 public function rebuildOnDemand() {
349 $this->hasCached = false;
350 }
351
352 /**
353 * Sets a function that gets called when initialization of the cache is done.
354 *
355 * @since 1.20
356 *
357 * @param $handlerFunction
358 */
359 public function setOnInitializedHandler( $handlerFunction ) {
360 $this->onInitHandler = $handlerFunction;
361 }
362
363 }