Categories
Magento

Magento Model Events Explained

Events are an important set of features a Magento code must know. Events are not Magento specific: Drupal, WordPress and many others systems use events to let plugins and modules and extensions to interact with the code without changing it.

Magento suffers of bad coders’ habits which rewrite or replace core classes making hard to keep the modules compatible each others. When possible modules should use events as first choice.

To create a real use case, I’ll know try to intercept the order status history change, without rewrite the order status model. Of course, using an event.

Model and CRUD events

Magento has a base class, Mage_Core_Model_Abstract, which implements the basic methods to make the model able to interact with the database (in an abstracted fashion). Methods are: save, load, delete and so on.

Looking at the code of Mage_Core_Model_Abstract, you can see that each CRUD (Create, Read, Update, Delete) operation fires an event, before and after the single action. For example, the save method calls:

$this->_beforeSave();
[...]
$this->_afterSave();

The methods _beforeSave() and _afterSave() do some setup and cleanup, but we are interested in the events fired:

protected function _beforeSave()
    {
        if (!$this->getId()) {
            $this->isObjectNew(true);
        }
        Mage::dispatchEvent('model_save_before', array('object'=>$this));
        Mage::dispatchEvent($this->_eventPrefix.'_save_before', $this->_getEventData());
        return $this;
    }

    protected function _afterSave()
    {
        $this->cleanModelCache();
        Mage::dispatchEvent('model_save_after', array('object'=>$this));
        Mage::dispatchEvent($this->_eventPrefix.'_save_after', $this->_getEventData());
        return $this;
    }

so every time a Magento model is saved, you can intercept it listening (observing) the event model_save_before (and model_save_after) which carries as data a simple associative array where under the key “object” there is the model instance itself.

As you can see there is even a more specific event fired, which uses a “prefix”. In Magento most of the core models can redefine the internal variable “_eventPrefix”, so one can observe only model specific CRUD operations. Attention: not all Magento core models support that event format (for example the order status totally ignore it… shame on them, they could have used the class name as prefix).

To find out the prefix for Magento model, you can check the documentation or the model source. For example, the order model uses as prefix “sales_order” (actually its module name plus its model name, at least this is coherent…).

Observing the order status CRUD events

The order status history model does not fire specific events but it should: order status changes are useful to notify external systems, for example. So how can we observer them?

Simple, we must use the general model CRUD events and check the model which is firing it.

So, in our module (sources available at the end of this article) we define an observer for the event “model_save_before” (and maybe for the event “model_save_after, if needed).

<global>
  <models>
    <satolloexamples>
      <class>Satollo_Examples_Model</class>
    </satolloexamples>
  </models>
 
  <events>
    <model_save_before>
      <observers>
        <satolloexamples_model_save_before>
          <type>model</type>
          <class>satolloexamples/observer</class> 
          <method>modelBeforeSave</method>
        </satolloexamples_model_save_before>
      </observers>
    </model_save_before>
  </events>
</global>

“satolloexamples_model_save_before” is only a name for our observe definition where we say to Magento to call the method “modelBeforeSave()” of the model “satolloexamples/observer”, another abstract reference for our Observer class inside the “satolloexamples” set of models.

(Note: since Magento require every module to be a folder inside a package, to avoid conflict I prefix names referencing my modules with the package name and the module name, lowercase, and without any other separator. This prefix IS UNIQUE for every module… but not so many developers are using it…)

The code of the observer will be something like:

function modelBeforeSave($observer) {
  $event = $observer->getEvent();
  $object = $event->getData('object'); // Alternative $event->getObject()
  if ($object instanceof Mage_Sales_Model_Order_Status_History) {
    Mage::log('Order status history data: ' . print_r($object->getData(), true));
    return;
  }
}

And for a new status history we have (in /var/log/system.log):

2015-03-06T14:28:58+00:00 DEBUG (7): Order status history data: Array
(
    [status] => processing
    [comment] => 
    [entity_name] => invoice
    [store_id] => 1
    [is_customer_notified] => 
)

while for an existent status history updated we have:

2015-03-06T14:28:57+00:00 DEBUG (7): Order status history data: Array
(
    [entity_id] => 4
    [parent_id] => 2
    [is_customer_notified] => 0
    [is_visible_on_front] => 0
    [comment] => 
    [status] => pending
    [created_at] => 2015-03-06 13:50:24
    [entity_name] => order
    [store_id] => 1
)

Note: the status history model redefine the base _beforeSave() method and add the “parent_id” which is the referencing order id. This is why on a new model, when saving, we have not the parent_id and the entity_id (that will be autogenerated by the database).

Code

Download the code of this example. The code contains some extra PHP which is not related to this example, just ignore it.

Leave a Reply