Friday, 18 February 2011

Magento Enterprise Full Page Caching

We will discuss here how to setup various magento full page cache options for your site. How it works

Basically, magento has defined 3 processors 1) for product pages, 2) catalog lists and 3) for rest of the CMS pages. In order to add full page caching to any page in magento you need to do have the following tags in your module's config.xml in etc... **Note: Magento Enterprise by default has configured all the CMS pages, Product pages and catalog lists for Caching.

<frontend> <cache> <requests> <!-- Supported definitions: <module_front_name>processor_model</module_front_name> - all module urls<br /> <module_front_name><controller>processor_model</controller></module_front_name> - all module controller actions<br /> <module_front_name><controller><action>processor_model</action></controller></module_front_name> - specific action<br /> --> <mymodule_mycontroller_myaction>enterprise_pagecache/processor_default</mymodule_mycontroller_myaction> </requests> </cache> </frontend>

This could be your dashboard for instance which magento does not cache by default in which case you need to add the tags:

<customer_account>enterprise_pagecache/processor_default</customer_account>

But this configuration would cache the whole page regardless, and you would not want that on customer dashboards where you want to see only a valid customer information and the information might also need to update based on some events.

So we now need to have a cache.xml file which tells us which blocks on the page needs to have special behavioral pattern than other blocks on the page. For instance the recent order block in my dashboard would need to update each time an order is placed, where else the address and customer names should only belong to the current logged in customer and not a cached data from another customer. Some of the blocks may on the other hand be used by different customers.

<?xml version="1.0" encoding="UTF-8"?> <config> <placeholders> <myblock_someaction> <block>mymodule/myblock</block> <name>blockname</name> <placeholder>MY_BLOCK</placeholder> <container>MyModule_Model_MyModel</container> <cache_lifetime>84600</cache_lifetime> </myblock_someaction> </placeholders> </config>

Notice in the above tags, the tagname should be any unique cache tagname, in our case its 'myblock_someaction'. The block tag and the name tag should have the block name and the name as it appears in the layout in design. The placeholder tag should have any unique string. The container needs to contain the model which actually defines the behavior of the block cache. And finally, the cache_lifetime is the time till the cache will be valid. Now, all that matters is the model which defines the behavior of the cache and we hope to look it in details.

Magento Enterprise already handles some of the blocks like the cart_container on the header, the catalog navigation and some other blocks for reporting. Let us know try to define three type of cache model 1) Which is customer specific 2) Is some template specific 3) The block that we always want to refresh.

For Customer specific model we would mainly need to override two abstract methods. 1) _getCacheId() and _renderBlock(). An Example is shown below:

class MyModule_Model_MyModel extends Enterprise_PageCache_Model_Container_Abstract { protected function _getCacheId() { return 'CUSTOMER_STATE_' . md5($this->_placeholder->getAttribute('cache_id') . $this->_getCookieValue(Enterprise_PageCache_Model_Cookie::COOKIE_CUSTOMER, '')); } protected function _renderBlock() { $block = $this->_placeholder->getAttribute('block'); $template = $this->_placeholder->getAttribute('template'); $block = new $block; $block->setTemplate($template); $block->setLayout(Mage::app()->getLayout()); return $block->toHtml(); } }

The above code would allow Mage to have browser cookie for the customer and it would use the cookie value to generate an Id. If the customer logs out and logs in as another customer the cookie would refresh and the cache will be invalidated, and so the block will be newly generated for the customer.

Now, we will look into a template specific caching model.

class MyModule_Model_MyModel extends Enterprise_PageCache_Model_Container_Abstract { protected function _getCacheId() { return 'TEMPLATE_' . md5($this->_placeholder->getAttribute('template').$this->_placeholder->getAttribute('cache_id')); } }

Notice that the above does not requires a renderer. This is because we are expecting the page to be there always and we do not expect that it would get invalidated based on some events. This kind of model would help us have template specific caching of a shared block. Finally, we have the ever refreshing block or the ever self invalidating block.

class MyModule_Model_MyModel extends Enterprise_PageCache_Model_Container_Abstract { protected function _getCacheId() { return 'CONSTANT_STRING_' . md5($this->_placeholder->getAttribute('cache_id')); } protected function _renderBlock() { $block = $this->_placeholder->getAttribute('block'); $template = $this->_placeholder->getAttribute('template'); $block = new $block; $block->setTemplate($template); $block->setLayout(Mage::app()->getLayout()); return $block->toHtml(); } protected function _saveCache($data, $id, $tags = array(), $lifetime = null) { return false; } }

Notice in the above we added / overridden _saveCache(...) and it basically never saves. The Id should be return values based on your requirements. And because it always invalidates you would need _renderBlock() funtion.

Finally, here is a tiny little but very useful trick... Because we can never call mage functionalities from these models (yes we cannot call helpers or do logs, AFAIK). We can slip in values to these blocks using a useful mage technique. Suppose we want some dynamic ID based on some block requirements how do we do it in _getCacheId()... remember we cannot even call session or registry.

In order to achieve that you would need a function in your block (the one you mentioned in cache.xml -> block), public function getCacheKeyInfo(). Here is an example below.

public function getCacheKeyInfo() { return $cacheId = array('my_id' => '1234'); }

Now, in the model you can use this as :

class MyModule_Model_MyModel extends Enterprise_PageCache_Model_Container_Abstract { protected function _getCacheId() { return 'SOME_BLOCK_' . md5($this->_placeholder->getAttribute('my_id').$this->_placeholder->getAttribute('cache_id')); } }

Good Luck with your caching!