Programatically add a Layout Update File in Magento

We sometimes need logic to decide whether an update file should be loaded or not, and need to do this outside of a module’s config.xml area/layout/update node. Instead, we can use an observer and programatically decide whether to add the layout file or not.

config.xml

<?xml version="1.0"?>
<config>
    <modules>
        <Namespace_Module>
            <version>1.0.0</version>
        </Namespace_Module>
    </modules>
    <global>
        <models>
            <mymodule>
                <class>Loewenstark_Layout_Model</class>
            </mymodule>
        </models>
        <events>
            <core_layout_update_updates_get_after>
                <observers>
                    <mymodule_add_layout>
                        <type>singleton</type>
                        <class>mymodule/observer</class>
                        <method>addLayoutXml</method>
                    </mymodule_add_layout>
                </observers>
            </core_layout_update_updates_get_after>     
        </events>
    </global>
</config>

The Observer

Because the updates object extends a SimpleXML Object, we can add our file to it here

<?php
class Namespace_Module_Model_Observer
{
    public function loadXML(Varien_Event_Observer $observer)
    {
        // Conditional to decide whether to load the XML file
        if(Mage::helper('mymodule')->isEnabled()) {
            $xml = $observer->getUpdates()
                ->addChild('mymodule');
            /* @var $xml SimpleXMLElement */
            $xml->addAttribute('module', 'Namespace_Module');
            $xml->addChild('file', 'mymodule/layoutfile.xml');
        }
    }
}

Temporarily disable a cache type

In particular instances, it may be necessary to disable a type of layout cache. For instance, you may AJAX a call to a page which loads a layout handle for the root node and returns it as an JSON response. It may be it necessary to later load another layout handle as the root node, but with layout caches on this erroneously loads the first layout handle. The following solution will temporarily prevent the layout XML from being cached:

    protected function _disableLayoutCache()
    {
        $cache = Mage::app()->getCacheInstance();
        $cache->banUse('layout');
    }

Adding Layout Handles Programatically (and in order)

Adding custom layout handles in the correct order can sometimes be an awkward process, it can often be tempting to use either the controller_action_predispatch or the controller_action_predispatch_fullActionName, event and then add the layout handle in the corresponding observer code.

The problem with this approach is that these events are fired in the controller’s preDispatch method, meaning loadLayout hasn’t yet been called and there won’t be any other layout handles present at that point. This causes problems because your layout handle may rely on items from other layout handles which haven’t been loaded at that point.

Correcting the handles’ order

One method to fix this issue is to use the controller_action_layout_load_before event which is fired in the loadLayoutUpdates, method (this is called via the loadLayout method). At this point, addActionLayoutHandles has already been executed, which makes relying on other handles a possibility. One problem with this approach is the observer method will be called for every page, which won’t work correctly for code which relies on running for a fullActionName. One fix for this method is to either check the existence of a required layout handle or controller action’s full name in the observer code.

XML

<controller_action_layout_load_before>
    <observers>
        <Demo_Module>
            <class>skin/observer</class>
            <method>updateLayoutHandle</method>
        </Demo_Module>
    </observers>
</controller_action_layout_load_before>

Observer

public function updateLayoutHandle($observer)
{
    $update = $observer->getEvent()->getLayout()->getUpdate();
    $handles = $update->getHandles();

    foreach($handles as $k => $handle){
        if($handle == 'catalog_category_default'){
            $handles[$k] = 'catalog_category_layered';
        }
    }

    $update->resetHandles();
    $update->addHandle($handles);   
}

This allows us to get all layout handles, change them as necessary, and re-add them (an array can be passed to the addHandle method). We could also splice in handles at required points using this method.