update layout programmatically in magento event observer

Why on earth are you doing it this way?

It would be better to use either the local.xml layout file or a layout file declared for a custom module to do this:

<?xml version="1.0" encoding="UTF-8"?>
<layout>
    <catalog_product_view>
        <reference name="product.info">
            <action method="setTemplate">
                <tpl>customelayout/product/view.phtml</tpl>
            </action>
        </reference>
    </catalog_product_view>
</layout>

FYI when a block name is <remove/>ed, no block with that name will be instantiated for any rendering scope which includes that remove directive.


As Ben said, I don't know why you're going to put it on the observer but the problem in your case is the sequence of loadLayout.

You can check your loaded layout xml by using:

Mage::log(Mage::getSingleton('core/layout')->getUpdate()->asString());

Pretty sure your <action method="setTemplate"><template>customelayout/product/view.phtml</template> has been overridden by other setTemplate that's the reason your template is not shown.

Mage_Core_Controller_Varien_Action

public function loadLayout($handles=null, $generateBlocks=true, $generateXml=true)
{
    // if handles were specified in arguments load them first
    if (false!==$handles && ''!==$handles) {
        $this->getLayout()->getUpdate()->addHandle($handles ? $handles : 'default');
    }

    // add default layout handles for this action
    $this->addActionLayoutHandles();

    $this->loadLayoutUpdates(); //in here: $this->getLayout()->getUpdate()->load();

    if (!$generateXml) {
        return $this;
    }
    //event: controller_action_layout_generate_xml_before
    $this->generateLayoutXml(); //in here: $this->getLayout()->generateXml();

    if (!$generateBlocks) {
        return $this;
    }
    //event: controller_action_layout_generate_blocks_before, your observer is located here
    $this->generateLayoutBlocks(); //in here: $this->getLayout()->generateBlocks();
    $this->_isLayoutLoaded = true;

    return $this;
}

So, you're going to modify the xml using event: controller_action_layout_generate_blocks_before.

It means what you need to do is:

//add the update
$layout->getUpdate()->addUpdate('<reference name="product.info"><action method="setTemplate"><template>customelayout/product/view.phtml</template></action></reference>');
//then generate the xml
$layout->generateXml();

What cause your problem is:

$layout->getUpdate()->load();

was called again after

$layout->getUpdate()->addUpdate('<reference name="product.info"><action method="setTemplate"><template>customelayout/product/view.phtml</template></action></reference>');

Though it is better to use event: controller_action_layout_generate_xml_before. So that you don't need to generate your xml twice.


Another solution, that is, from my point of view, more in the Magento's Spirit is to declare our own handle.

1. Declare an observer of controller_action_layout_load_before

In your module config.xml, under the node config>frontend>events put this code :

<controller_action_layout_load_before>
  <observers>
     <stackoverflow_set_handle>
        <class>stackoverflow_module/observer</class>
        <method>setHandle</method>
     </stackoverflow_set_handle>
  </observers>
</controller_action_layout_load_before>

2. Define your observer

class Stackoverflow_Module_Model_Observer
{
    public function setHandle(Varien_Event_Observer $observer)
    {
        $fullActionName = $observer->getEvent()->getAction()->getFullActionName();
        if (/* Any condition you may want to modify the layout */) {
            Mage::app()->getLayout()->getUpdate()->addHandle('MY_HANDLE_' . $fullActionName);
        }
    }

3. Create a layout xml file

Once done, you have any fullActionName available to use as second level node in your layout update files prefixed by MY_HANDLE_.

Theses instructions will be only triggered if the handle is present, so basicly for any condition you have set in your observer.

<?xml version="1.0"?>
<layout version="0.1.0">

    <MY_HANDLE_catalogsearch_result_index>
        <reference name="left">
            <remove name="catalogsearch.leftnav" />
        </reference>
    </MY_HANDLE_catalogsearch_result_index>

    <MY_HANDLE_catalog_product_view>
        <!-- Do anything you want -->
    </MY_HANDLE_catalog_product_view>

</layout>

Last words

You can of course test the $fullActionName within your observer to have your handle added more specifically, and you can build a handle not dynamically based on fullActionName.

For information, this is the way Magento manages a lot of layout variations :

  • STORE_default > Built dynamically with the current store
  • THEME_frontend_enterprise_enterprise > Built dynamically with the current theme
  • PRODUCT_TYPE_simple > Built dynamically with the current product type
  • PRODUCT_16 > Built dynamically with the current product id
  • customer_logged_out > Only present if customer is logged in
  • and others...

To view them, you can temporarily put this at the end of your index.php :

var_dump(Mage::app()->getLayout()->getUpdate()->getHandles());

If you want to change template of a block from an observer, you should

  1. Listen for the controller_action_layout_generate_blocks_after event

  2. Use PHP to manipulate the layout

By listening for the generate after event, you ensure every action method specified via a file based Layout Update XML string will be called first, and your template change will "win".

I recommend using PHP code because the Layout Update XML system is a domain specific language, the intent of which was to provide a limited set of functionality for layout updates without having to write a single line of PHP. If you're already using a PHP observer, it just makes sense to manipulate the layout via PHP.

Code something like this should get you what you want (again, from the after observer method)

$controller   = $observer->getAction();

//limit to the product view page 
if($controller->getFullActionName() != 'catalog_product_view')
{
    return;
}

$layout       = $controller->getLayout();
$product_info = $layout->getBlock('product.info');
if(!$product_info)
{
    Mage::log('Could not find product.info block');
    return;
}

$product_info->setTemplate('customelayout/product/view.phtml');

Tags:

Magento