How it works
In this article I am going to shed light on the classes and functions which work with Magento themes.
Main classes:
- Mage_Core_Model_Layout
- Mage_Core_Model_Layout_Update
- Mage_Core_Model_Design
- Mage_Core_Model_Design_Package
- Mage_Core_Block_Templat
So, where does it all start?
Well, I am not going bother you with the detailed description of how Magento is launched from the outset. Instead, I would like to begin with Controllers, since they launch the assembling of a shop theme .
Of course you have already met the below two functions:
loadLayout() – prepares the whole layout for the current page.
renderLayout() – generates and outputs HTML.
LoadLayout
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
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; } |
$this->getLayout() – it is the class Mage_Core_Model_Layout , which contains the main functions for generating layout and creating the (Block) classes, that are attributed to the templates described in layout.
$this->getLayout()->getUpdate() – is the class Mage_Core_Model_Layout_Update, which retrieves from xml files the handles related to the current url (<modul_controller_action>) and related to handle <default> As a result, it generates or takes xml for the page from cache.
1. As you see, in the first place the following two functions are executed:
1 |
$this->getLayout()->getUpdate()->addHandle($handles ? $handles : 'default'); |
addHandle – simply creates an array of the names of the handles, which will be brought together. Any name indicated in the loadLayout function argument or default handle name gets added to the array in the first place. Next, the second function adds 3-rd handles into the array. For instance, the product page will have the following array:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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') ); $update->addHandle(strtolower($this->getFullActionName())); return $this; } |
Array
(
[0] => default
[1] => STORE_air_english_storeview
[2] => THEME_frontend_air_christmas
[3] => catalog_product_view
)
Mage::app()->getStore()->getCode() | Displays the code of the current Store View |
$package = Mage::getSingleton(‘core/design_package’); | will describe this class a bit later |
$package->getArea() | the frontend code |
$package->getPackageName() | package name of the displayed theme |
$package->getTheme(‘layout’) | the name of the theme that layout files are downloaded from |
$this->getFullActionName() | our main handle: modul_controller_action |
Those of you who are familiar with the theme may have noticed that these are not yet all handles which are going to be assembled for the page. So, let’s move on.
2. Calling the assembling of all received handles into a single xml file.
1 2 3 4 5 6 7 8 9 10 11 |
public function loadLayoutUpdates() { … Mage::dispatchEvent( 'controller_action_layout_load_before', array('action'=>$this, 'layout'=>$this->getLayout()) ); … $this->getLayout()->getUpdate()->load(); … } |
Here you see the Observer which is frequently used to add extra handles for processing. For instance, in my case for the product page:
Array
(
[0] => default
[1] => STORE_air_english_storeview
[2] => THEME_frontend_air_christmas
[3] => catalog_product_view
[4] => PRODUCT_TYPE_configurable
[5] => PRODUCT_108
[6] => customer_logged_in
)
The array processing starts after the Observer completes the task.
$this->getLayout()->getUpdate()->load() – adds more extra handles if their array has been set as an attribute, processes all the <update> tags described in the found handles and creates a single XML file.
1 2 3 4 5 6 7 8 9 10 11 12 |
public function load($handles=array()) { ... foreach ($handles as $handle) { $this->addHandle($handle); } ... foreach ($this->getHandles() as $handle) { $this->merge($handle); } ... } |
A bit later we will see how the marge function, responsible for assembling handles, works.
3. Generating XML
1 2 3 4 5 6 7 8 9 10 11 |
public function generateLayoutXml() { … Mage::dispatchEvent( 'controller_action_layout_generate_xml_before', array('action'=>$this, 'layout'=>$this->getLayout()) ); … $this->getLayout()->generateXml(); … } |
The function $this->getLayout()->generateXml() processes all the <remove> tags in the previously obtained XML file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public function generateXml() { $xml = $this->getUpdate()->asSimplexml(); $removeInstructions = $xml->xpath("//remove"); ... foreach ($removeInstructions as $infoNode) { $attributes = $infoNode->attributes(); $blockName = (string)$attributes->name; ... $ignoreNodes = $xml->xpath("//block[@name='".$blockName."']"); $ignoreReferences = $xml->xpath("//reference[@name='".$blockName."']"); ... $ignoreNodes = array_merge($ignoreNodes, $ignoreReferences); foreach ($ignoreNodes as $block) { ... $block->addAttribute('ignore', true); } } $this->setXml($xml); return $this; } |
Adds the ‘ignore’ property for <block> and <reference> tags that have their names indicated in the <remove> tags.
But at the end there is also a very important function which inputs our final XML into the variable $this->_xml.
Now we have the complete version of the structure which can be accessed through the getNode() function. And this is what we are going to use
1 2 3 4 5 6 7 8 9 |
public function getNode($path=null) { … if ($path === null) { return $this->_xml; } else { return $this->_xml->descend($path); } } |
4. Generating blocks
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public function generateLayoutBlocks() { … Mage::dispatchEvent( 'controller_action_layout_generate_blocks_before', array('action'=>$this, 'layout'=>$this->getLayout()) ); … $this->getLayout()->generateBlocks(); … Mage::dispatchEvent( 'controller_action_layout_generate_blocks_after', array('action'=>$this, 'layout'=>$this->getLayout()) ); … } |
Ok, when all the preliminary procedures have been completed it is time to generate a set of blocks (declare classes).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public function generateBlocks($parent=null) { if (empty($parent)) { $parent = $this->getNode(); } foreach ($parent as $node) { ... 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; } } } |
The called function $this->getLayout()->generateBlocks() has an internal cycle which goes through all XML tags in the obtained Layout and recursively goes through all the tags <block> and <reference>.
The key functions here are the below two:
- $this->_generateBlock($node, $parent)
This function creates an instance of the current block
- $this->_generateAction($node, $parent)
This one performs the indicted function of the ($parent) block, created a level higher.
Ok, looks like we are done with that. All classes have been created. Now it is time to render HTML.
But I have also promised to tell more about this function:
Mage_Core_Model_Layout_Update :: marge()
Also have given only a brief notice for these functions:
Mage_Core_Model_Layout :: _generateBlock($node, $parent)
Mage_Core_Model_Layout :: _generateAction($node, $parent)
…to be continued
Nice. Thank you.
Thanks for the article. There is a typo in several articles: “marge” function, it should be “merge”.