- Magento Front Controller
- Reinstalling Magento Modules
- Clearing the Magento Cache
- Magento’s Class Instantiation Abstraction and Autoload
- Magento Development Environment
- Logging Magento’s Controller Dispatch
- Magento Configuration Lint
- Slides from Magento Developer’s Paradise
- Generated Magento Model Code
- Magento Knowledge Base
- Magento Connect Role Directories
- Magento Base Directories
- PHP Error Handling and Magento Developer Mode
- Magento Compiler Mode
- Magento: Standard OOP Still Applies
- Magento: Debugging with Varien Object
- Generating Google Sitemaps in Magento
- IE9 fix for Magento
- Magento’s Many 404 Pages
- Magento Quickies
- Commerce Bug in Magento CE 1.6
- Welcome to Magento: Pre-Innovate
- Magento’s Global Variable Design Patterns
- Magento 2: Factory Pattern and Class Rewrites
- Magento Block Lifecycle Methods
- Goodnight and Goodluck
- Magento Attribute Migration Generator
- Fixing Magento Flat Collections with Chaos
- Pulse Storm Launcher in Magento Connect
- StackExchange and the Year of the Site Builder
- Scaling Magento at Copious
- Incremental Migration Scripts in Magento
- A Better Magento 404 Page
- Anatomy of the Magento PHP 5.4 Patch
- Validating a Magento Connect Extension
- Magento Cross Area Sessions
- Review of Grokking Magento
- Imagine 2014: Magento 1.9 Infinite Theme Fallback
- Magento Ultimate Module Creator Review
- Magento Imagine 2014: Parent/Child Themes
- Early Magento Session Instantiation is Harmful
- Using Squid for Local Hostnames on iPads
- Magento, Varnish, and Turpentine
There’s two design patterns used in PHP systems that are often conflated. First, there’s the event/observer pattern. This pattern allows a system-developer to issue global events which client-developers may then setup listener object/methods/functions for. These listeners observe the event, and then perform some action. This keeps client code out of system code, and the OOP gods are happy.
The second pattern is often called, confusingly, the event listener pattern. I’ve also seen it called the callback pattern. This pattern is used when a client-developer is defining classes which extend base classes provided by the system developers. The classic example is an ActiveRecord/CRUD model. Imagine a simple ORM where you define a model, something like this
class Car extends BaseModel
{
}
Most ORMs provide an afterSave
method of some kind
class Car extends BaseModel
{
protected function _afterSave()
{
}
}
This theoretical _afterSave
method is the event listener. It’s conceptually similar to the event/observer pattern, but its context and implementation is very different. If we’re talking classic OOP design patterns, this is the template method pattern (thanks Stack Overflow).
In my own out I’ve often called these sorts of methods “lifecycle callbacks”, because they’re template methods that relate directly to certain stages in a block’s life. That’s what we’ll be calling them for the remainder of this article.
Magento’s Lifecycle Callbacks
Magento is filled with all sorts of these callbacks. For example, Magento’s CRUD/EAV models have their own version of the before/after save methods common in most ORMs. Another place you see the pattern is a controller’s preDispatch
method. A third system that’s littered with these calls is the layout/block system. Magento’s unique approach to layout generation means developers often have trouble understanding all the callback methods available to them. Today we’ll be reviewing these methods: What they do, how they’re implemented, and how some will behave in unexpected ways.
While not required reading, owners of No Frills Magento Layout will feel right at home.
The Six Block Lifecycle Callbacks
To start we’re going to briefly describe each of the methods we’re singling out as lifecycle callbacks. Definitions get fuzzy here because while a client-developer might describe these methods as listeners, to a systems developer they’re just solid OOP. Any method is a potential “callback” when you’re intimately familiar with the base class.
Callback: _construct()
The _construct
method is Magento’s internal (or “pseudo”) constructor. All objects that inherit from Varien_Object
(which includes blocks) have this callback, which is called when you instantiate a block. This method isn’t specific to blocks, but its behavior is close enough to warrant its inclusion in our lifecycle callback lineup.
Callback: _prepareLayout()
The _prepareLayout()
method is called immediately after a block has been added to the layout object for the first time. If this seems vague don’t worry, we’ll get to some specifics in a moment.
Callback: _beforeToHtml()
The _beforeToHtml()
method is called immediately before a block’s HTML content is rendered.
Callback: _afterToHtml($html)
The _afterToHtml
method is called immediately after a block’s HTML content is generated. It’s also the only method with both a method parameter and a required return value. Whatever content is returned from your _afterToHtml
method call will become the rendered content for that block. So the following
class Packagename_Namespace_Block_Model extends Mage_Core_Block_Template
{
protected function _afterToHtml($html)
{
return $html . '<div>Killroy was here</div>';
}
}
would automatically add the text
<div>Killroy was here</div>
to the end of your block when it rendered. However, failure to return a value
class Packagename_Namespace_Block_Model extends Mage_Core_Block_Template
{
protected function _afterToHtml($html)
{
Mage::Log("I just rendered " . $this->getName());
}
}
would result in an empty block being rendered. Make sure you don’t forget your return value if you’re using _afterToHtml
.
Callback: _toHtml()
The _toHtml
method is another questionable inclusion in the lifecycle lineup. The _toHtml
method is where you put the code that should render your block. You’ll see a little later why we’re including this as a lifecycle callback.
Callback: _beforeChildToHtml($name, $block)
Finally, there’s the little known _beforeChildToHtml
method. In Magento, a layout is a nested tree structure of blocks, with parent blocks rendering child blocks. When a parent renders one of its children (through a call to $this->getChildHtml('name')
), this method is called immediately before rendering the child.
This method isn’t in use anywhere in core Magento code, so it’s likely one of those legacy methods they’re keeping around for backward compatibility. You’ll probably never encounter it, but it’s worth knowing about.
Creating a Block
Before we jump into the implementation details, we’re going to take a look at the ultimate lifecycle callback, and one we didn’t mention above: PHP’s constructor method. In you’re brand new to PHP, whenever you instantiate an object from a class, that class’s __construct
method is called.
$o = new SomeObject(1,2,3);
class SomeObject
{
public function __construct($parm,$second,$etc)
{
}
}
PHP classes also have an older, but still supported, feature where a method with the same name as the class will be used as the constructor.
$o = new SomeObject(1,2,3);
class SomeObject
{
//still a constructor
public function SomeObject($parm,$second,$etc)
{
}
}
We mention this mainly in the interest of completeness, as the __construct
format is the preferred technique for defining a PHP constructor, and the “same name” functionality has been dropped from code using namespaces.
If you examine at the definition of an abstract block class (the class all proper blocks inherit from)
#File: app/code/core/Mage/Core/Block/Abstract.php
abstract class Mage_Core_Block_Abstract extends Varien_Object
{
//...
}
you’ll see there’s no constructor (of either style) defined. This abstract class extends the base Varien_Object
class, so we should inspect that class for a constructor, as PHP will climb the ancestry chain until it finds one to call.
#File: lib/Varien/Object.php
class Varien_Object implements ArrayAccess
{
//...
public function __construct()
{
$this->_initOldFieldsMap();
if ($this->_oldFieldsMap) {
$this->_prepareSyncFieldsMap();
}
$args = func_get_args();
if (empty($args[0])) {
$args[0] = array();
}
$this->_data = $args[0];
$this->_addFullNames();
$this->_construct();
}
//...
}
This is the constructor that’s shared by all objects that inherit from Varien_Object
. It’s also the home of the first implemented Magento lifecycle callback, the _construct
method.
By placing the following code at the end of the PHP constructor
$this->_construct();
Magento gives client developers the ability to define a _construct
method in their own classes.
class Packagename_Namespace_Block_Model extends Mage_Core_Block_Abstract
{
public function _construct()
{
//do your own constructor stuff here
}
}
Why create a separate system for constructors instead of relying on the built in method? There’s lots of reasons system developers like doing this, but most of them boil down to control.
By providing an internal _construct
method, the core team is giving client developers the same functionality as PHP’s constructor while simultaneously claiming the native PHP constructor for themselves.
Consider a block like this, which is (incorrectly) using PHP’s constructor for some block initialization
class Packagename_Namespace_Block_Model extends Mage_Core_Block_Abstract
{
public function __construct()
{
$this->setTemplate('path/to/the.phtml');
}
}
See anything wrong? No? Well, for one there’s no call to the parent constructor, meaning the Varien_Object
code never runs. Failure to call, or realize you’re supposed to call, the parent constructor is common among users of PHP frameworks. When this happens, you end up with a block object that behaves slightly differently than a standard block object that’s had its Varien_Object::__construct
code called. This is a “bad thing” from a system developer’s point of view, and can result in inconsistent/confusing system behavior.
Even if an OOP savvy developer did call it
public function __construct()
{
parent::__construct();
$this->setTemplate('path/to/the.phtml');
}
there’s still a problem. The client developer has created a constructor prototype and parent call with no arguments. This is perfectly valid PHP, but if we look at the factory instantiation code for a block
#File: app/code/core/Mage/Core/Model/Layout.php
$block = new $block($attributes);
we see that Magento’s system code passes in an argument to the constructor. With the __construct
using block above, those arguments don’t get passed through. However, the Varien_Object
constructor expects those arguments
#File: lib/Varien/Object.php
public function __construct()
{
//...snip...
$args = func_get_args();
if (empty($args[0])) {
$args[0] = array();
}
//...snip...
}
//...
So, again, we have a block that behaves differently than other system blocks, hurting system consistency. By creating their own internal _construct
for users, the core team avoids this entire problem while still giving developers 99% of what they need from a constructor.
When is a Block Created
Above we briefly discussed the code Magento’s block factory method uses. Next we’re going to dive a bit deeper into the createBlock
method, and how it ties into the _prepareLayout
lifecycle callback.
To start, let’s review the various ways a block might be instantiated. The first form, and one that’s discouraged, would be by direct class instantiation
$block = new Packagename_Modulename_Block_Foo;
Here we’re using basic PHP to create an instance of a block.
A more knowledgeable developer might try something like this.
$class = Mage::getConfig()->getBlockClassName('groupname/foo');
$block = new $class;
Here we’re using the class alias groupname/foo
to lookup the proper class name, and then instantiating that.
A third, and recommended, method is to use the layout object’s createBlock
factory method
$layout = Mage::getSingleton('core/layout');
$block = $layout->createBlock('groupname/foo');
//in a controller action context (or other class with a getLayout method)
$block = $this->getLayout()->createBlock('groupname/foo');
In addition to ensuring instantiation passes through Magento’s class alias/rewrite system, the createBlock
method also ensures that a block is associated with the layout. (Yes, that’s vague. Patience Luke!)
The fourth method of creating a block is via layout update XML. When the Layout generating system encounters code like this
<block type="groupname/foo" name="baz" />
a block will be immediately instantiated via the createBlock
method, so the above is roughly equivalent to
$layout = Mage::getSingleton('core/layout');
$block = $layout->createBlock('groupname/foo','baz');
meaning layout update XML created blocks also pass through the createBlock
method.
Neighborhood Block Association
Earlier we said blocks created by the createBlock
method are “associated” with the layout. Here’s what associated meant. If you look at this small slice of the createBlock
method in the layout object
#File: app/code/core/Mage/Core/Model/Layout.php
public function createBlock($type, $name='', array $attributes = array())
{
//...
//$block is the instantiated block object at this point
$block->setLayout($this);
//...
}
you can see that after Magento instantiates a block we’re calling that block’s setLayout
method and passing in a reference to the layout object ($this
). This is not a magic set
method, so lets take a look at its definition in setLayout
#File: app/code/core/Mage/Core/Block/Abstract.php
public function setLayout(Mage_Core_Model_Layout $layout)
{
$this->_layout = $layout;
Mage::dispatchEvent('core_block_abstract_prepare_layout_before', array('block' => $this));
$this->_prepareLayout();
Mage::dispatchEvent('core_block_abstract_prepare_layout_after', array('block' => $this));
return $this;
}
After this method has done the job of setting the layout object
#File: app/code/core/Mage/Core/Block/Abstract.php
$this->_layout = $layout;
we can see it also makes a call to _prepareLayout
#File: app/code/core/Mage/Core/Block/Abstract.php
$this->_prepareLayout();
Ah ha! There’s our next lifecycle callback. The _prepareLayout
method allows a client-developer to hook into the point in time when a block is being added to a layout
class Packagename_Namespace_Block_Model extends Mage_Core_Block_Abstract
{
protected function _prepareLayout()
{
//do stuff after you've been added to the layout
}
}
While this happens at a stage of the block’s lifecycle that’s very close to the _construct
method, it only happens for block objects that have been added to the layout. If a block’s instantiated in a way that doesn’t involve createBlock
or setLayout
, this lifecycle callback will not be called, while the _construct
method will always be called.
This means it’s safe to assume to existence of a layout object in your _prepareLayout
method, and if your block needs to manipulate other blocks to achieve its ends you can safely call methods on the layout object. If you tried this in _construct
, someone using the block outside of a layout (rare, but it does happen), might run into fatal errors.
One last thing to note before we move on. Notice the call to _prepareLayout
is nestled in-between two calls to Mage::dispatch
#File: app/code/core/Mage/Core/Block/Abstract.php
Mage::dispatchEvent('core_block_abstract_prepare_layout_before', array('block' => $this));
$this->_prepareLayout();
Mage::dispatchEvent('core_block_abstract_prepare_layout_after', array('block' => $this));
The dispatchEvent
method is part of the event/observer pattern. We mention it mainly so you’re aware that your own _prepareLayout
code will be called after the core_block_abstract_prepare_layout_before
global system observers have had a chance to muck about with the block, and that further changes can be made to the block with the core_block_abstract_prepare_layout_after
global system observers. If your code in _prepareLayout
isn’t working as you expect, make sure there’s not a global listener that’s silently confusing you.
Rendering HTML
So far we’ve covered lifecycle callbacks that are triggered during block instantiation. There are no further callbacks until its time to render a block’s html. The Magento system code renders block by calling the toHtml
method.
This toHtml
method is, and always will be, defined on the base abstract block class
#File: app/code/core/Mage/Core/Block/Abstract.php
final public function toHtml()
{
...
}
When we say always will be, we mean it. Notice this method is defined with the final
keyword. This means no child class is allowed to redefine its own toHtml
method. This is another example of system developers ensuring they own a certain section of the system, and that the code in the toHtml
method will always be called.
We’re not going to go completely into how a block is rendered. Instead, we’ll want to pay attention to the following code slice in the toHtml
method.
#File: app/code/core/Mage/Core/Block/Abstract.php
$html = $this->_loadCache();
if ($html === false) {
//...snip...
$this->_beforeToHtml();
$html = $this->_toHtml();
$this->_saveCache($html);
//...snip...
}
$html = $this->_afterToHtml($html);
The method that actually does the work (creating the PHP string) of rendering a block is the _toHtml
method. Notice the leading underscore. That’s what the $html = $this->_toHtml();
is all about.
Earlier we identified _toHtml
as a lifecycle callback, although it’s a callback in same way a model object’s save
method is a callback. Your custom block can tap into it, but unless you’re creating a new block type you’ll almost always want to call the parent method, as that’s what does the actual rendering
public function _toHtml()
{
$html = parent::_toHtml();
//more stuff here
return $html;
}
You can also see the implementation of the _beforeToHtml
callback, made immediately before the _toHtml
method.
#File: app/code/core/Mage/Core/Block/Abstract.php
$this->_beforeToHtml();
$html = $this->_toHtml();
More interesting though it the entire conditional block surrounding these two method calls
#File: app/code/core/Mage/Core/Block/Abstract.php
$html = $this->_loadCache();
if ($html === false) {
...
$this->_beforeToHtml();
$html = $this->_toHtml();
}
Prior to rendering the layout, Magento checks the cache for any rendered output. If Magento loads an entry from the cache (that is, if _loadCache
returns something other than boolean false
), both the _toHtml
and _beforeToHtml
calls are skipped. This can trip people up who want their block to do something extra on every page and unwittingly put their code in a _beforeToHtml
method.
Contrast this with the implementation of the _afterToHtml()
method.
#File: app/code/core/Mage/Core/Block/Abstract.php
$html = $this->_loadCache();
if ($html === false) {
//...
}
$html = $this->_afterToHtml($html);
This happens outside the cache checking block. This means if a block is being actively rendered, both the _beforeToHtml
and _afterToHtml
methods will be called but if a block’s content is being pulled from cache, only the _afterToHtml
method will be called.
Global Event Equivalents
Just as with the _prepareLayout
method, the before and after html rendering methods have analogous global system events
core_block_abstract_to_html_before
core_block_abstract_to_html_after
Unlike the lifecycle callbacks, these events will fire regardless of the cache state of a block. So, if you were trying to use _beforeToHtml
but its cache behavior was problematic, setting a listener for core_block_abstract_to_html_before
should get you where you need to go.
Rendering Child Blocks
Our final lifecycle callback requires a quick layout/block primer. As we said previously, a Magento layout object is a nested tree of block objects. That is, an individual block in the layout may have multiple child block. This individual block is the parent block, and is responsible for rendering the child blocks. The call you make to render a child block looks like this
$this->getChildHtml(); //renders all children
$this->getChildHtml('block_name') //renders the block_name block
The _beforeChildToHtml
method is one you define on the parent block, and it will be passed the name and instance of every child block it renders.
This seems straight forward enough, but there’s a few quirks to be aware of. The best way to understand these quirks is to examine the getChildHtml
implementation in full. If we take a look at the method definition
#File: app/code/core/Mage/Core/Block/Abstract.php
public function getChildHtml($name = '', $useCache = true, $sorted = false)
{
if ($name === '') {
if ($sorted) {
$children = array();
foreach ($this->getSortedChildren() as $childName) {
$children[$childName] = $this->getLayout()->getBlock($childName);
}
} else {
$children = $this->getChild();
}
$out = '';
foreach ($children as $child) {
$out .= $this->_getChildHtml($child->getBlockAlias(), $useCache);
}
return $out;
} else {
return $this->_getChildHtml($name, $useCache);
}
}
we see there’s two top level code paths. The first is for a call with no $name
parameter, which renders all the children. The second is for a call with a name parameter. However, both leafs lead to a call to _getChildHtml
$this->_getChildHtml($name, $useCache);
$out .= $this->_getChildHtml($child->getBlockAlias(), $useCache);
And here’s the method definition for _getChildHtml
#File: app/code/core/Mage/Core/Block/Abstract.php
protected function _getChildHtml($name, $useCache = true)
{
if ($useCache && isset($this->_childrenHtmlCache[$name])) {
return $this->_childrenHtmlCache[$name];
}
$child = $this->getChild($name);
if (!$child) {
$html = '';
} else {
$this->_beforeChildToHtml($name, $child);
$html = $child->toHtml();
}
$this->_childrenHtmlCache[$name] = $html;
return $html;
}
The first quirk you’ll want to be aware of is the $useCache
parameter. You might think a client developer uses this to control whether or not a call loads at item from Magento’s global block output cache, or generates the block anew. However, this is not what this parameter is for.
Instead, every block keeps a cache of output in the object property _childrenHtmlCache
. This is a per-http-request cache. If the $useCache
parameter is true, Magento will check this internal property for an already generated output block before calling the toHtml
method on the child block
#File: app/code/core/Mage/Core/Block/Abstract.php
if ($useCache && isset($this->_childrenHtmlCache[$name])) {
return $this->_childrenHtmlCache[$name];
}
Assuming the output was not found in the cache array, Magento will fetch the specified child from the layout.
#File: app/code/core/Mage/Core/Block/Abstract.php
$child = $this->getChild($name);
and then, if there’s a child with the specified name, Magento will render the block. Otherwise, it will set the HTML output to a blank string
#File: app/code/core/Mage/Core/Block/Abstract.php
if (!$child) {
$html = '';
} else {
$this->_beforeChildToHtml($name, $child);
$html = $child->toHtml();
}
Finally, the generated output will be returned right after Magento stashes the output in the aforementioned _childrenHtmlCache
cache.
#File: app/code/core/Mage/Core/Block/Abstract.php
$this->_childrenHtmlCache[$name] = $html;
return $html;
This implementation leads to a few quirks. First, as the $useCache
method defaults to true, _beforeChildToHtml
method will only be called the first time a specific individual parent block renders a child block. If the parent block calls getChildHtml
again with the same block _beforeChildToHtml
won’t be called. However, if a different parent block calls getChildHtml
using the same block name (possible if someone used unsetChild
to move a block), _beforeChildToHtml
will be called a second time.
Another quirk worth noting is that unlike the other lifecycle callbacks, there’s no global Magento event equivalent for _beforeChildToHtml
or an _after
method. These omissions, plus the core system code not using this method at all lead me to believe this callback — while still technically supported — is an early concept that the core team has since abandoned. Unless there’s no other way to implement your feature, I’d stay away from this particular callback.
Wrap Up
Lifecycle callback “listeners” can be a useful tool for client developers. However, as you can see, when these callback methods become part of the core system code, it becomes tempting for the system-developers to tweak their implementation is ways that serve their own needs, but aren’t always immediately intuitive to those same client developers.
Because this callback pattern was an early part of PHP 4 MVC systems, there’s a historical cultural expectation that any new system will have similar callbacks. However, as PHP 5 continues its evolution and object oriented PHP becomes the de-facto professional standard, the drawbacks of in-object listener methods start to outweigh the benefits. If you and your team are capable of understanding and debugging the observer/listener patterns available in Magento, I’d consider sticking to them exclusively. They’re the better, more stable choice for implementing additional functionality in your modules.