magento module

Magento Events

When it comes to extending Magento core functionality you have two options - override core classes or use event-driven architecture. The major disadvantage of first is that you can override class only once, so if you want to override it in multiple modules you are soon going to find yourself in dependency hell. Event-driven architecture allows you to keep loose coupling without losing the flexibility of extending Magento modules.

When you want to use Magento event-driven architecture you must know basically two things - how to dispatch an event and how to catch it.

Dispatching events

Within Magento you can dispatch an event as simple as by calling Mage::dispatchEvent(...) method, for example:

 Mage::dispatchEvent('custom_event', array('object'=>$this));

This methods accepts two parameters - event unique identifier and associative array of data that is set as Varien_Event_Observer object data, so in fact passed to event observers.

Catching events

Catching events is a little bit more complex than dispatching. You have to use existing custom module or create a new one. In the minimal case the module file tree should look like this:

  

Within config.xml you have to add a definition of event observer. Which of the main config.xml file sections (frontend, adminhtml) should contain this definition depends on the scope you want your observer to work on. Here is the example of definition:

<events>
      <custom_event> <!-- identifier of the event we want to catch -->
        <observers>
          <custom_event_handler> <!-- identifier of the event handler -->
            <type>model</type> <!-- class method call type; valid are model, object and singleton -->
            <class>baobazacustommodule/observer</class> <!-- observers class alias -->
            <method>customObserverAction</method>  <!-- observer's method to be called -->
            <args></args> <!-- additional arguments passed to observer -->
          </custom_event_handler>
        </observers>
      </custom_event>
</events>

Xml above should be self-explanatory. I will just explain that for type model and object are equal in behavior and mean that object of a class will be instantiated using Mage::getModel(...) method, and singleton means it will be instantiated using Mage::getSingleton(...) method.

Observer.php file should contain relevant observer class. There is no interface nor need to extend any class for observer classes. The method though should accept one parameter which is the object of Varien_Event_Observer class. This object is the link between dispatcher and event handler. It inherits from Varien_Object so has all required getters handled magically. For example:

class Baobaz_ACustomModule_Model_Observer
{
  public function customObserverAction(Varien_Event_Observer $observer)
  {
    $object = $observer->getEvent()->getObject(); // we are taking the item with 'object' key from array passed to dispatcher
    $object->doSomething();

    return $this;
}

Default events

Magento implements lot of events. You can find list of them here. What you may miss reading this list, and what was spotted on MageDev blog, Mage_Core_Model_Abstract by default dispatch some special events. Those are:
 

event identifier event parameters
model_save_before 'object'=>$this
{_eventPrefix}_save_before {_eventObject}=>$this
model_save_after 'object'=>$this
{_eventPrefix}_save_after {_eventObject}=>$this
model_delete_before 'object'=>$this
{_eventPrefix}_delete_before {_eventObject}=>$this
model_delete_after 'object'=>$this
{_eventPrefix}_delete_after {_eventObject}=>$this
model_load_after 'object'=>$this
{_eventPrefix}_load_after {_eventObject}=>$this

 

{_eventPrefix} means the value of $_eventPrefix variable and {_eventObject} means the value of $_eventObject variable. All classes inheriting from Mage_Core_Model_Abstract should override these variables to create specific events being dispatched. For example for catalog cagetory these variables take following values: $_eventPrefix = 'catalog_category';  $_eventObject = 'category';

Magento Dataflow - Optimized Product Import [Part 3]

Magento Dataflow module comes with standard product adapter (see Magento Dataflow - Default Adapters [Part 2]). Sometimes though, default solution is not enough and you may want to create your own adapter processing products.

Creating own adapter is not hard, but if you forget two lines of code, you may be very surprised with its performance. These two lines you should add before calling $product->save():

$product->setIsMassupdate(true);
$product->setExcludeUrlRewrite(true);

First line sets $data variable 'is_massupdate', which can be later checked to save some postprocessing actions time. Some observers watching for catalog_product_save_after event check this value (i.e. CatalogRule module's Observer, which skips action of applying catalog rules on products if  $product->getIsMassupdate() returns  true )

Second line also sets $data variable 'exclude_url_rewrite', which is used by afterSave method of Mage_Catalog_Model_Product_Attribute_Backend_Urlkey to check if catalog url rewrite cache should be refreshed.

Those two lines allow you to save few seconds per each row of processed products, so keep in mind to not forget about them

Magento Translations Module

One of best things in magento is you can easily add new language versions to the site. It's all done using CSV files - 1 file for 1 module per language. For most popular languages translations are available as free Magento modules on magento connect.

What if we want to change the translations and don't lose changes after updating magento and translations? Or if we want to have translation common independently of module? Or just list all the texts that were added by us and are not original magento texts? We can create our own very simple translations module.

You need to edit only 2 files:

  • etc/config.xml
    <?xml version="1.0"?>
    <config>
            <modules>
                    <Baobaz_Translations>
                            <version>0.1.0</version>
                    </Baobaz_Translations>
            </modules>
           
            <global>
                    <helpers>
                            <translations>
                                    <class>Baobaz_Translations_Helper</class>
                            </translations>
                    </helpers>
            </global>
           
            <frontend>
                    <translate>
                            <modules>
                                    <Baobaz_Translations>
                                            <files>
                                                    <default>Baobaz_Translations.csv</default>
                                            </files>
                                    </Baobaz_Translations>
                            </modules>
                    </translate>
            </frontend>

    </config>
  • and helper/data.php:
    class Baobaz_Translations_Helper_Data extends Mage_Core_Helper_Abstract
    {

    }

and that's it... Module must be turned on (in etc/modules) and it will work fine.

in app/locale/fr_fr/ we create file Baobaz_Translations.csv and in it we put all the new translations we need.

Then, anywhere in magento - doesn't matter if it's template, block or controller - we can use:

echo Mage::helper('translations')->__('text to translate');

Yes. It is that simple.

Magento Dataflow - Default Adapters [Part 2]

"Magento DataFlow - Data Exchange Made Flexible" article introduced global concept of data exchange framework implemented in Magento. Today I would like to tell more about default adapters implemented in DataFlow module.

  1. Adapter definition

    Adapters are responsible for pluging into an external data resource and fetching requested data or saving given data into data resource. For this purpose all adapters implement interface Mage_Dataflow_Model_Convert_Adapter_Interface which contains two methods: load() and save(). Data exchange concept introduced in DataFlow module use adapters in 3 contexts:

    • to load data from resource - using load() method
    • to save data to resource - using save() method
    • to process one parsed row - when defined as adapter/method pair of variables of parser

    For first two contexts adapter's xml definition looks like that:

    <action type="dataflow/convert_adapter_io" method="load">
        ...
    </action>

    Action tag has two parameters: type and method. Type tells as which adapter class is to be used in this action. It is defined using its alias. Method tells us which method of this adapter class action should call. As mentioned before, by default there are two available methods: load and save. Children of action tag define variables which are parameters used when executing adapter's method. Variables are defined like in the example below:

    <action type="dataflow/convert_adapter_io" method="load">
        <var name="type">file</var>
        <var name="path">var/import</var>
        <var name="filename"><![CDATA[products.csv]]></var>
        <var name="format"><![CDATA[csv]]></var>
    </action>

  2. Magento DataFlow default adapters

    Magento DataFlow module contains few default adapter classes which you can find in app/code/core/Dataflow/Model/Convert/Adapter. Not all of them have yet implemented load() and save() methods.

    For common case of reading data from or saving data to local or remote file you will use dataflow/convert_adapter_io (Mage_Dataflow_Model_Convert_Adapter_Io).

    Following variables will allow you to define local/remote file as data source:

    • type - defines type of io source we want to process. Valid values: file, ftp
    • path - defines relative path to the file
    • filename - defines data source file's name
    • host - for ftp type it defines the ftp host
    • port - for ftp type it defines the ftp port; if not given, default value is 21
    • user - for ftp type it defines the ftp user, if not given default value is 'anonymous' and password then is 'anonymous@noserver.com'
    • password - for ftp type it defines the ftp user's password
    • timeout - for ftp type it defines connection timeout; default value is 90
    • file_mode - for ftp type it defines file mode; default value is FTP_BINARY
    • ssl - for ftp type if it is not empty, then ftp ssl connection is used
    • passive - for ftp type it defines connection mode; default value is false
  3. Customer and Product adapters

    For most commonly exchanged entities - customer and product - Magento provides default adapters: customer/convert_adapter_customer (Mage_Customer_Model_Convert_Adapter_Customer) and catalog/convert_adapter_product (Mage_Catalog_Model_Convert_Adapter_Product). Both inherit from Mage_Eav_Model_Convert_Adapter_Entity.

    To simply load all customers data for selected store you can use the following xml:

    <action type="customer/convert_adapter_customer" method="load">
        <var name="store">default</var>
    </action>

    Sometimes you may want to not load all customers in database. To help you with this there are following variables valid:

    • filter/firstname - to load only customers with firstname starting with value of this variable
    • filter/lastname - to load only customers with lastname starting with value of this variable
    • filter/email - to load only customers with email starting with value of this variable
    • filter/group - to load only customers from group with id equal to value of this variable
    • filter/adressType - to export only selected addressType; valid values are: both, default_billing, default_shipping
    • filter/telephone - to load only customers with telephone starting with value of this variable
    • filter/postcode - to load only customers with postcode starting with value of this variable
    • filter/country - to load only customers with country iso code equal to value of this variable
    • filter/region - to load only customers with region equal to value of this variable (for US just 2-letter state names)
    • filter/created_at/from - to load only customers created after a date defined as value of this variable
    • filter/created_at/to - to load only customers created before a date defined as value of this variable

    For example:

    <action type="customer/convert_adapter_customer" method="load">
        <var name="store"><![CDATA[0]]></var>
        <var name="filter/firstname"><![CDATA[a]]></var>
        <var name="filter/lastname"><![CDATA[a]]></var>
        <var name="filter/email"><![CDATA[a]]></var>
        <var name="filter/group"><![CDATA[1]]></var>
        <var name="filter/adressType"><![CDATA[default_billing]]></var>
        <var name="filter/telephone"><![CDATA[1]]></var>
        <var name="filter/postcode"><![CDATA[7]]></var>
        <var name="filter/country"><![CDATA[BS]]></var>
        <var name="filter/region"><![CDATA[WA]]></var>
        <var name="filter/created_at/from"><![CDATA[09/22/09]]></var>
        <var name="filter/created_at/to"><![CDATA[09/24/09]]></var>
    </action>

    Same way you can load and filter products loaded from database with following variables:

    • filter/name - to load only products with name starting with value of this variable
    • filter/sku - to load only products with sku starting with value of this variable
    • filter/type - to load only products with type defined as value of this variable; valid values are: simple, configurable, grouped, bundle, virtual, downloadable
    • filter/attribute_set - to load only products with attribute set id equal to value of this variable
    • filter/price/from - to load only products with price starting from value of this variable
    • filter/price/to - to load only products with price up to value of this variable
    • filter/qty/from - to load only products with quantity starting from value of this variable
    • filter/qty/to - to load only products with quantity up to value of this variable
    • filter/visibility - to load only products with visibility id equal to value of this variable
    • filter/status - to load only products with status id equal to value of this variable

    Example:

    <action type="catalog/convert_adapter_product" method="load">
        <var name="store"><![CDATA[0]]></var>
        <var name="filter/name"><![CDATA[a]]></var>
        <var name="filter/sku"><![CDATA[1]]></var>
        <var name="filter/type"><![CDATA[simple]]></var>
        <var name="filter/attribute_set"><![CDATA[29]]></var>
        <var name="filter/price/from"><![CDATA[1]]></var>
        <var name="filter/price/to"><![CDATA[2]]></var>
        <var name="filter/qty/from"><![CDATA[1]]></var>
        <var name="filter/qty/to"><![CDATA[2]]></var>
        <var name="filter/visibility"><![CDATA[2]]></var>
        <var name="filter/status"><![CDATA[1]]></var>
    </action>

Seems a little bit frightening if you see all those id values you have to provide for filters. Fortunatelly for these two entities - customers and products - there is wizard like profile generator that allows you to define filter with simple select boxes.

In next part I will describe use of parsers and adapters in context of parsers.