- The Magento Config
- Magento Controller Dispatch and Hello World
- Layouts, Blocks and Templates
- Magento Models and ORM Basics
- Magento Setup Resources
- Magento ORM: Entity Attribute Value; Part 1
- Custom Magento System Configuration
- In Depth Magento System Configuration
- Varien Data Collections
- Magento System Overrides and Upgradability
- Magento Default System Configuration Values
Update: In researching No Frills Magento Layout it became apparent that this article contains incorrect and misleading information. Specifically, the sections on block and reference tags is incorrect. The article still contains useful context (handles, layout XML, etc.), so I’m letting it stand, but proceed with caution.
Continuing along with Magento, we’re going to skip models for the time being, and jump to the right side of our flowchart to look at Layouts and Blocks.
Unlike many popular PHP MVC systems, Magento’s Action Controller does not pass a data object to the view or set properties on the view object. Instead, the View component of Model, View, Controller directly references system models to get the information it needs for display.
One consequence of this design decision is that the View has been separated into Blocks and Templates. Blocks are PHP objects, Templates are “raw” PHP files that contain a mix of HTML and PHP (where PHP is used as a template language). Each Block is tied to a single Template file. Inside a phtml file, PHP’s $this keyword will contain a reference to the Template’s Block object.
A quick example. Take a look a the default product Template at
File: app/design/frontend/base/default/template/catalog/product/list.phtml
//..
You’ll see the following PHP template code.
<?php $_productCollection=$this->getLoadedProductCollection() ?> <?php if(!$_productCollection->count()): ?> <div class="note-msg">
<?php echo $this->__("There are no products matching the selection.") ?> </div>
<?php else: ?>
The getLoadedProductCollection
method can be found in the Template’s Block, Mage_Catalog_Block_Product_List
File: app/code/core/Mage/Catalog/Block/Product/List.php
...
public function getLoadedProductCollection()
{
return $this->_getProductCollection();
}
...
The block’s _getProductCollection
then instantiates models and reads their data, returning a result to the template.
Nesting Blocks
The real power of Blocks/Templates come with the getChildHtml
method. This allows you to include the contents of a secondary Block/Template inside of a primary Block/Template.
Blocks calling Blocks calling Blocks is how the entire HTML layout for your page is created. Take a look at the one column layout Template.
File: app/design/frontend/base/default/template/page/1column.phtml
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="?php echo $this->getLang() ?>" lang="<?php echo $this->getLang() ?>">
<head>
<?php echo $this->getChildHtml('head') ?>
</head>
<body class="page-popup <?php echo $this->getBodyClass()?$this->getBodyClass():'' ?>">
<?php echo $this->getChildHtml('content') ?>
<?php echo $this->getChildHtml('before_body_end') ?>
<?php echo $this->getAbsoluteFooter() ?>
</body>
</html>
The template itself is only 11 lines long. However, each call to $this->getChildHtml(...)
will include and render another Block. These Blocks will, in turn, use getChildHtml
to render other Blocks. It’s Blocks all the way down.
The Layout
So, Blocks and Templates are all well and good, but you’re probably wondering
- How do I tell Magento which Blocks I want to use on a page?
- How do I tell Magento which Block I should start rendering with?
- How do I specify a particular Block in
getChildHtml(...)
? Those strings don’t look like Block names to me.
This is where the Layout Object enters the picture. The Layout is an XML string that will define which Blocks are included on a page, and which Block(s) should kick off the rendering process.
Last time we were echoing content directly from out Action Methods. This time let’s create a simple HTML template for our Hello World module.
First, create a file at
app/design/frontend/base/default/layout/local.xml
with the following contents
<layout version="0.1.0">
<default>
<reference name="root">
<block type="page/html" name="root" output="toHtml" template="simple_page.phtml" />
</reference>
</default>
</layout>
Then, create a file at
app/design/frontend/base/default/template/simple_page.phtml
with the following contents
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Untitled</title>
<meta name="generator" content="BBEdit 9.2" />
<style type="text/css">
body {
background-color:#f00;
}
</style>
</head>
<body>
</body>
</html>
Finally, each Action Controller is responsible for kicking off the layout process. We’ll need to add two method calls to the Action Method.
public function indexAction() {
//remove our previous echo
//echo 'Hello Index!';
$this->loadLayout();
$this->renderLayout();
}
Clear your Magento cache and reload your Hello World controller page. You should now see a website with a bright red background and an HTML source that matches what’s in simple_page.phtml.
What’s Going On
So, that’s a lot of voodoo and cryptic incantations. Let’s take a look at what’s going on. First, you’ll want to install the Layoutviewer module. This is a module similar to the Configviewer module that will let us peek at some of Magento’s internals.
Once you’ve installed the module (similar to how you setup the Configviewer module), go to the following URL
http://example.com/helloworld/index/index?showLayout=page
This is the layout xml for your page/request. It’s made up of <block />, <reference /> and <remove /> tags. When you call the loadLayout
method of your Action Controller, Magento will
- Generate this Layout XML
- Instantiate a Block class for each <block /> and <reference /> tag, looking up the class using the tag’s name attribute as a global config path and store in the internal
_blocks
array of the layout object. -
If the <block /> tag contains an output attribute, its value is added to the internal
_output
array of the layout object.
Then, when you call the renderLayout
method in your Action Controller, Magento will iterate over all the Blocks in the _blocks
array, using the value of the output attribute as a callback method. This is almost always toHtml, and means the starting point for output will be that Block’s Template.
The following sections will cover how Blocks are instantiated, how this layout file is generated, and finishes up with kicking off the output process.
Block Instantiation
So, within a Layout XML file, a <block /> or <reference /> has a “type” that’s actually a URI
<block type="page/html" ...
<block type="page/template_links"
The URI references a location in the (say it with me) global config. The first portion of the URI (in the above examples page
) will be used to query the global config to find the page class name. The second portion of the URI (in the two examples above, html
and template_links
) will be appended to the base class name to create the class Magento should instantiate.
We’ll go through page/html
as an example. First, Magento looks for the global config node at
/global/blocks/page
and finds
<page>
<class>
Mage_Page_Block
</class>
</page>
This gives us our base class name MagePageBlock
. Then, the second part of the URI (html
) is appended to the class name to give us our final Block class name MagePageBlock_Html
. This is the class that will be instantiated.
Blocks are one of the Grouped Class Names in Magento, all which share a similar instantiation method. This concept will be covered in more detail in a later chapter.
Difference between <block /> and <reference />
We mentioned that both <blocks /> and <refernces /> will instantiate Block classes, and you’re probably wondering what the difference is.
<reference />’s are used to replace existing Blocks in a layout file. For example, consider the following layout snippet.
<block type="page/html" name="root" output="toHtml" template="page/2columns-left.phtml">
<!-- ... sub blocks ... -->
</block>
<!-- ... -->
<reference name="root">
<block type="page/someothertype" name="root" template="path/to/some/other/template" />
<!-- ... sub blocks ... -->
</block>
</reference>
Magento initially creates a page/html
Block named root
. Then, when it later encounters the reference with the same name (root
), it will replace the original root <block /> with the <block /> enclosed in the <reference />.
This is what we’ve done in our local.xml file from above.
<layout version="0.1.0">
<default>
<reference name="root">
<block type="page/html" name="root" output="toHtml" template="simple_page.phtml" />
</reference>
</default>
</layout>
The Block named root
has been replaced with our Block, which points at a different phtml Template file.
Update: As mentioned in the comments and elsewhere, <reference /> blocks do not replace blocks. Instead, they’re used to add to or modify existing blocks. The above code samples and text are incorrect. What’s they’ve actually done is insert a new block named root into the existing root block, which creates an undefined state in the Magento layout (blocks are supposed to be uniquely named). The end results is that the root block is replaced, but relying on this behavior to remain consistent would be a bad idea.
How Layout Files are Generated
So, we have a slightly better understanding of what’s going on with the Layout XML, but where is this XML file coming from? To answer that question, we need to introduce two new concepts; Handles and the Package Layout.
Handles
Each page request in Magento will generate several unique Handles. The Layoutview module can show you these Handles by using a URL something like
http://example.com/helloworld/index/index?showLayout=handles
You should see a list similar to the following (depending on your configuration)
default
STORE_bare_us
THEME_frontend_default_default
helloworld_index_index
customer_logged_out
Each of these is a Handle. Handles are set in a variety of places within the Magento system. The two we want to pay attention to are default
and helloworld_index_index
. The default
Handle is present in every request into the Magento system. The helloworld_index_index
Handle is created by combining the frontName (helloworld), Action Controller name (index), and Action Controller Action Method (index) into a single string. This means each possible method on an Action Controller has a Handle associated with it.
Remember that “index” is the Magento default for both Action Controllers and Action Methods, so the following request
http://example.com/helloworld/?showLayout=handles
Will also produce a Handle named helloworld_index_index
Package Layout
You can think of the Package Layout similar to the global config. It’s a large XML file that contains every possible layout configuration for a particular Magento install. Let’s take a look at it using out Layoutview module
http://example.com/helloworld/index/index?showLayout=package
This may take a while to load. If your browser is choking on the XML rendering, try the text format
http://example.com/helloworld/index/index?showLayout=package&showLayoutFormat=text
You should see a very large XML file. This is the Package Layout. This XML file is created by combining the contents of all the XML layout files for the current theme (or package). For the default install, this is at
app/design/frontend/base/default/layout/
Behind the scenes there’s an <updates /> section of the global config that contain nodes with all the file names to load. Once the files listed in the config have been combined, Magento will merge in one last xml file, local.xml. This is the file where you’re able to add your customizations to your Magento install.
Combining Handles and The Package Layout
So, if you look at the Package Layout, you’ll see some familiar tags such as <block /> and <reference />, but they’re all surrounded by tags that look like
<default />
<catalogsearch_advanced_index />
etc...
These are all Handle tags. The Layout for an individual request is generated by grabbing all the sections of the Package Layout that match any Handles for the request. So, in our example above, our layout is being generated by grabbing tags from the following sections
<default />
<STORE_bare_us />
<THEME_frontend_default_default />
<helloworld_index_index />
<customer_logged_out />
There’s one additional tag you’ll need to be aware of in the Package Layout. The <update /> tag allows you to include another Handle’s tags. For example
<customer_account_index>
<!-- ... -->
<update handle="customer_account"/>
<!-- ... -->
</customer_account_index>
Is saying that requests with a customeraccountindex Handle should include <reference />s and <blocks />s from the <customer_account /> Handle.
Applying What We’ve Learned
OK, that’s a lot of theory. Lets get back to what we did earlier. Knowing what we know now, adding
<layout version="0.1.0">
<default>
<reference name="root">
<block type="page/html" name="root" output="toHtml" template="simple_page.phtml" />
</reference>
</default>
</layout>
to local.xml means we’ve overridden the “root” tag. with a different Block. By placing this in the <default /> Handle we’ve ensured that this override will happen for every page request in the system. That’s probably not what we want.
If you go to any other page in your Magento site, you’ll notice they’re either blank white, or have the same red background that your hello world page does. Let’s change your local.xml file so it only applies to the hello world page. We’ll do this by changing default to use the full action name handle (helloworldindexindex
).
<layout version="0.1.0">
<helloworld_index_index>
<reference name="root">
<block type="page/html" name="root" output="toHtml" template="simple_page.phtml" />
</reference>
</helloworld_index_index>
</layout>
Clear your Magento cache, and the rest of your pages should be restored.
Right now this only applies to our index Action Method. Let’s add it to the goodbye Action Method as well. In your Action Controller, modify the goodbye action so it looks like
public function goodbyeAction() {
$this->loadLayout();
$this->renderLayout();
}
If you load up the following URL, you’ll notice you’re still getting the default Magento layout.
http://example.com/helloworld/index/goodbye
We need to add a Handle for the full action name (helloworldindexgoodbye
) to our local.xml file. Rather than specify a new <reference />, lets use the update tag to match the helloworld_index_index
Handle.
<layout version="0.1.0">
<!-- ... -->
<helloworld_index_goodbye>
<update handle="helloworld_index_index" />
</helloworld_index_goodbye>
</layout>
Loading the following pages (after clearing your Magento cache) should now produce identical results.
http://example.com/helloworld/index/index
http://example.com/helloworld/index/goodbye
Starting Output and getChildHtml
In a standard configuration, output starts on the Block named root (because it has an output attribute). We’ve overridden root’s Template with our own
template="simple_page.phtml"
Templates are referenced from the root folder of the current or base theme. In this case, that’s
app/design/frontend/base/default/template
Normally, you’d add templates to either the default theme, or your own
app/design/frontend/default/default/template
app/design/frontend/default/custom/template
The base folder you added your template to is the final fall back for any template. If Magento doesn’t find a template in any of the themes, it will fall back to the base. However, as mentioned, you don’t want to add your own templates to this folder, as Magento updates will override them
Adding Content Blocks
A simple red page is pretty boring. Let’s add some content to this page. Change your <helloworldindexindex />
Handle in local.xml so it looks like the following
<helloworld_index_index>
<reference name="root">
<block type="page/html" name="root" template="simple_page.phtml">
<block type="customer/form_register" name="customer_form_register" template="customer/form/register.phtml"/>
</block>
</reference>
</helloworld_index_index>
We’re adding a new Block nested within our root. This is a Block that’s distributed with Magento, and will display a customer registration form. By nesting this Block within our root Block, we’ve made it available to be pulled into our simple_page.html
Template. Next, we’ll use the Block’s getChildHtml method to in our simple_page.phtml
file. Edit simple_page.html
so it looks like this
<body>
<?php echo $this->getChildHtml('customer_form_register'); ?>
</body>
Clear your Magento cache and reload the page and you should see the customer registration form on your red background. Magento also has a Block named top.links. Let’s try including that. Change your simple_page.html file so it reads
<body>
<h1>Links</h1>
<?php echo $this->getChildHtml('top.links'); ?>
</body>
When you reload the page, you’ll notice that your <h1>Links</h1> title is rendering, but nothing is rendering for top.links. That’s because we didn’t add it to local.xml. The getChildHtml
method can only include Blocks that are specified as sub-Blocks in the Layout. This allows Magento to only instantiate the Blocks it needs, and also allows you to set difference Templates for Blocks based on context.
Let’s add the top.links Block to our local.xml
<helloworld_index_index>
<reference name="root">
<block type="page/html" name="root" template="simple_page.phtml">
<block type="page/template_links" name="top.links"/>
<block type="customer/form_register" name="customer_form_register" template="customer/form/register.phtml"/>
</block>
</reference>
</helloworld_index_index>
Clear your cache and reload the page. You should now see the top.links module.
Wrapup
That covers Layout fundamentals. If you found it somewhat daunting, don’t worry, you’ll rarely need to work with layouts on such a fundamental level. Magento provides a number of pre-built layouts which can be modified and skinned to meet the needs of your store. Understanding how the entire Layout system works can be a great help when you’re those irritating Layout problems that are part and parcel of a Magento project.
Like this article? Then you’ll love Commerce Bug, the must have debugging extension for anyone using Magento. Whether you’re just starting out or you’re a seasoned pro, Commerce Bug will save you and your team hours everyday. Grab a copy and start working with Magento instead of against it.