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_
, followed by the store code.THEME_
, followed by the current area, current package name and the current theme name.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]
somemodule_index_index
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.
loadLayout()
method is located within app/code/core/Mage/Core/Controller/Varien/Action.php
.default
layout handle is added within an if statement at the top of the methodSTORE_
, THEME_
and the full action name layout handles are added within the addActionLayoutHandles()
method.loadLayoutUpdates()
method, the event controller_action_layout_load_before
is dispatched.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.generateLayoutXml()
method delegates to the generateXml()
method located in app/code/core/Mage/Core/Model/Layout.php
.generateXml()
method gets our XML config object, checks nodes that are named remove
and set an ignore
attribute to true against these.generateLayoutBlocks()
method delegates to the generateBlocks()
method located in app/code/core/Mage/Core/Model/Layout.php
.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.