Any Magento developer has to deal with those config.xml files. And they actually hide some nice features that make cleaner, more powerful and more readable code possible. Let's reveal some of their secrets.

TL;DR

Get the full example extension on Github (mid/high Magento experience required): https://github.com/herveguetin/Herve_ConfigExample_M1

A refreshner on the Magento configuration object

The config.xml files declared in Magento modules' /etc dir are part of a larger configuration object that Magento uses at runtime. This configuration object is an instance of Mage_Core_Model_Config and combines information coming from several sources (see Mage_Core_Model_Config::init):

  • all XML files stored in /app/etc
  • config.xml files stored in each module's /etc dir
  • entries from the core_config_data database table

Once loaded, the information coming from all those sources is combined in a single XML object with a "merge and replace" strategy. In other words, if a module's config.xml declares a path, the resulting value of this path may be overridden by another module.

For example, let's have a look at app/code/core/Mage/Newsletter/etc/config.xml. This file declares the following XML path: /config/adminhtml/events/customer_save_after/observers/newsletter_subscribe_observer/method whose value is subscribeCustomer. This value may be overridden by another module that would declare the same XML path.

This "merge and replace" strategy allows us to do interesting things like:

  • creating our own configuration nodes in the config.xml files of our modules
  • creating our own configuration files (my_config.xml for example)
  • implementing the Decorator Design Pattern in an elegant way

Creating our own configuration nodes

Let's take a very basic config.xml that we would create for our Namespace_Module module:

<config>  
    <modules>
        <Namespace_Module>
            <version>1.0.0</version>
        </Namespace_Module>
    </modules>
</config>  

And let's enhance it with some very custom nodes:

<config>  
    <modules>
        <Namespace_Module>
            <version>1.0.0</version>
        </Namespace_Module>
    </modules>

    <namespace_module_custom_config>
        <some_key>Some value</some_key>
    </namespace_module_custom_config>
</config>  

We are now able to retrieve Some value in any PHP file by just using the path where <config> is the root: namespace_module_custom_config/some_key.

Mage::getConfig()->getNode('namespace_module_custom_config/some_key');  
// Some value

This kind of trick is very interesting when you wish to use some specific configuration for your business logic. For example, when you are building a code that needs to call some API, instead of using a constant with the URL of the webservice, you may store this URL in the config.xml file of your module.

This has several advantages:

  • it is easier for a fellow developer to understand the purpose of your module just by reading thru its config.xml
  • but, more interesting, it opens this configuration for modification by another module without touching your own module's code! (see the refreshner part of this post).

Creating our own configuration files

Using the trick we just spoke about is very interesting when we need to do some kind of mapping. A multi-language "Hello world" is a good example. Let's populate our config.xml in a way that maps a language code to its locale way of saying "Hello world":

<config>  
    <modules>
        <Namespace_Module>
            <version>1.0.0</version>
        </Namespace_Module>
    </modules>

    <hello_worldwide>
        <en>Hello World</en>
        <fr>Bonjour Monde</fr>
        <de>Halo Welt</de>
    </hello_worldwide>
</config>  

Getting the sentence in French from our PHP code is then as easy as:

Mage::getConfig()->getNode('hello_worldwide/fr');  
// Bonjour Monde

But imagine that we had to populate the translation for the hundreds of languages that mankind uses... Our config.xml file would quickly be unreadable and difficult to maintain. Wouldn't it be much more elegant to extract all our translations in their own hello_world.xml? Yes, it would.

So the first easy step is to create this XML file in our module's /etc dir (app/code/local/Namespace/Module/etc/hello_world.xml) and place the extracted content enclosed in a <config> node:

<config>  
    <hello_worldwide>
        <en>Hello World</en>
        <fr>Bonjour Monde</fr>
        <de>Halo Welt</de>
    </hello_worldwide>
</config>  

Now we must tell to the Magento config object to load this additionnal hello_world.xml. Easy again:

$config = Mage::getConfig();
$config->loadModulesConfiguration('hello_world.xml', $config);

This very simple snippet tells Magento to "merge and replace" its existing config object with the content of the hello_world.xml file of each module. Yes, you guessed well, Magento will look for the hello_world.xml file in the /etc dir of each module and merge the whole thing.

And this is great because this gives us a great flexibility to enhance our translations. Hence, our current hello_world.xml is only able to speak English, French and German. But what if some project we are working on needs to speak Spanish? What we showed until here allows us to:

  • create another module that speaks Spanish: Namespace_Spanish
  • add a hello_world.xml in app/code/local/Namespace/Spanish/etc/hello_world.xml
  • populate this XML file only with the Spanish translation like so:
<config>  
    <hello_worldwide>
        <es>Ola Mundo</es>
    </hello_worldwide>
</config>  

And there we are:

$config = Mage::getConfig();
$config->loadModulesConfiguration('hello_world.xml', $config);
$translations = $config->getNode('hello_worldwide')->asArray();
foreach ($translations as $translation) {  
    echo $translation;
}
// Hello World Bonjour Monde Halo Welt Ola Mundo

Implementing the Decorator Design Pattern

Magento combines the Decorator Design Pattern and its configuration XML system to implement its checkout totals (please dig into Mage_Sales_Model_Quote_Address::collectTotals and be persistant!)

To make things easier, I have made a more simple full example available on Github: https://github.com/herveguetin/Herve_ConfigExample_M1