Magento Factory Methods

Introduction to Magento Factory Methods

Using Magento Factory Methods within the application is easy to do and allows classes to be instantiated using the Factory Pattern. These factory methods are used to instantiate blocks, helpers and models. An example would be when Magento attempts load a product.

$product = Mage::getModel('catalog/product')->load(1);
// Where 1 is the product ID in this example

The snippet of code is used instead of instantiating the class directly.

$product = new Mage_Catalog_Model_Product();
$product->load(1);

The Factory Pattern allows for the creation of new objects without having to know the details of how they’re created. So how does this work in Magento?

How It Works

The getModel() method resides in Magento’s Mage.php class.


public static function getModel($modelClass = '', $arguments = array()) 
{ 
    return self::getConfig()->getModelInstance($modelClass, $arguments); 
}

First of all, the method delegates to the getModelInstance() method of the Mage_Core_Model_Config class.

The getModelInstance() method can be seen below.


public function getModelInstance($modelClass='', $constructArguments=array()) 
{
    $className = $this->getModelClassName($modelClass);
    if (class_exists($className)) {
        Varien_Profiler::start('CORE::create_object_of::'.$className);
        $obj = new $className($constructArguments);
        Varien_Profiler::stop('CORE::create_object_of::'.$className);
        return $obj;
    } else {
        return false;
    }
}

So here we can see that a new class of $className gets instantiated if a class exists for $className. So we need to find out what $className contains by looking at the getModelClass() method.


public function getModelClassName($modelClass) 
{
    $modelClass = trim($modelClass);
    if (strpos($modelClass, '/')===false) {
        return $modelClass;
    }
    return $this->getGroupedClassName('model', $modelClass);
}

As a reminder, the variable $modelClass is the parameter you initially pass into the getModel() method, so in our example, it would be catalog/product.

A check for a forward slash, / is then looked for in $modelClass, and if it is not found, the method will return the $modelClass. So whilst you could pass in the actual product model class into getModel(), such as getModel('Mage_Catalog_Product_Model'), it would still work.

This is not recommended because of an important reason. Passing in the class name rather than using the module/class syntax will mean that Magento will not check to see if the class has been rewritten. This is important to remember as there may be many custom modules that extend from core Magento classes.

The getGroupedClassName() method contains the below code.


public function getGroupedClassName($groupType, $classId, $groupRootNode=null) 
{
    if (empty($groupRootNode)) {
        $groupRootNode = 'global/'.$groupType.'s';
    }

    $classArr = explode('/', trim($classId));
    $group = $classArr[0];
    $class = !empty($classArr[1]) ? $classArr[1] : null;

    if (isset($this->_classNameCache[$groupRootNode][$group][$class])) {
        return $this->_classNameCache[$groupRootNode][$group][$class];
    }

    $config = $this->_xml->global->{$groupType.'s'}->{$group};

    // First - check maybe the entity class was rewritten
    $className = null;
    if (isset($config->rewrite->$class)) {
        $className = (string)$config->rewrite->$class;
    } else {
        if (isset($config->deprecatedNode)) {
            $deprecatedNode = $config->deprecatedNode;
            $configOld = $this->_xml->global->{$groupType.'s'}->$deprecatedNode;
            if (isset($configOld->rewrite->$class)) {
                $className = (string) $configOld->rewrite->$class;
            }
        }
    }

    // Second - if entity is not rewritten then use class prefix to form class name
    if (empty($className)) {
        if (!empty($config)) {
            $className = $config->getClassName();
        }
        if (empty($className)) {
            $className = 'mage_'.$group.'_'.$groupType;
        }
        if (!empty($class)) {
            $className .= '_'.$class;
        }
        $className = uc_words($className);
    }

    $this->_classNameCache[$groupRootNode][$group][$class] = $className;
    return $className;
}

To start with, we firstly have these values in the parameters.

$groupType = 'model';
$classId = 'catalog/product';

So the first part of the method sets the $groupRootNode to global/models.

if (empty($groupRootNode)) {
    $groupRootNode = 'global/'.$groupType.'s';
}

If you’ve delved around in Magento modules’ config.xml files, the global/models pattern should look familiar.

Next, we then split the $classId variable by the / as seen below.

$classArr = explode('/', trim($classId));
$group = $classArr[0];
$class = !empty($classArr[1]) ? $classArr[1] : null;

Which gives us these two variables:

$group = 'catalog';
$class = 'product';

Now we can see a configuration structure for the $config variable below. We can also see that a check is made to see if the class has been rewritten (again, if you’re familiar with rewriting a class in Magento, the config pattern should be recognisable).

$config = $this->_xml->global->{$groupType.'s'}->{$group};

// First - check maybe the entity class was rewritten
$className = null;
if (isset($config->rewrite->$class)) {
    $className = (string)$config->rewrite->$class;
} else {
    /**
     * Backwards compatibility for pre-MMDB extensions.
     * In MMDB release resource nodes <..._mysql4> were renamed to <..._resource>. So <deprecatedNode> is left
     * to keep name of previously used nodes, that still may be used by non-updated extensions.
     */
    if (isset($config->deprecatedNode)) {
        $deprecatedNode = $config->deprecatedNode;
        $configOld = $this->_xml->global->{$groupType.'s'}->$deprecatedNode;
        if (isset($configOld->rewrite->$class)) {
            $className = (string) $configOld->rewrite->$class;
        }
    }
}

So if a rewrite has occurred, we already have the $className variable populated, but if we don’t, we can see that Magento has given us an explanation about checking for deprecated nodes.

If the class has not been rewritten, and there are no deprecated nodes present, we can start formatting the class name,

// Second - if entity is not rewritten then use class prefix to form class name
if (empty($className)) {
    if (!empty($config)) {
        $className = $config->getClassName();
    }
    if (empty($className)) {
        $className = 'mage_'.$group.'_'.$groupType;
    }
    if (!empty($class)) {
        $className .= '_'.$class;
    }
    $className = uc_words($className);
}

$this->_classNameCache[$groupRootNode][$group][$class] = $className;
return $className;

Referring back to our example, $className contains the following value.

mage_catalog_model_product

We finally capitalise the first character of each word using PHP’s uc_words() function and return it back to the getModelInstance() method.

The functionality above is similar for other Magento factory methods such as:

  • Mage::getSingleton()
  • Mage::getResourceModel()
  • Mage::getResourceSingleton()
  • Mage::getBlockSingleton()
  • Mage::helper()
  • Mage::getResourceHelper()

Mage::helper()

The Mage::helper() method for example, also resides in Mage.php and delegates to the getHelperClassName() method:


public static function helper($name)
{
    $registryKey = '_helper/' . $name;
    if (!self::registry($registryKey)) {
        $helperClass = self::getConfig()->getHelperClassName($name);
        self::register($registryKey, new $helperClass);
    }
    return self::registry($registryKey);
}

The getHelperClassName() method will make a call to the same getGroupedClassName() method as we saw with the getModel() flow, but instead passes in the helper string as the $groupType variable.


public function getHelperClassName($helperName)
{
    if (strpos($helperName, '/') === false) {
        $helperName .= '/data';
    }
    return $this->getGroupedClassName('helper', $helperName);
}

Hopefully this article has helped you understand how Magento uses the Factory Pattern within its framework. To find out how to create your own Magento blocks, helpers, models and more, be sure to check out the Magento Custom Block, Model Helper and Controller article.

Note: This article is based on Magento Community/Open Source version 1.9.