I’m sure you’ve saved something in Magento’s admin area and seen that a cache got invalidated - whether it was modifying a Mailchimp setting, or refreshed some indexes. Maybe you created a cached block and you want it to clear/refresh its cache after a specific setting gets altered. It’s good to know there’s an easy way to do this - this article will explain you how, as well as showing how to use Magento’s block cache.

Block cache

Caching is an important part to make Magento perform better, but a lot of developers - even third-party module creators - forget or just don’t bother to add it in. Even Magento core doesn’t support caching static CMS blocks. Saving one or two database queries doesn’t sound like a significant performance gain, but using widgets or blocks that perform complex tasks isn't uncommon.

As a side-note, check out the Quafzi performance-tweaks module and slides - it caches static blocks and has heaps of other performance improvements.

To add caching functionality to your block, add the following code to your block class:

protected function _construct()
{
    // Add lifetime to your block
    $this->addData(array('cache_lifetime' => 86400)); // 1 day

    // Add cache tags
    $this->addCacheTag(array(
        Mage_Core_Model_Store::CACHE_TAG,
        Mage_Cms_Model_Block::CACHE_TAG,
        Namespace_Modulename_Model_Carousel::CACHE_TAG
    ));
}

public function getCacheKeyInfo()
{
    return array(
        Namespace_Modulename_Model_Carousel::CACHE_TAG,
        $this->getNameInLayout(),
        Mage::app()->getStore()->getId(),
        Mage::getDesign()->getPackageName(),
        Mage::getDesign()->getTheme('template'),
    );
}

Let’s talk about what the above code does.

Cache Lifetime

cache_lifetime is the duration - in seconds - the data will stay alive for. This means that after expiry the next pageload will cause your blocks toHtml method to be called instead of the HTML being retrieved from cache. The cache will automatically be refreshed as opposed to invalidated.

A cache_lifetime of false will cause cache_lifetime to be set to the default value, which is 7200 or 2 hours.

A lifetime of NULL will cause the block to not be cached at all - this is useful for removing cache functionality from core or third party blocks. You can set cache lifetime to NULL through observer events or by rewriting blocks.

This is a good article which goes deeper into the different values of cache_lifetime: Make a clean sweep of commons Magento cache_lifetime workarounds usage

Note: setting the cache lifetime to 0 is very bad because it causes caches to be written to disk*, but they will never be used because the cache invalidates instantly.

* or whatever you have set up as your cache backend

Cache Tags

Cache tags exist so that we can selectively clear cache instances. In the above code example, we can rid of the cache if we clear either the store cache, CMS cache, or our own defined tag.

If we didn’t have our own tag, we could only get rid of the cache instance if we cleared the store or CMS cache, which will probably get rid of heaps of other things as well. We don’t want that because it’s unnecessarily and it might reduce performance. On the contrary, if you only have your own tag, your HTML might contain old information as it might depend on store information or a static CMS block.

The addCacheTag method is implemented by Mage_Core_Block_Abstract and makes it easy for us to add our tags to the cache instance.

Cache Key

Your cacheKeyInfo method returns a key that must be unique for each specific instance of your cache.

Here is our example again:

    return array(
        Namespace_Modulename_Model_Carousel::CACHE_TAG,
        $this->getNameInLayout(),
        Mage::app()->getStore()->getId(),
        Mage::getDesign()->getPackageName(),
        Mage::getDesign()->getTheme('template'),
    );

In this case, it’s pretty simple. I’ll explain why we use certain information.

  • Cache tag, to use our namespace
  • Block name - so that if you add another instance of your block (in your layout xml) with a different name, you won’t be using the same cache
  • Store id: for if you have multiple stores
  • Package and theme: a new cache instance for each new theme, because the phtml file might have been overwritten in a different theme

If the block contains data about a specific product (such as related products), you would need to add the product ID or SKU to the list - you don't want each product to show the same related products!

We can conclude that there is a big difference in cache tag and cache key: cache tags are used as handles for clearing certain caches. Cache keys have to be unique for each "HTML".

Executing code after changes to system config

Now let’s say your module contains system configuration that, after you change its values, has to either refresh or invalidate cache.

Most tables relate to a model. The customer object, for example, maps to the customer_entity table. The core configuration is no different. Mage_Core_Model_Config_Data is the class responsible for the core_config_data table.

For instance, we can load a certain config value just like we load any other object:

echo Mage::getModel('core/config_data')
    ->load(4)
    ->getPath(4);
// outputs: admin/dashboard/enable_charts

Basically, we have to execute some code whenever we save a certain fields. We also know that each (resource) model has _beforeSave and _afterSave methods.

One way of solving this, is to rewrite the Mage_Core_Config_Data class with our own model, and have something like this method:

private function _afterSave() {
    if($this->getPath() === ‘namespace/general/enabled’) {
        // Code that flushes cache goes here
    }
}

This will probably work, but we know that rewrites should be avoided if possible. Luckily, Magento offers a way to use any model for reading or writing to the config when using the System -> Configuration area. You can add a backend_model tag in the place where you define your configuration value in your module’s system.xml (or in config.xml):

<enabled>
    <label>Enabled</label>
    <frontend_type>select</frontend_type>
    <source_model>adminhtml/system_config_source_yesno</source_model>
    <backend_model>namespace/system_config_backend_carousel</backend_model>
    <sort_order>1</sort_order>
    <show_in_default>1</show_in_default>
    <show_in_website>1</show_in_website>
    <show_in_store>1</show_in_store>
</enabled>

Now we need to create our system config model:

class Namespace_Modulename_Model_System_Config_Backend_Carousel
extends Mage_Core_Model_Data {
    // ...
}

Now, when someone reads or writes to our config value through the backend interface Magento instantiates our newly created model.

We’re ready to add implement the cache refreshing. Luckily, Magento already provides a model that does this!

Refreshing cache after changes to system config

Meet Mage_Adminhtml_Model_System_Config_Backend_Cache. He will become your best friend. His _afterSave method looks like this:

protected function _afterSave()
{
    if ($this->isValueChanged()) {
        Mage::app()->cleanCache($this->_cacheTags);
    }
}

The cleanCache takes an array with cache tags. Therefor, all we have to do, is to create a class that implements the protected variable $_cacheTags:

class Mage_Catalogsearch_Model_System_Config_Backend_Sitemap
    extends Mage_Adminhtml_Model_System_Config_Backend_Cache
{
    /**
    * Cache tags to clean
    * @var array
    */
    protected $_cacheTags = array(Mage_Core_Model_Store::CACHE_TAG, Mage_Cms_Model_Block::CACHE_TAG);
}

Invalidating the cache instead or refreshing

If, for some reason, you wish to invalidate the block cache instead of refreshing it, you can do something like:

Mage::app()
    ->getCacheInstance()
    ->invalidateType(Mage_Core_Block_Abstract::CACHE_GROUP);

Be aware though, Magento considers invalidated cache types as disabled - Instead of showing the wrong thing, it chooses to not use cache at all. For this reason you might want to have a cron running that refreshes invalid caches. This is a design decision Magento made most likely so that you can stage changes. Instead of 4 actions, and automatically clearing the cache 4 times, you do 4 actions and clear it once manually.

caching blocks layout.xml magento