Adding event observer on the fly

It’s quite easy to add event observer in Magento with the use of config files (see Magento events on_create and on_update). However it might be desired to add observers during runtime and this requires a bit more work.

Magento utilises publish/subscribe pattern with the use of a message broker storing observers data in the Mage_Core_Model_App::_events array. This array is divided into areas (global, frontend, admin and crontab by default), each area might contain a number of listened events and each of these event might have assigned multiple observers. All system events call Mage::dispatchEvent() method which delegates the execution to Mage_Core_Model_App::dispatchEvent(). Then the event is searched in Mage_Core_Model_App::_events array and if there are any observers assigned to this event appropriate methods are executed.

There is another message broker which has the same events populated from config files – it is stored as Varien_Event_Collection in Mage::_events property. It is very easy to add event observer to this collection (by using Mage::addObserver() method) and dispatch event with the use of Mage::getEvents()->dispatch(). The drawback of this solution is that this message broker must store observer class instance in order to call dispatched method (as opposite to Mage_Core_Model_App which can instantiate observer class during event dispatch). I guess this caused memory problems thus Magento favours Mage_Core_Model_App::_events broker. Although you can use Mage::_events to store and dispatch events in your custom modules bear in mind that this approach is less memory friendly than using Mage_Core_Model_App::_events.

So how to add new observers to the app message broker? Bad news first – it is not possible without rewriting core class. More precisely you can add observers to new events but not to existing ones. For example you can observe new ‘my_custom_trigger’ event but not add new observer for ‘catalog_product_save_after’. The Mage_Core_Model_App::dispatchEvent() is constructed to use protected Mage_Core_Model_App::_events for which there’s no public setter (it is initialised from config files). Having no other choice we’ve got to copy the app/code/core/Mage/Core/Model/App.php file into app/code/local/Mage/Core/Model directory. Although it is possible to add event observer data directly into the _events array, I’ll go step further by adding data to configuration object and reloading events config thus the new observer will be visible just like it was added from configuration file. To do this add the following methods to the app/code/local/Mage/Core/Model/App.php:

    /**
     * Adds new observer for specified event
     *
     * @param string $area (global|admin...)
     * @param string $eventName name of the event to observe
     * @param string $obsName name of the observer (as specified in config.xml)
     * @param string $type (model|singleton)
     * @param string $class identifier of the observing model class
     * @param string $method name of the method to call
     */
    public function addEventObserver($area, $eventName, $obsName, $type=null, $class=null, $method=null)
    {
    	$eventConfig = new Varien_Simplexml_Config();
    	$eventConfig->loadDom($this->_getConfigDom($eventName, $obsName, $type, $class, $method));
    	Mage::getConfig()->extend($eventConfig, true);
        $this->_events[$area][$eventName] = null;
    }
 
    /**
     * Prepares event DOM node used for updating configuration
     * 
     * @param string $eventName
     * @param string $obsName
     * @param string $type
     * @param string $class
     * @param string $method
     * @return DOMDocument 
     */
    protected function _getConfigDom($eventName, $obsName, $type=null, $class=null, $method=null)
    {
        $dom = new DOMDocument("1.0");
        $config = $dom->createElement("config");
        $observers = $config->appendChild($dom->createElement('global'))
               ->appendChild($dom->createElement("events"))
               ->appendChild($dom->createElement($eventName))
               ->appendChild($dom->createElement("observers"));
        $observer = $dom->createElement($obsName);
        if ($class) {
            if ($method) {
                if ($type) {
                    $observer->appendChild($dom->createElement('type', $type));
                }
                $observer->appendChild($dom->createElement('class', $class));
                $observer->appendChild($dom->createElement('method', $method));
            }
        }
        $observers->appendChild($observer);
        $dom->appendChild($config);
        return $dom;
    }

After calling Mage::app()->addEventObserver() Magento will listen to specified event and when it occurs will run newly added observer.

4 thoughts on “Adding event observer on the fly”

Comments are closed.