event机制解析

Magento Events Explained and a few Gotchas avoided!

This post is about the Magento Event system – a full explanation of how it works and a couple of issues I had with it resolved. Hope it is a help for people wrestling with the Magento event dispatch mechanism.

My particular situation was this: when automatically fetching tracking details from our carriers via a Magento cron job, the resulting Google Checkout Magento event did not fire, so the end customer was not receiving the notification properly – even though the ‘shipment’ object within Magento was correctly displaying the tracking details.

So a great deal of debuggerying and I eventually found that it was because not all config ‘areas’ are loaded on all requests. Namely the events that are loaded when accessing the admin area are not the same as those that are loaded when cron runs. So the event that fires when you update a shipment from the admin has different config areas loaded than the one that fires if you update a shipment from some PHP code run during the cron process.

Before discussing the problem I had and how it was solved, let’s dig a little deeper into events in Magento – I’ll start with a summary of the event mechanism in general.

THE MAGENTO EVENT MECHANISM

The Magento event framework is really a great idea and I think it works well in general. It’s sort of like WordPress hooks in a lot of ways (it’s the same basic pattern) except that the binding is done within xml (reminds me a little of Spring in Java, before Guice came along and saved me from XML-Hell!)

Basically the event firing/dispatching process is made up of X steps.

THE MAGENTO CODE FIRES (DISPATCHES) AN EVENT

This will look something like this in the code:

Mage::dispatchEvent('admin_session_user_login_success', array('user'=>$user));

It is important to note though that the actual number of events being fired is way higher than just the number of times you see that code within Magento. Magento fires dispatches (old habits die hard, I’ll use both interchangeably from here on out) loads of implicit events too, for example here is a snippet from the base class of all Magento ObjectsMage_Core_Model_Abstract

In this snippet we see how every time a model is saved, two protected functions are called; _beforeSave and _afterSave. This is important because during these methods events are fired.

 public function save()
    {
        $this->_getResource()->beginTransaction();
        try {
            $this->_beforeSave();
            if ($this->_dataSaveAllowed) {
                $this->_getResource()->save($this);
                $this->_afterSave();
            }
            $this->_getResource()->commit();
        }
        catch (Exception $e){
            $this->_getResource()->rollBack();
            throw $e;
        }
        return $this;
    }

This snippet shows two events being fired. Notice that there is a generic event and then a dynamic one where the name of the event is built based on the object being saved. This allows you to actually listen for very explicit events like “after a shipment is saved” or “before a customer is saved” as well as general ones like “before object any saved”- the possibilities are limitless. I would like to experiment using the before load and after save to implement memcaching for Magento – anyone interested in collaborating should contact me.

The other thing to notice is that objects can be passed in to an event dispatch which then in turn get passed on to the listeners. The way to pass these is through an associative array. The keys become the names of the fields on the observer object – I’ll show you that later.

    protected function _beforeSave()
    {
        Mage::dispatchEvent('model_save_before', array('object'=>$this));
        Mage::dispatchEvent($this->_eventPrefix.'_save_before', array($this->_eventObject=>$this));
        return $this;
    }

You can see that each event is actually constructed based on the object being saved. So if we were saving a Mage_Catalog_Model_Product then the event will have:

 protected $_eventPrefix      = 'catalog_product';
 protected $_eventObject      = 'product';

So that is how we fire events, there are 100′s explicitly littered throughout the Magento core code and 1000′s more implicitly fired by actions like saving and loading objects. If you don’t believe me, try putting aMage::log() statment in the dispatchEvent function. You can fire events yourself in your own modules too, just use the dispatchEvent() function like the Magento developers do.

BINDING A FUNCTION TO AN EVENT

The next step is to actually configure Magento to call your function when a particular event is fired. This is handled in the config.xml file as shown in the snippet below – which in this example is listening toadmin_session_user_login_success. You can place this event block inside any of the config ‘areas’ – but it is important to think carefully about which one, as I will show you shortly, if you use admin or adminhtmlinstead of global – then you will restrict the circumstances under which your observer is bound to the event.

        <events>
	      <admin_session_user_login_success>
	        <observers>
	          <some_descriptive_phrase_for_your_listener>
	            <type>singleton</type>
	            <class>model_base/class_name</class>
	            <method>function_name</method>
	          </some_descriptive_phrase_for_your_listener>
	        </observers>
	      </admin_session_user_login_success>
    	</events>

IMPLEMENTING THE OBSERVER

Right so if you get this far you just need to actually implement the function that will get called, and correctly access any data that has been passed along within the event. That’s what I’ll show you in this next snippet.

 
public function salesOrderShipmentTrackSaveAfter(Varien_Event_Observer $observer) {
 
        $track = $observer->getEvent()->getTrack();
 
        $order = $track->getShipment()->getOrder();
 
        $order->getShippingMethod();
        // ...
    }

The $observer object get’s populated with variables that can be accessed via get’s and set’s courtesy of Magento’s use of the magic__get and __set – a feature which for me, the jury is still out on. The names of the variables are the keys of the associative array passed in when the event was fired (you were paying attention to that part right?) so the getTrack() function here can be called on the event because the array passed to the event looked like array('track'=>$track).

WHY IS MY OBSERVER NOT FIRING – CONFIG AREAS IN MAGENTO

Right so that’s my little intro to events and observers in Magento out of the way, now to my problem and how I solved it.

The problem was that my event was firing but my observer was not being called. It was being called if I saved the object through the browser, but not if I ran my test harness on the command line. Weird. So after a lot of rummaging through code I found the problem was because the area of the config that bound my observer to the event, was not being loaded when the code was called on the command line – but was when it was called from the Admin controller. So on investigating why I found this code in the base Adminhtml controller (Mage_Adminhtml_Controller_Action):

 
 public function preDispatch()
    {
        Mage::getDesign()->setArea('adminhtml')
            ->setPackageName((string)Mage::getConfig()->getNode('stores/admin/design/package/name'))
            ->setTheme((string)Mage::getConfig()->getNode('stores/admin/design/theme/default'));
 
        $this->getLayout()->setArea('adminhtml');
 
        Mage::dispatchEvent('adminhtml_controller_action_predispatch_start', array());
        parent::preDispatch();

And of course in the parent I find:

loadAreaPart('adminhtml', Mage_Core_Model_App_Area::PART_EVENTS);
Mage::app()->loadAreaPart(Mage_Core_Model_App_Area::AREA_GLOBAL,
                                                        Mage_Core_Model_App_Area::PART_EVENTS);
Mage::app()->loadAreaPart(Mage_Core_Model_App_Area::AREA_FRONTEND,
                                                        Mage_Core_Model_App_Area::PART_EVENTS);
Mage::app()->loadAreaPart(Mage_Core_Model_App_Area::AREA_ADMIN,
                                                        Mage_Core_Model_App_Area::PART_EVENTS);
//Load all parts of the config
Mage::app()->loadArea('adminhtml');
Mage::app()->loadArea(Mage_Core_Model_App_Area::AREA_FRONTEND);
// etc...

Note: I don’t know why there is no constant for adminhtml in Magento – but there isn’t at the moment.

Anyway to conclude I hope this little explanation of Magento events has been helpful to some of you, and if you have to run any commandline/cron Magento code I hope this helps you to ensure you are loading the right config file areas/parts. If anyone is interested in trying to use the events to deal with some basic memcaching of objects in a bid to speed up magento, please let me know.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值