While using select inputs in system.xml modules' files, it is required to define the source_model node. Magento will then search, by default, the toOptionArray() method of the class defined in  source_model. But it is possible to tell Magento to use another method.


Setup

It is very simple to tell Magento to use a specific method to populate options in a select input for system configuration fields: you just have to add ::methodNameWithoutBrackets to the uri of the called class.

For insteance, instead of writing:

<frontend_type>select</frontend_type>  
<source_model>module/path_to_class</source_model>  

We will write :

<frontend_type>select</frontend_type>  
<source_model>module/path_to_class::methodNameWithoutBrackets</source_model>  

On the PHP code side, it will still be required to return an array that respects Magento standards. This will be done like this:

class Namespace_Module_Model_Path_To_Class {  
    public function methodName()
    {
        return array(
            array('value'=>value_1, 'label'=>Mage::helper('module')->__('Value 1')),
            array('value'=>value_2, 'label'=>Mage::helper('module')->__('Value 2')),
        );
    }
}

For which uses?

The main interest of this technic is to allow gathering of several Source Models in a single class. So, for a module that has many dropdowns in its configuration, there will be no need to create as many classes as there are dropdowns. This allows a lighter code that is then more readable and easy to maintain.

Another interest is when making dynamic configuration elements generation. Indeed, if some developments require to populate some module's system configuration dynamically without having to declare each and every possible configuration in system.xml, it is possible to use this technic to call a different method depending on the system configuration item to inject dynamically. Dynamic configuration elements generation will be detailed in a further post.

Behind the scenes...

Here is how Magento gets to know how to populate a dropdown in system configuration ; and how Magento gets to know which method to use in the Source Model.

On system configuration display in the back-office:

  • Magento calls Mage_Adminhtml_Block_System_Config_Form::initFields()
  • Then, around line 440, the code below runs (comments have been added in order to explain how it works):
// If config element has the "source_model" node
if ($element->source_model) {  
    $factoryName = (string)$element->source_model;

    // The method to use in initialize with "false"
    $method = false;

    // If the "source_model" node has "::"
    if (preg_match('/^([^:]+?)::([^:]+?)$/', $factoryName, $matches)) {
        array_shift($matches);

        // We can get the "$method" var value : "methodNameWithoutBrackets" in our example
        list($factoryName, $method) = array_values($matches);
    }

    $sourceModel = Mage::getSingleton($factoryName);
    if ($sourceModel instanceof Varien_Object) {
        $sourceModel->setPath($path);
    }

    // If $method is not "false" anymore because we found a method to use
    // in the "source_model" node, we use it to populate the $optionArray var
    if ($method) {
        if ($fieldType == 'multiselect') {
            $optionArray = $sourceModel->$method();
        } else {
            $optionArray = array();
            foreach ($sourceModel->$method() as $value => $label) {
                $optionArray[] = array('label' => $label, 'value' => $value);
            }
        }

    // Otherwise, we use the "toOptionArray" method which is the default method
    // used by Magento for the "source_model" node
    } else {
        $optionArray = $sourceModel->toOptionArray($fieldType == 'multiselect');
    }
    $field->setValues($optionArray);
}