10

In my observer function, I get a variable passed by the event like that:

public function observerFunc(Varien_Event_Observer $observer)
{
    $sth = $observer->getEvent()->getSth();
}

If sth is an object, I can alter it by calling methods on it. But how can I alter sth if it is a simple string? I tried the following without success:

public function observerFunc(Varien_Event_Observer $observer)
{
    $sth = $observer->getEvent()->getSth();
    $observer->getEvent()->setSth('test');
    $observer->setSth('test');
}

I just learned that some events also pass a transport object in which the string can be altered (thanks Alex), but the event page_block_html_topmenu_gethtml_after does not. So what can I do?

The event in question gets dispatched like this and I want to alter $html:

$html = $this->_getHtml($this->_menu, $childrenWrapClass);
Mage::dispatchEvent('page_block_html_topmenu_gethtml_after', array(
    'menu' => $this->_menu,
    'html' => $html
));
12

You can't.

The reason the transport object approach works is PHP's objects are aliases/references. When you modify an object, you're modifying the One True Object.

PHP's primitive types (ints, strings, booleans, etc.) are not objects, and fall under PHP's pass by value rules for arguments. If a Magento module developer passes you a raw string in an event observer

    Mage::dispatchEvent('page_block_html_topmenu_gethtml_after', array(
        'menu' => $this->_menu,
        'html' => $html
    ));

that's their way of saying

You can look at this value, but I don't want you modifying it.

We'll leave whether this is a deliberate design decision or a developer not thinking things through as an exercise for the reader.

As to your unasked question, if you want to modify the top menu, there's a few approaches I'd take. Hooking into the page_block_html_topmenu_gethtml_before event and modifying the menu object

    Mage::dispatchEvent('page_block_html_topmenu_gethtml_before', array(
        'menu' => $this->_menu
    ));

should work, since _menu is an object

/**
 * Top menu data tree
 *
 * @var Varien_Data_Tree_Node
 */
protected $_menu;

Secondly, you can rewrite the menu generating class

public function getHtml($outermostClass = '', $childrenWrapClass = '')
{
    $html = parent::getHtml($outermostClass, $childrenWrapClass);
    //monkey with $html here to add your menu items or custom markup
    return $html;
}

Third, you could use layout updates to remove the existing top menu block, and insert a new block with a custom class you've created. You custom class would extend the existing top menu class, and then redefine getHtml. This is more complicated, but avoids the problems associated with rewrites.

5

I would say that is a design bug in that event.

Objects get passed around by reference, so they can be manipulated. Strings always get copied. So in this case the string can not be manipulated inside the observer, even the page_block_html_topmenu_gethtml_after event just looks to me like its purpose is to give you a chance to manipulate the $html.

3

It is possible to modify block output via transported string by observing the core_block_abstract_to_html_after event (link). In this event, the rendered content is transported from the block instance to the observer instance, and - most importantly - the transported content is what is returned by the block class. Note that there's an important caching consideration which I've explained below the example.

Example

Because this event is fired for every block render, you should configure the observer as a singleton and test that the block type is an instance of Mage_Page_Block_Html_Topmenu.

public function manipulateTopmenuOutput(Varien_Event_Observer $obs)
{
     if ($obs->getBlock() instanceof Mage_Page_Block_Html_Topmenu){
         $initialOutput = $obs->getTransport()->getHtml();
         //e.g. $modified output = $this->yourManipulationMethod($initialOutput);
         $obs->getTransport()->setHtml($modifiedOutput);
     }
}

Your manipulation logic could be implemented in the observing method or placed in another method in the observer.

Issues

Because it involves output manipulation and the observer is called for all block renders, this should only be used when the primary concern is avoiding a block rewrite. Also, the generated content in this observer is manipulated post-block_html cache write (via the block instance's call to _saveCache()), so you would either need to re-cache the block_html entry in the observer (a bit sticky, as you'd either be using Reflection or duplicating the logic from both the _saveCache() and _getSidPlaceholder() methods to write the cache entry. And finally, if you need to manipulate anything relating to the tree node data, you would have to generate a copy of the tree node data. This could theoretically be done by grabbing the Mage_Catalog_Model_Observer singleton and grabbing the tree from it... very sticky indeed.

  • 1
    I hate Magento's implementation of the TopMenu with the very essence of my soul. I routinely bump my head against it during any implementation that requires navigation customization. They've made it super-difficult to tweak that HTML output in an unobtrusive manner; Magento fights you every step of the way. – wlvrn Sep 18 '13 at 22:05
  • Well, yes, the menu is inappropriately inflexible, but you do get quite a bit of functionality which works. – benmarks Sep 19 '13 at 0:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.