We suppose you continue following our Magento certification-dedicated posts in our blog. This time we will describe two topics – the Process and Configuration of Class Overrides in Magento and Register an Observer.
In the previous post we have described the following factory methods, used in Magento.
1 2 3 |
• for blocks Mage_Core_Model_Layout :: createBlock(...) • for models Mage :: getModel(...) and getResourceModel(...) • for helpers Mage :: helper(...) |
Due to the use of these factory methods, it became possible to implement rewrite.
What is it and how can we make use of it?
Let’s take the example of a model. As we mentioned, due to the getModel(‘catalog/product’) method, Magento returns an object of a certain class the way as follows:
1) It defines class name.
2) It creates an object for this class and calls autoloader.
a. It turns class name into Mage/Catalog/Model/Product.php path.
b. Server searches this file automatically among 3 codepools (local, community, core) and in the lib directory.
As a result, Magento loads the first found class according to the order, defined in include_path.
If we want to change basic Magento functionality, here are the options.
To do poorly. We can simply duplicate the file and its path (see the ‘a’ step above) into local codepool. Our class will be loaded from the local folder due to the priority of this codepool in the list, mentioned in include_path. Now make the necessary changes in the copied file.
So why is it bad? Because after copying the entire file, we will most likely miss all changes, made in a new version of the file, when Magento update is released. This method is better to be used for fast and dirty fixes.
To act smart. Inherit this class and describe in it changes only. There is a huge plus, compared to the previous method – readability. After all, we immediately see what was changed. Rewrite hurries to our rescue here. It allows us on the very first define class name stage to replace the name of the existing class with the name of our class (inherited from the original one).
How to describe it
Let’s create a module. In config.xml inside globals -> models, blocks and helpers we can specify our rewrites. For example, to override the standard class of the Mage_Catalog_Model_Product product, you need to use:
1 2 3 4 5 6 7 8 9 10 11 |
<global> ... <models> ... <catalog> <rewrite> <product> Namespace_Modulename_Model_Myproduct </product> </rewrite> </catalog> </models> </global> |
Principle is the same as in Magento path.
<catalog> – specify module namespace (catalog – node name can be viewed in module configuration in the section for modules);
<rewrite> – mark that next the list of module rewrites will be described;
<product> – part of the classname following the group classname (Mage_Catalog_Model). A new class name is specified inside, and the class will be used instead of the standard one.
To override the Mage_Adminhtml_Sales_Order_Create class, we will add the following lines in configuration (models section):
1 2 3 4 5 |
<adminhtml> <rewrite> <sales_order_create> Namespace_Modulename_Model_Adminhtml_Order_Create</sales_order_create> </rewrite> </adminhtml> |
The substitution is made here:
1 2 3 4 5 6 7 8 9 |
Mage :: getModel($modelClass, $arguments){ Mage_Core_Model_Config :: getModelInstance($modelClass, $arguments){ $className = $this->getModelClassName($modelClass){ return $this->getGroupedClassName('model', $modelClass) } ... $obj = new $className($arguments); } } |
getGroupedClassName makes search in rewrites, described in the layout. What will happen if we describe several rewrites for a single class? The first found will be executed. Peep inside a previous post Module Conflicts to find out more about such cases.
Creating rewrite for Blocks and Helpers is similar. We also have another integral part of Magento MVC – controllers. How can we change the functionality of a standard controller?
Controllers
To override the controller, we add a new router to config.xml of the module (expect to read more about router in the future posts). In brief, the pattern below helps establish the connection between router (used in the url) and a particular module.
1 2 3 4 5 6 7 8 9 10 11 |
<frontend> <routers> <modulename> <use>standard</use> <args> <module>Namespace_Modulename</module> <frontName>routername</frontName> </args> </modulename> </routers> </frontend> |
Creating modules, we normally set the connection between our router and a module. In this case we override the connection of existing router with our module.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<frontend> <routers> <prevmodulename> <args> <modules> <namespace_modulename before="Prevnamespace_Prevmodulename"> Namespace_Modulename </namespace_modulename> </modules> </args> </prevmodulename> </routers> </frontend> |
Now we create a controller with the same name in our module like this:
1 2 3 4 5 6 7 |
<?php require_once 'Prevnamespace/Prevmodulename/controllers/FilenameController.php'; class Namespace_Modulename_FilenameController extends Prevnamespace_Prevmodulename_FilenameController { ... } |
Prevnamespace, Prevmodulename – name space and modul name, where we need to override the controller.
Filename – the name of an overridable controller
Namespace, Modulename– name space and module name with an extended controller.
Rewrites may certainly be inevitable. But they cause modules conflicts and problems with updates. How do we pull ourselves through this? Let’s have a look at a very classy way to affect Magento.
Events in Magento
Observer pattern, implemented in Magento, allows inserting additional actions at given moments of code execution.
This pattern helps developing independent modules that embed their functionality with the help of events. It means it will be way more preferable to find an “observer” instead of rewriting the class. This observer will allow executing events at a certain time, influencing some kind of data. How is it realized?
Look for the mentioned notes in the code
Mage::dispatchEvent(‘event_name’, $event_arguments);
This note is an “observer” that allows executing methods, attached to it, transferring $event_arguments data array into methods.
For example, there is a great “observer” in the Mage_Core_Model_Abstract class, which allows impacting any object after it has been saved in database as far as models inherit Mage_Core_Model_Abstract.
1 2 3 4 5 |
protected function _afterSave() { ... Mage::dispatchEvent($this->_eventPrefix.'_save_after', $this->_getEventData()); ... } |
Let’s have a look at the Mage_Catalog_Model_Product model. After saving the product, the described method will be called.
1 2 3 4 5 6 |
$this->_eventPrefix = 'catalog_product'; $this->_getEventData()= array( 'data_object' => $this, ‘product’ => $this, ); |
Here is what we get:
Mage::dispatchEvent(‘catalog_product_save_after’, array(…, ‘product’ => $this));
Events that will be called are described in config.xml via layout. In our case:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<adminhtml> <events> <catalog_product_save_after> <observers> <my_individual_name> <type>singleton</type> <class>modulename/observer</class> <method>methodName</method> </my_individual_name> </observers> </catalog_product_save_after> </events> </adminhtml> |
(In this case I announce the event for area adminhtml, but events may also be described both by global and, of course, frontend).
1 2 3 4 5 6 7 8 |
class Namespace_Modulename_Model_Observer { public function methodName(Varien_Event_Observer $observer) { ... } } |
We can get the data of the array, transferred in dispatchEvent method, via $observer object. In our example array(…, ‘product’ => $this) is transferred. To receive the element of the product array, we should use $observer->getEvent()->getProduct().
Together with other events we are free to use the ones, formed automatically and individually for your module and for models and controllers, created in it.
• Models
*_load_before
*_load_after
*_save_before
*_save_after
*_save_commit_after
*_delete_before
*_delete_after
*_delete_commit_after
* – protected $_eventPrefix = ‘…’
Specified individually in a created model
• Controllers
controller_action_predispatch_*
controller_action_predispatch_**
controller_action_postdispatch_**
controller_action_postdispatch_*
controller_action_layout_render_before_**
(described in a controller, it mostly concerns blocks)
* – RouteName
** – RouteName_ControllerName_ActionName
Using Observers, described in Magento core, is a very good habit. Remember to create Observers in the main locations of your modules. It will help developers use and customize extensions in their projects easily.
Read our previous posts.
Describing Class Group Configuration and Use in Factory Methods
Explaining how Magento loads and manipulates configuration information. Part II
Explaining how Magento loads and manipulates configuration information. Part I
Describing Methods for Resolving Module Conflicts
Class Naming Conventions and Their Relationship with the Autoloader
Tobi,
You are right, we forgot the word “model”, but we meant it, because we do rewrite for the model.
Hi,
A very good post and in general a helpful blog. But I think there is a small mistake:
… To override the Mage_Adminhtml_Sales_Order_Create class, we will add the following lines in configuration (models section):
shouldn’t is rather be:
… to overwrite the Mage_Adminhtml_Model_Sales_Order_Create class…
correct me, if I’m wrong.
Cheers,
Tobi
Thanks this is very good and helpful.