Here’s a fun little heisenbug I had to deal with. I was working in a Magento block observer — specifically the core_block_abstract_to_html_after
event. If the block was a specific block, I wanted to change its output slightly.
$transport = $observer->getTransport();
$block = $observer->getBlock();
if($block->getNameInLayout() != 'foo')
{
return;
}
$new_output = Mage::getSingleton('core/layout')->createBlock('group/class')
->setTemplate('foo/baz/bar.html')
$new_html = str_replace('to find', $new_output , $transport->getHtml());
$transport->setHtml($new_html);
Pretty standard stuff, if a little simplified for the sake of pseudo code. This sort of transformation is often necessary when a particular Magento block/template doesn’t have a method to rewrite, and the block template is complex enough that you don’t feel comfortable creating your own version of the template.
When Magento fires core_block_abstract_to_html_after
, it sends along the transport object specifically so you can do stuff like this. Here’s the spot in the code where that happens.
#File: app/code/core/Mage/Core/Block/Abstract.php
self::$_transportObject->setHtml($html);
Mage::dispatchEvent('core_block_abstract_to_html_after',
array('block' => $this, 'transport' => self::$_transportObject));
However, for reasons that weren’t obvious to me, instead of replacing the to find
string, this observer changed the entire output of the block to the output of the group/class
block, but with none of the original block’s content.
Can you spot my bad assumption? Take a look at the transport object
#File: app/code/core/Mage/Core/Block/Abstract.php
'transport' => self::$_transportObject
It’s a static property of the abstract block class —
#File: app/code/core/Mage/Core/Block/Abstract.php
private static $_transportObject;
This means every block uses the same transport object. Which means if you render a block inside core_block_abstract_to_html_after
, the transport in your observer no longer contains the html for block you’re observing, it contains the html for the block you just rendered.
Once I realized this, the problem was easy enough to solve — just extract the HTML before rendering the new block
//get the HTML
$old_html = $transport->getHtml();
//render the block
$new_output = Mage::getSingleton('core/layout')->createBlock('group/class')
->setTemplate('foo/baz/bar.html')
//teh transport now contains html for the group/class block
//which doesn't matter, because we already extracted the HTML into a
//string primitive variable
$new_html = str_replace('to find', $new_output , $old_html);
$transport->setHtml($new_html);