Next, we will move on to an event/observer pattern implemented by Magento. Events and observers are extremely important in Magento because they enable you to easily hook onto various parts of Magento and add your own pieces of code to it. In certain situations they are slightly underestimated by extension developers, either due to their knowledge of the platform, or due to the lack of grand vision or forced quick and dirty implementations.
Events and observers are the key to writing unobtrusive code in cases where you need to change or add to the default Magento behavior. For example, if you need to send all your newly created orders to an external fulfillment system, most of the time you simply need to observe a proper event and implement your business logic within an observer.
There are several types of events getting fired in Magento depending on how you differentiate them. For example, we could divide them into static and dynamic events.
Static events are all those events defined through code with full event names such as Mage::dispatchEvent('admin_session_user_login_failed', array('user_name' => $username, 'exception' => $e));
, Mage::dispatchEvent('cms_page_prepare_save', array('page' => $model, 'request' => $this->getRequest()));
, Mage::dispatchEvent('catalog_product_get_final_price', array('product' => $product, 'qty' => $qty));
, Mage::dispatchEvent('catalog_product_flat_prepare_columns', array('columns' => $columnsObject));
, and Mage::dispatchEvent('catalog_prepare_price_select', $eventArgs);
.
Dynamic events are all those events defined through code dynamically at runtime such as Mage::dispatchEvent($this->_eventPrefix.'_load_before', $params);
, Mage::dispatchEvent($this->_eventPrefix.'_load_after', $this->_getEventData());
, Mage::dispatchEvent($this->_eventPrefix.'_save_before', $this->_getEventData());
, Mage::dispatchEvent($this->_eventPrefix.'_save_after', $this->_getEventData());
, and Mage::dispatchEvent('controller_action_layout_render_before_'.$this->getFullActionName());
.
Both types of events are absolutely the same; they function the same, and the preceding differentiation is simply a matter of terminology. We are calling the other ones dynamic because their full name is not known until the runtime.
For example, each time you wish to intercept certain parameters passed to a controller action, you could simply create an event observer that would observe the controller_action_predispatch_*
event, which is triggered within the Mage_Core_Controller_Varien_Action
class file as follows: Mage::dispatchEvent('controller_action_predispatch_' . $this->getFullActionName(), array('controller_action' => $this));
.
Now, let us see how exactly do we define the event observer and place some of our code to be executed upon certain events. First, we need to create an entry within our extensions config.xml
file.
Let's say we want to introspect all the parameters passed to the controller action during the customer registration process. When a customer fills in the required registration fields and clicks on Submit, the form posts the data to the http://{{shop.domain}}/index.php/customer/account/createpost/
URL.
If you look at the previously mentioned the controller_action_predispatch_*
event, the expression $this->getFullActionName()
would return the customer_account_createpost string
. You can find that out easily by placing the var_dump($this->getFullActionName()); exit;
expression right there under the Mage::dispatchEvent('controller_action_predispatch_...
expression. Please note that we are using var_dump
here just for the simplicity of demonstration. So now that we know this, we can safely conclude that the full event name we need to observe in this case is controller_action_predispatch_customer_account_createpost
.
Now we know that the event name is a requirement upon which we create a proper config.xml
entry for defining our event observer as shown in the following code:
<?xml version="1.0"?> <config> <!-- … other elements ... --> <frontend> <events> <controller_action_predispatch_customer_account_createpost> <observers> <foggyline_happyhour_intercept> <class>foggyline_happyhour/observerobserver</class> <method>intercept</method> </foggyline_happyhour_intercept> </observers> </controller_action_predispatch_customer_account_createpost> </events> </frontend> <!-- … other elements ... --> </config>
Within the observer's element comes the definition of our observer, which we call foggyline_happyhour_intercept
in this case. Each observer needs two properties-defined classes, which in this case points to the foggyline_happyhour
class group and Observer
class file thus, the string foggyline_happyhour/observer
; the other one is the method within the Observer
class file.
Next, we create the actual Observer
class file app/code/community/Foggyline/HappyHour/Model/Observer.php
with the following content:
<?php class Foggyline_HappyHour_Model_Observer { public function intercept($observer = null) { $event = $observer->getEvent(); $controllerAction = $event->getControllerAction(); $params = $controllerAction->getRequest()->getParams(); Mage::log($params); } }
A quick look at Foggyline_HappyHour_Model_Observer
reveals one important thing: unlike Model
, Block
, and Controller
classes, the Observer
classes do not need to extend anything.
If you now go to your browser and try to create a new customer account, you will get your var/log/system.log
file filled with the HTTP POST parameters provided by the customer during the registration process. You might need to refresh/re-open system.log
in your editor in order to pick up the changes, in case you don't see the log entries.
Sometimes, the right event might not be there; so you might need to look for the second best. For example, if we did not have the controller_action_predispatch_customer_account_createpost
event dispatched, the next best event would probably be the following one: Mage::dispatchEvent('controller_action_predispatch', array('controller_action' => $this));
.
However, the event controller_action_predispatch
is pretty generic, which means it will get triggered for every controller action predispatch
. In this case, you would have to do a little if/else logic within your event observer code. Just as we have controller fired events, we also have model-fired events. If you open a class file like Mage_Catalog_Model_Product
, you can see property definitions like protected $_eventPrefix = 'catalog_product';
and protected $_eventObject = 'product';
.
Now, if you trace the code a little bit down to the Mage_Core_Model_Abstract
class file, you will see that the properties $_eventPrefix
and $_eventObject
are used for dynamic events such as (along with the static events for the same action) Mage::dispatchEvent($this->_eventPrefix.'_load_before', $params);
, Mage::dispatchEvent($this->_eventPrefix.'_load_after', $this->_getEventData());
, Mage::dispatchEvent($this->_eventPrefix.'_save_commit_after', $this->_getEventData());
, Mage::dispatchEvent($this->_eventPrefix.'_save_before', $this->_getEventData());
, Mage::dispatchEvent($this->_eventPrefix.'_save_after', $this->_getEventData());
, Mage::dispatchEvent($this->_eventPrefix.'_delete_before', $this->_getEventData());
, Mage::dispatchEvent($this->_eventPrefix.'_delete_after', $this->_getEventData());
, Mage::dispatchEvent($this->_eventPrefix.'_delete_commit_after', $this->_getEventData());
, and Mage::dispatchEvent($this->_eventPrefix.'_clear', $this->_getEventData());
.
Knowing this is extremely important, as it enables you to create all sorts of event observers for specific models and their actions, for example customer, order, and invoice entity create/update/delete actions. This is why defining the $_eventPrefix
and $_eventObject
properties on your custom model classes is something you should adopt as a sign of good coding practice. Doing so enables other third-party developers to easily hook onto your extension code via the observer in a clean and unobtrusive way.