Manual:CacheHelper

This page documents the CacheHelper class and associated functionality, which was available from MediaWiki core since version 1.20 until 1.36, and removed in 1.37 (task T249230).

Rationale

edit

It happens reasonably frequent that you have an interface where you want to display some content that is expensive to obtain or compute. MediaWiki has a caching interface which can be used for this. However, this interface is rather low level and forces you to re-implement the same basic functionality on top of it again and again. The CacheHelper class and associated code aim to provide a solution which is efficient, consistent and easy to use.

Implementation

edit

The functionality is implemented as a CacheHelper object which implement the ICacheHelper interface. This object supports caching of any type of value. To increase easy of usage in special pages and actions, each category has it's own class containing a CacheHelper and additional utilities that allow directly adding cached HTML to the output, notifying the user they are watching a cached interface and allowing the user to invalidate the cache. These are CachedAction and SpecialCachedPage.

  • CacheHelper: contains all the core logic and can be used pretty much any where where you need to cache a bunch of stuff and would like to do so in batch.
  • ICacheHelper: interface for everything implementing CacheHelper functionality.
  • SpecialCachedPage: SpecialPage holding a CacheHelper and implementing ICacheHelper.
  • CachedAction: FormlessAction holding a CacheHelper and implementing ICacheHelper.

CacheHelper

edit

A CacheHelper object takes one or more callbacks that return the results you want to have cached. This is done via the getCachedValue method, which returns the computed value.

Example:

$cache = new CacheHelper();
$cache->setExpiry(60 * 60 * 24); // 1 day
$cache->setCacheKey(array("Cache1", "Cache2"));

$expensiveResult1 = $cache->getCachedValue( function() {
    // Expensive computation here
    return $expensiveResult;
}, array(), "Cache1" );

$expensiveResult2 = $cache->getCachedValue( function() {
    // Expensive computation here
    return $expensiveResult;
}, array(), "Cache2" );

$cache->saveCache();

In case the cache result is in the cache, you will get this cached value. If it's not, the value is freshly computed and kept track of in the object before it's returned.

You can add as many such cached values to the CacheHelper object. After you're done, you need to call the saveCache method, which will combine all the values and stuff them into a single cache entry. This combining makes the whole process more efficient and also ensures that all values where computed at the same point, thus avoiding potential inconsistency issues.

Expiry

edit

You can set the expiry of the cache via the setExpiry method. This can be an interval in seconds, or a unix timestamp.

$cache->setExpiry( 60 * 30 ); // Sets the expiry of the cache to half an hour

The default is 3600.

Cache key

edit

Often you need to be able to cache multiple variations of the same interface, for instance because you want to show different content to users with different rights, or have an option to show verbose output. The setCacheKey method takes an array of values to be used as cache key.

$cache->setCacheKey( array( 'my-awesome-feature', $userIsLoggedIn, $showVerboseOutput ) );

This needs to be set correctly before the first call to getCachedValue.

CachedAction and SpecialCachedPage

edit

These classes both contain a CacheHelper and build on top of it in the same way, so provide the same caching interface.

This is a complete example of a SpecialPage with a chunk of cached HTML and another cached value.

class MySpecialPage extends SpecialCachedPage {
    
    public function execute( $subPage ) {
        // Start the cache, using a time to live of 5 minutes
        $this->startCache( 300 );

        // You can add dynamic, non cached, elements to the page
        $this->getOutput()->addHTML( $subPage );

        $this->addCachedHTML( function() {
            return '<b>Some expensive result: ' . $result . '</b>';
        } );

        // You can add dynamic, non cached, elements to the page, even in between cached parts
        $this->getOutput()->addHTML( $this->getUser()->getName() );

        $anotherValue = $this->getCachedValue( function() {
            return $anotherValue;
        } );

        // If you need a value both for displaying more then one element,
        // and the formatting is cheap, you can just obtain it and not cache
        // the final HTML. This is also applicable when the final HTML
        // varies a lot more then the actual value (for instance due to i18n),
        // or when you need the value for non-displaying purposes.
        $this->updateSomeLogs( $anotherValue );
        $this->displayAsTable( $anotherValue );
    }
    
}
edit

Caching is great, but sometimes users really want to get the latest value. MediaWiki articles have action=purge just for this. SpecialCachedPage and CachedAction come with a similar facility build in. Whenever a cached version is shown, the subtitle of the page will be set to something like "You are watching a cached version of this page which can be up to 30 minutes old. [view latest]". Where "view latest" is a link that after clicking will provide the user with a rebuild version of the page.

This behavior can be altered by overriding the onCacheInitialized method. It's default implementation is as follows:

public function onCacheInitialized( $hasCached ) {
    if ( $hasCached ) {
        $this->getOutput()->setSubtitle( $this->cacheHelper->getCachedNotice( $this->getContext() ) );
    }
}

Expiry

edit

Like with CacheHelper, you can set the expiry via setExpiry.

Cache key

edit

By default the cache key is constructed from the internal name of the SpecialPage or Action, plus the users language. You can change this by overriding the getCacheKey method.

Example:

protected function getCacheKey() {
     return array_merge( parent::getCacheKey(), $this->getRequest()->getValues() );
}

This leads to having a cache version not only varying on the variables described above, but also on any request arguments.

Disabling the cache

edit

It's quite possible that in certain cases you do not want to cache at all. For instance when you have a page of which the content is dependent on many user preferences, caching might result into to big fragmentation. You could however still cache for anonymous users, which might very well be causing the most requests anyway. In such a case you want to be able to disable the cache for logged in users. This can be done via the setCacheEnabled method.

$this->setCacheEnabled( $this->getUser()->isAnon() );

Saving

edit

Classes deriving from SpecialCachedPage will automatically save the cache after your code has been executed. Those deriving from CachedAction need to call save manually, just like when using a raw CacheHelper object.