37

TL;DR: is there a way I can debug the loading of the layout? As I believe one module's layout is conflicting with another.

Related to a previous question I made: How to make a module layout to be shown on all themes

I have successfully loaded my module on my local testing environment (aka my development PC), tested switching between 3 different themes and it is ok. Then I updated the module on the test or "pre-production" environment we have, where there are a lot of different modules, some proprietary other made by us. On this environment, the module does not show what is needed on the product frontpage. After some testings, I finally come to the conclusion that the problem should be on the layout load process.

So, is there a way I can debug the loading of the layout, how different modules replace or add their own blocks? My point is that I believe there is at least one module that should be conflicting with mine. And as we have so many modules I'm looking for an approach different to disabling modules one by one and see which is the problematic one.

My config.xml file is:

<?xml version="1.0" encoding="UTF-8"?>
<config>
    <modules>
        <Dts_Banners>
            <version>0.1.0</version>
        </Dts_Banners>
    </modules>
    <global>
        <blocks>
            <banners>
                <class>Dts_Banners_Block</class>
            </banners>
        </blocks>
  ....
        <events>
            <controller_action_layout_load_before>
                <observers>
                    <attributesethandle>
                        <class>Dts_Banners_Model_Observer</class>
                        <method>addAttributeSetHandle</method>
                    </attributesethandle>
                </observers>
            </controller_action_layout_load_before>
        </events>
    </global>    
  ....
</config>

My Observer file:

<?php
class Dts_Banners_Model_Observer
{
    /**
     * Checks if the search text on the list of active campaigns (dts_banners_admin table) has some of the comma separated text on the product name
     * If text found, add a layout handle PRODUCT_CAMPAIGN_BANNER after PRODUCT_TYPE_<product_type_id> handle
     * This handle is handled on the banners.xml layout file that triggers the use of the Front.php frontend block
     *
     * Event: controller_action_layout_load_before
     *
     * @param Varien_Event_Observer $observer
     */
    public function addAttributeSetHandle(Varien_Event_Observer $observer) {
        $product = Mage::registry('current_product');
        if (!($product instanceof Mage_Catalog_Model_Product)) return;
      ....
      ....
}

This is my layout file:

<?xml version="1.0" encoding="UTF-8"?>
<layout version="0.1.0">
    <default>
        <reference name="content">
            <block type="banners/front" name="banners.front" as="banners_front" template="banners/product.phtml" before="-"/>
        </reference>
    </default>
</layout>

Previously had a slightly different one where instead of <default></default> I had <Product_Campaign_Banner></Product_Campaign_Banner>. It also worked.

My product.phtml file:

<div class="visual">
    <?php echo $this->showCampaign(); ?>
</div>

The product.phtml file is not loaded and therefore the showCampaign is not executed and there is where all the needed HTML is created.

4
  • 2
    The best would be to have local env as much as possible same as Pre-Production env
    – Fra
    Jan 23, 2013 at 14:49
  • That is what I'm doing right now, but is not easy, we have over 20 third-party modules and even some of them don't work on the pre-production enviroment and their developer(s) are checking the code.
    – Yaroslav
    Jan 23, 2013 at 15:02
  • 4
    I am very curious to know the reasons behind this question being flagged for closing as too localized. The question speaks to general layout debugging, which if you've not done, is quite useful and broadly-applicative.
    – benmarks
    Jan 23, 2013 at 20:09
  • I'm curious too. But I think it could as a result that after several edits to others seems that I'm just trying to solve my own very localized problem. And indeed I am, but at the same time I still hope that by debugging the layout generation process I will be able to find where my error is located. Therefore the solution will be useful for others.
    – Yaroslav
    Jan 23, 2013 at 20:18

6 Answers 6

57

You can log the compiled layout XML directives which are used to generate blocks. Create an observer on controller_action_layout_generate_blocks_before, and in the observer method log the update XML from the transported layout object:

public function logCompiledLayout($o)
{
    $req  = Mage::app()->getRequest();
    $info = sprintf(
        "\nRequest: %s\nFull Action Name: %s_%s_%s\nHandles:\n\t%s\nUpdate XML:\n%s",
        $req->getRouteName(),
        $req->getRequestedRouteName(),      //full action name 1/3
        $req->getRequestedControllerName(), //full action name 2/3
        $req->getRequestedActionName(),     //full action name 3/3
        implode("\n\t",$o->getLayout()->getUpdate()->getHandles()),
        $o->getLayout()->getUpdate()->asString()
    );

    // Force logging to var/log/layout.log
    Mage::log($info, Zend_Log::INFO, 'layout.log', true);
}

Output will be similar to:

2013-01-23T16:24:26+00:00 INFO (6): 
Request: cms
Full Action Name: cms_index_index
Handles:
    default
    cms_page
    STORE_default
    THEME_frontend_default_default
    cms_index_index
    page_two_columns_right
    customer_logged_out
Update XML:
<block name="formkey" type="core/template" template="core/formkey.phtml"/>
<label>All Pages</label>
<!-- ... ->
5
  • Seems helpful, will try it out tomorrow at first hour
    – Yaroslav
    Jan 23, 2013 at 19:22
  • Your answer was the right one, just what I was looking for. The created log outputs all the handles, requests, etc on page load. I've confirmed that my handle is correctly loaded into the main handles array but for some reason the corresponding block is not loaded/displayed.
    – Yaroslav
    Jan 24, 2013 at 10:50
  • 1
    And that should be easier to troubleshoot now that layout has been ruled out :-).
    – benmarks
    Jan 24, 2013 at 11:54
  • I am assuming that you are putting this inside the Action.php file? Also, how is this being called by magento?
    – Metropolis
    Jun 22, 2017 at 15:29
  • "Create an observer on controller_action_layout_generate_blocks_before" - This is an M1 question.
    – benmarks
    Jun 23, 2017 at 3:43
23

You can retrieve all the layout handles in your controller by doing this:

var_dump($this->getLayout()->getUpdate()->getHandles());

Or anywhere (as long as the layout has been initialized) using this:

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

Perhaps that will help you debug.

EDIT

Have you set your config.xml to specify the block class?

    <blocks>
        <banners>
            <class>My_Banners_Block</class>
        </banners>
    </blocks>
9
  • Both tested, my handle is correctly added on the observer method but then the layout is not loaded. I will update my question with some code, maybe it will help.
    – Yaroslav
    Jan 23, 2013 at 13:32
  • @Yaroslav updated my answer Jan 23, 2013 at 13:39
  • Yes, I have that on the config, will update my question.
    – Yaroslav
    Jan 23, 2013 at 13:42
  • @Yaroslav Can you check if the product.phtml gets loaded when changing the block type to core/template? This is just to cancel out errors in your module settings. Jan 23, 2013 at 13:43
  • 1
    @Yaroslav it seems like the problem is a bit too widely spread and difficult to debug here on stackexchange. It's not very clear to me what could cause the issue. Jan 23, 2013 at 14:07
14

I'm using PhpStorm with Magicento and so I thought I would adapt @benmarks great answer to my usage.

In PhpStorm, open up app/code/core/Mage/Core/Controller/Varien/Action.php and put a break point in method generateLayoutBlocks(). I think the point is to insert it anywhere before $this->getLayout()->generateBlocks();. I put it on the previous line.

After you insert the breakpoint, indicated by the red dot on the left by the line number, you can right click it to customize behavior. Click "More" at the bottom to open up all options. enter image description here

Once you open that up, you check the box for "Log message to console" (optional) and "Log evaluated expression" (where the magic happens). Then copy paste this adaptation of benmark's code into the text box. The only thing I've changed is spelling out the $request variable as Mage::app()->getRequest() every time, and changed the $o variable to $this (b/c we're not in the observer context here).

sprintf("\nRequest: %s\nFull Action Name: %s_%s_%s\nHandles:\n\t%s\nUpdate XML:\n%s",Mage::app()->getRequest()->getRouteName(),Mage::app()->getRequest()->getRequestedRouteName(),Mage::app()->getRequest()->getRequestedControllerName(),Mage::app()->getRequest()->getRequestedActionName(),implode("\n\t",$this->getLayout()->getUpdate()->getHandles()),$this->getLayout()->getUpdate()->asString())

So now it looks like this: Image shows advanced breakpoint settings

After you run the program (using xdebug or zend debugger) you'll stop at the breakpoint and see this in the log:

Update XML:
<block name="formkey" type="core/template" template="core/formkey.phtml"/>
<label>All Pages</label>
<block type="page/html" name="root" output="toHtml" template="page/2columns-left.phtml">
   <block type="page/html_head" name="head" as="head">
      <action method="addJs">
         <script>jquery/jquery-migrate-1.2.1.min.js</script>
      </action>
      <action method="addJs">
         <script>jquery/jquery-ui/jquery-ui.min.js</script>
      </action>
      <action method="addJs">
         <script>prototype/prototype.js</script>
      </action>
      <action method="addJs" ifconfig="dev/js/deprecation">
         <script>prototype/deprecation.js</script>
      </action>
      <action method="addJs">
         <script>lib/ccard.js</scrip

There seems to be a size limit for the log entries that might be determined by the idea.cycle.buffer.size property in the idea.properties file for PhpStorm, according to this. You could change that, or just right click on the code window and select "Evaluate Expression" from the dropdown menu, and copy and paste the code to execute in there and you will get the full output.

In the "Evaluate Expression" pop-up, you can right-click (Windows) on the result and select "Copy Value" to get the entire output and paste it somewhere else for analysis.

PhpStorm - copy from Evaluate Expression popup

5

We use Alan Storm's Commerce Bug extension, and find it indispensable for debugging a variety of things in Magento, including layout issues. For layouts you can see which layout handles are active on each page, and which layout xml configurations are being applied to the page.

It's not free, but will save lots of time debugging these kinds of things.

Note: I'm not affiliated with Alan Storm or Commerce Bug in any way, just a happy customer.

3
  • 9
    I am affiliate with Alan Storm (in that I'm him), and just wanted to point out that Commerce Bug 2 also gives you the ability to produce a directed graph diagram of your layouts. alanstorm.com/find_magento_block_name Jan 24, 2013 at 2:04
  • We are also happy customers of Alan Storm commerce bug. But on the system where I'm having these issues it's not installed and we don't have enough licenses for all the test and pre-production systems. And by the way, @AlanStorm, hoy we can get the update for the Commerce Bug 2?
    – Yaroslav
    Jan 24, 2013 at 7:46
  • 1
    @Yaroslav Contact support and we'll get you sorted out with updates and upgrades pulsestorm.net/contact-us Jan 24, 2013 at 8:00
3

Thanx Ben Marks ! This is my version of the xml layout logger you discribed.

It's a very long file so i made XML out of it... :-) You can open with a normal editor....

    <?php

class Gn_Optimization_Model_Debug_Layout {
  public function logCompiledLayout($o) {
    $req = Mage::app()->getRequest();

    $routeName = $req->getRouteName();
    $fullname = $req->getRequestedRouteName() . '_' . $req->getRequestedControllerName() . '_' . $req->getRequestedActionName();

    $info = sprintf(
      "\nRequest: %s\nFull Action Name: %s\nHandles:\n\t%s\n",
      $routeName, $fullname, implode("\n\t", $o->getLayout()->getUpdate()->getHandles())
    );

    Mage::log($info, Zend_Log::DEBUG, 'debug.'.$routeName.'.layout.log', true);
    file_put_contents(Mage::getBaseDir('log').DS.'debug.'.$routeName.'.layout.xml',
                      '<?xml version="1.0" encoding="utf-8"?>'.PHP_EOL
                      .'<layout>'.PHP_EOL.
                      $o->getLayout()->getUpdate()->asString().
                      '</layout>');
  }
}

And my config.xml look's like this in the node:

<events>
  <controller_action_layout_generate_blocks_before>
    <observers>
      <gn_optimization_controller_action_layout_generate_blocks_before>
        <type>singleton</type>
        <class>gn_optimization/debug_layout</class>
        <method>logCompiledLayout</method>
      </gn_optimization_controller_action_layout_generate_blocks_before>
    </observers>
  </controller_action_layout_generate_blocks_before>
</events>

Now, i hope my designer can explain all of this...\o/

1

You can add this to your controller action. It will show handles in more neat way than var_dump.

Zend_Debug::dump($this->getLayout()->getUpdate()->getHandles());

Not the answer you're looking for? Browse other questions tagged or ask your own question.