Magento Load Layout

Magento layout files are located within the app/design/frontend/[your_package]/[your_theme]/layout directory. Template files are defined within these files within layout handles that Magento loads depending on what specific page you’re viewing on the website. How does Magento do this? For this, we’ll need to take a look at the Magento load layout and render layout methods located in the majority of controller files.

$this->loadLayout();
$this->renderLayout();

Note that not every controller file will have these exact two lines. If you’re creating a module that has its own URL that can be accessed, more than likely within one of the “action” methods within your module’s controller, you will have a reference to these two lines.

To start with, let’s have a look at the Magento load layout method.

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();

    if (!$generateXml) {
        return $this;
    }
    $this->generateLayoutXml();

    if (!$generateBlocks) {
        return $this;
    }
    $this->generateLayoutBlocks();
    $this->_isLayoutLoaded = true;

    return $this;
}

The first if statement checks to see if there are any handles specified as a parameter of the loadLayout() method.

If there are, we add the $handles variable to a handles property with the addHandle() method.

If not, we simply add the default handle to the handles property.

public function addHandle($handle)
{
    if (is_array($handle)) {
        foreach ($handle as $h) {
            $this->_handles[$h] = 1;
        }
    } else {
        $this->_handles[$handle] = 1;
    }
    return $this;
}

We then proceed to the addActionLayoutHandles() method.

public function addActionLayoutHandles()
{
    $update = $this->getLayout()->getUpdate();

    // load store handle
    $update->addHandle('STORE_'.Mage::app()->getStore()->getCode());

    // load theme handle
    $package = Mage::getSingleton('core/design_package');
    $update->addHandle(
        'THEME_'.$package->getArea().'_'.$package->getPackageName().'_'.$package->getTheme('layout')
    );

    // load action handle
    $update->addHandle(strtolower($this->getFullActionName()));

    return $this;
}

So here we’re adding three more layout handles.

  • Store handle – The string STORE_, followed by the store code.
  • Theme handle – The string THEME_, followed by the current area, current package name and the current theme name.
  • Full Action Name handle – The full action name that is usually made up the request URL. For example, a URL of www.somesite.com/somemodule/index/index would more than likely have a handle of somemodule_index_index.

Moving forward, we then arrive at the loadLayoutUpdates() method.

public function loadLayoutUpdates()
{
    $_profilerKey = self::PROFILER_KEY . '::' .$this->getFullActionName();

    // dispatch event for adding handles to layout update
    Mage::dispatchEvent(
        'controller_action_layout_load_before',
        array('action'=>$this, 'layout'=>$this->getLayout())
    );

    // load layout updates by specified handles
    Varien_Profiler::start("$_profilerKey::layout_load");
    $this->getLayout()->getUpdate()->load();
    Varien_Profiler::stop("$_profilerKey::layout_load");

    return $this;
}

This dispatches a controller_action_layout_load_before event and either the customer_logged_in or customer_logged_out layout handle depending on whether the customer is logged in or not on the website.

This happens with the method beforeLoadLayout() in the Mage_Customer_Model_Observer class.

public function beforeLoadLayout($observer)
{
    $loggedIn = Mage::getSingleton('customer/session')->isLoggedIn();

    $observer->getEvent()->getLayout()->getUpdate()
       ->addHandle('customer_logged_' . ($loggedIn ? 'in' : 'out'));
}

Altogether, we should have these layout handles.

  • default
  • STORE_[store_code]
  • THEME_[current_area]_[package_name]_[theme_name]
  • Your full action name handle i.e. somemodule_index_index
  • Either customer_logged_in or customer_logged_out

The generateLayoutXml() method is responsible for returning the XML generated from the layout handles in Magento. This method delegates to the generateXml() method as seen below.

public function generateLayoutXml()
{
    $_profilerKey = self::PROFILER_KEY . '::' . $this->getFullActionName();
    // dispatch event for adding text layouts
    if(!$this->getFlag('', self::FLAG_NO_DISPATCH_BLOCK_EVENT)) {
        Mage::dispatchEvent(
            'controller_action_layout_generate_xml_before',
            array('action'=>$this, 'layout'=>$this->getLayout())
        );
    }

    // generate xml from collected text updates
    Varien_Profiler::start("$_profilerKey::layout_generate_xml");
    $this->getLayout()->generateXml();
    Varien_Profiler::stop("$_profilerKey::layout_generate_xml");

    return $this;
}
public function generateXml()
{
    $xml = $this->getUpdate()->asSimplexml();
    $removeInstructions = $xml->xpath("//remove");
    if (is_array($removeInstructions)) {
        foreach ($removeInstructions as $infoNode) {
            $attributes = $infoNode->attributes();
            $blockName = (string)$attributes->name;
            if ($blockName) {
                $ignoreNodes = $xml->xpath("//block[@name='".$blockName."']");
                if (!is_array($ignoreNodes)) {
                    continue;
                }
                $ignoreReferences = $xml->xpath("//reference[@name='".$blockName."']");
                if (is_array($ignoreReferences)) {
                    $ignoreNodes = array_merge($ignoreNodes, $ignoreReferences);
                }

                foreach ($ignoreNodes as $block) {
                    if ($block->getAttribute('ignore') !== null) {
                        continue;
                    }
                    $acl = (string)$attributes->acl;
                    if ($acl && Mage::getSingleton('admin/session')->isAllowed($acl)) {
                        continue;
                    }
                    if (!isset($block->attributes()->ignore)) {
                        $block->addAttribute('ignore', true);
                    }
                }
            } 
        }
    }
    $this->setXml($xml);
    return $this;
}

The $xml variable within the generateXml() method contains a Mage_Core_Model_Layout_Element object of the layout XML files configuration under the saved layout handles within the _handles property.

A foreach loop checks the number of <remove> nodes included in the layout XML configuration and sets an ignore attribute of true against them.

This attribute will be used shortly, as we take a look at the generateLayoutBlocks() method.

public function generateLayoutBlocks()
{
    $_profilerKey = self::PROFILER_KEY . '::' . $this->getFullActionName();
    // dispatch event for adding xml layout elements
    if(!$this->getFlag('', self::FLAG_NO_DISPATCH_BLOCK_EVENT)) {
        Mage::dispatchEvent(
            'controller_action_layout_generate_blocks_before',
            array('action'=>$this, 'layout'=>$this->getLayout())
        );
    }

    // generate blocks from xml layout
    Varien_Profiler::start("$_profilerKey::layout_generate_blocks");
    $this->getLayout()->generateBlocks();
    Varien_Profiler::stop("$_profilerKey::layout_generate_blocks");

    if(!$this->getFlag('', self::FLAG_NO_DISPATCH_BLOCK_EVENT)) {
        Mage::dispatchEvent(
           'controller_action_layout_generate_blocks_after',
           array('action'=>$this, 'layout'=>$this->getLayout())
        );
    }

    return $this;
}

Not too much to mention here apart from calling the generateBlocks() method.

public function generateBlocks($parent=null)
{
    if (empty($parent)) {
        $parent = $this->getNode();
    }
    foreach ($parent as $node) {
        $attributes = $node->attributes();
        if ((bool)$attributes->ignore) {
            continue;
        }
        switch ($node->getName()) {
            case 'block':
                $this->_generateBlock($node, $parent);
                $this->generateBlocks($node);
                break;

            case 'reference':
                $this->generateBlocks($node);
                break;

            case 'action':
                $this->_generateAction($node, $parent);
                break;
        }
    }
}

Here we have a foreach loop that loops around our layout XML configuration object and checking each node’s name and executing the _generateBlock(), generateBlocks and/or _generateAction() methods depending on whether the name of the node is block, reference or action. Note that the ignore attribute is checked here and the loop continues if this matches true. The actual generation of the blocks will be covered in a separate article.

To summarise what we have learned.

  • The loadLayout() method is located within app/code/core/Mage/Core/Controller/Varien/Action.php.
  • The default layout handle is added within an if statement at the top of the method
  • The STORE_, THEME_ and the full action name layout handles are added within the addActionLayoutHandles() method.
  • Within the loadLayoutUpdates() method, the event controller_action_layout_load_before is dispatched.
  • An observer located in app/core/core/Mage/Customer/Model/Observer.php listens to the event and runs the beforeLoadLayout() method that adds either a customer_logged_in or customer_logged_out layout handle.
  • The generateLayoutXml() method delegates to the generateXml() method located in app/code/core/Mage/Core/Model/Layout.php.
  • The generateXml() method gets our XML config object, checks nodes that are named remove and set an ignore attribute to true against these.
  • The generateLayoutBlocks() method delegates to the generateBlocks() method located in app/code/core/Mage/Core/Model/Layout.php.
  • The generateBlocks() method executes the relevant method for each node in our XML config object. Note that the ignore attribute is checked here and the loop continues if this matches true.

Note: This article is based on Magento Community/Open Source version 1.9.