:::: MENU ::::

Magento Certification: Rendering Blocks (part 3)

4.45 avg. rating (87% score) - 11 votes

Describe the stages in the lifecycle of a block

Which class is responsible for creating an instance of the block?

In Magento, the whole block hierarchy during a page generation is created by the class Mage_Core_Model_Layout. The method generateBlocks is responsible of that. Let’s take a look…

public function generateBlocks($parent=null) {

    if (empty($parent)) {
        $parent = $this->getNode();
    }

    // This loop iterates through each block defined in the layout update 
    // for the page being generated
    foreach ($parent as $node) {

        $attributes = $node->attributes();
        // Ignore flag is set for a block by calling the action remove
        // in a layout file
        if ((bool)$attributes->ignore) {
            continue;
        }

        // Here we can see main tags in a layout XML. We have: 
        // - <blocks /> which generate a new block. 
        // - <reference /> which allows to reference to a pre-existing block. 
        // - <action /> which executes some action in a block.
       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;
        }
    }
}

Three main tags are possible in a Magento layout file:

<block /> creates a new instance of a block.
<reference /> allows to reference a previously created (i.e. by another module) to execute some action on it.
<action /> allows executing some method of the block being referenced.

Which class is responsible for figuring out which blocks should be created for certain pages?

In Magento, the process of determining which blocks from layout should be created and rendered is a bit complex to summarize in a few lines. Don’t expect to understand this on first reading…

Every request to the Magento system, goes through a particular module and controller. In most of controllers, you can find a couple of important calls like these:

function indexAction() {
    . . .
    $this->loadLayout();
    . . .
    $this->renderLayout();
    . . .
}

The first call, usually at the beginnig of the controller action, is responsible of determining which blocks should be rendered and create them. Second call, usually at the end of the action, is responsible to output generation.

If we go one level down into the loadLayout method, which is (if not overriden) in Core_Controller_Varien_Action, you’ll see something like this:

public function loadLayout($handles = null, $generateBlocks = true, $generateXml = true) { 
    . . .
    // This determines layout handles which will be loaded from the layout files.
    // Usually these handles are related to the controller in execution, store view, etc.
    $this->addActionLayoutHandles();

    // This strips the part of the layout which apply to the handles previously added. 
    $this->loadLayoutUpdates();
    if (!$generateXml) { 
        return $this; 
    }
 
    // This call merges the XML obtained in the previous step for each
    // different handle 
    $this->generateLayoutXml();
    if (!$generateBlocks) { 
        return $this;
    }

    // And finally, this call creates the blocks
    $this->generateLayoutBlocks();
    $this->_isLayoutLoaded = true; 
    return $this;
}

The handles that Magento includes automatically for a particular request, are determined in the method addActionLayoutHandles, which looks like the following:

public function addActionLayoutHandles() {

    $update = $this->getLayout()->getUpdate();

    // This adds the 'per store' handle, which will look like STORE_en 
    // if you named the stores with the associated language code
    $update->addHandle('STORE_'.Mage::app()->getStore()->getCode());

    // This a 'per theme' handle, and looks like
    // THEME_frontend_<package-name>_<theme-name>
    $package = Mage::getSingleton('core/design_package');
    $update->addHandle( 'THEME_'.$package->getArea().'_'.$package->getPackageName().'_'.$package->getTheme('layout') ); 
    
    // Finally, Magento adds the controller-action handle. If we are loading
    // a category page, this will look like:
    // catalog_category_view (module_controller_action)
    $update->addHandle(strtolower($this->getFullActionName()));
    return $this;
}

To load the layout handles, Magento calls the method loadLayoutUpdates which in fact is a call to the following method from the Magento Layout Update (Mage_Core_Model_Layout_Update) with some events around…

$this->getLayout()->getUpdate()->load();

If we go deep into this load method, we will see:

public function load($handles=array()) {
    . . .
    // This just checks if the request layout is stored in cache, so 
    // Magento can avoid loading files and mergin again the layout
    if ($this->loadCache()) { 
        return $this;
    }

    // If the cache check fails, merge method will load the whole package
    // layout and will extract only the information for each of the handles
    // we added previously
    foreach ($this->getHandles() as $handle) {
        $this->merge($handle);
    }
 
    // Finally we save the result in cache for future requests
    $this->saveCache();
    return $this;
}

The next step in our path to create blocks is generateLayoutXml, but its name its a bit tricky because it do nothig about generating XML, which in fact was generated in the previous step.

The only action performed by this method is to ‘clean’ blocks and references that are ‘removed’ in the layout by directives like:

<remove name="some-block-name" />

Finally, a call to generateLayoutBlocks, performs the hard work and creates instances for each of the blocks needed to fulfill the request. This is not directly done by the controller, which calls a method from Mage_Core_Model_Layout:

 $this->getLayout()->generateBlocks();

How is the tree of blocks typically rendered?

Following the previous example, the blocks in Magento are rendered when the controller calls to

$this->renderLayout();

This method is in the base class of all Magento Controllers, which is Mage_Core_Controller_Varien_Action. Take a look to the most important lines:

public function renderLayout($output='') {
    . . .
    // This is the main line. The Magento Controller uses the layout instance
    // Mage_Core_Model_Layout to render the blocks which was previously created
    $output = $this->getLayout()->getOutput();
    . . . 
    $this->getResponse()->appendBody($output);
    . . .
}

If we take a look at getOutput method in class Mage_Core_Model_Layout we’ll see:

public function getOutput() {

    $out = '';
    if (!empty($this->_output)) {

        // This seems a loop for outputing all the blocks, but it doesn't work
        // that way. See below.
        foreach ($this->_output as $callback) {
            $out .= $this->getBlock($callback[0])->$callback[1]();
        }
    }
    return $out;
}

If we put a Mage::log or var_dump to see the contents of each $callback var within the loop, we’ll see that this loops loop only twice: one for generating the output, and one for the profiler. What?.

The log inside the loop would show some like this:

 DEBUG (7): Array ( [0] => root [1] => toHtml )

Yes! you guessed… only root Block is kicked to generate its output. Then during the output of this block, i.e. in the template file, this root block is in some way rendeing all of its childs. And these childs then generate its own childs, and so on.

Call rendering takes place when inside a template file (or in code in those non-template based blocks), the block calls to:

 $this->getChildHtml(<name-of-the-child)

Is it possible to create an instance of the block and render it on the page without using the Magento layout?

Well, you can do something tricky like the following:

$block = new Mage_Core_Block_Text()->setText('hello');
$this->getResponse()->appendBody($block->toHtml();

But this is something definitelly not recomended…

Is it possible to create an instance of the block and add it to the current layout manually?

Of course, yes… and in fact this is a common practise by the Magento Team in most of the Admin Controllers. You cad do that with a snippet like the following in any controller:

public function indexAction() {

    // This loads the Layout (see previous sections)
    $this->loadLayout();

    // This creates a block which its class, name and some
    // data (in this case, template file)
    $block = $this->getLayout()->createBlock('module/block')
                               ->setName('blockname')
                               ->setTemplate('template.phtml');

    // Append block to the content block
    $this->getLayout()->getBlock('content')->append($block);

    // Render all the blocks
    $this->renderLayout();
}

How are a block’s children rendered? Once you added a child to the block, can you expect it will be rendered automatically?

It depends on the type of base block (see the next question for more info about this).

Childs may be rendered by calling any of the following Block methods, available in Mage_Core_Block_Abstract:

getChildHtml. Is the most common way of render a child block.

getChildChildHtml. Renders a child of a child block, so it is the same as the previous one but it descends two levels in the hierarchy.

What is a difference in rendering process for different types of blocks?

For the main predefined Magento blocks, it works as follow:

Mage_Core_Block_Text. This is a simple text block so it is not usual that it has child. If so, they will not be automatically rendered, and you would need to render it manually in code.

Mage_Core_Block_Template. Childs of template based block renders when calling getChildHtml from template file. The block should be defined as child either by layout directive or by code. But they are not automatically rendered.

Mage_Core_Block_Text_List. All the childs of a block of this type are automatically rendered when toHtml is called.




Hey! Qué opinas sobre el artículo?