Thursday, 28 June 2012

MCD: Describing class group configuration and use in the factory methods

Magento uses factory methods to instantiate Models, Blocks and Helpers classes, applying a necessary method (for example getModel, helper etc.). You should pass an abstract name of a class group, followed by an entity name. Class groups are described in configuration XML files in /etc/config.xml files of appropriate modules.

Let’s have a look at the proccess of class instantiating by the example of models and try to instantiate the product object. This can be done by using getModel() method:

$product = Mage::getModel(‘catalog/product’);

This method retrieves an instance of Mage_Catalog_Model_Product this way:
app/Mage.php

    /**
     * Retrieve model object
     *
     * @link    Mage_Core_Model_Config::getModelInstance
     * @param   string $modelClass
     * @param   array|object $arguments
     * @return  Mage_Core_Model_Abstract|false
     */
    public static function getModel($modelClass = '', $arguments = array())
    {
        return self::getConfig()->getModelInstance($modelClass, $arguments);
    }


getModel() calls getModelInstance() method, which gets class instance with the help of getModelClassName

app/code/core/Mage/Core/Model/Config.php

  /**      
     * Get model class instance.
     *
     * Example:
     * $config->getModelInstance('catalog/product')
     *
     * Will instantiate Mage_Catalog_Model_Mysql4_Product
     *
     * @param string $modelClass
     * @param array|object $constructArguments
     * @return Mage_Core_Model_Abstract|false
     */
    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;
        }
    }
 
    /**
     * Retrieve module class name
     *
     * @param   sting $modelClass
     * @return  string
     */
    public function getModelClassName($modelClass)
    {
        $modelClass = trim($modelClass);
        if (strpos($modelClass, '/')===false) {
            return $modelClass;
        }
        return $this->getGroupedClassName('model', $modelClass);
    }


getGroupedClassName() method does all the work. As you can see from getModelClassName, we pass the group type (model, block or helper) and the class identifier (in our case catalog/product) to this method. It explodes our string by using ‘/’ as needle, removes whitespaces and forms array of strings.

Then we load Varien_Simplexml_Element and pass our group name (catalog) to find class prefix name from config.xml of our module. In our example it would be Mage_Catalog_Model; in case this extension was overwritten, it could be Namespace_Catalog_Model. We also add entity class name (product) and after using Magento uc_words (based on PHP ucwords that returns a string with the first character of each word capitalized, if that character is alphabetic), we finally receive Mage_Catalog_Model_Product that will be returned in getModelInstance. As we noticed from above, it will create the object, using a new operator.

  /**
     * Retrieve class name by class group
     *
     * @param   string $groupType currently supported model, block, helper
     * @param   string $classId slash separated class identifier, ex. group/class
     * @param   string $groupRootNode optional config path for group config
     * @return  string
     */
    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 {
            /**
             * Backwards compatibility for pre-MMDB extensions.
             * In MMDB release resource nodes <..._mysql4> were renamed to <..._resource>. So  is left
             * to keep name of previously used nodes, that still may be used by non-updated extensions.
             */
            if ($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;
    }

Now let’s review how to declare class alias – a string that calls class (model, block, helper – depends from usage context)) we have declared. Alias naming rule is: group/entity.

As we observed, the group name is declared in configuration files in /etc/folder of the module.

General case
<config>
  <global>
    <models>
     <groupname>
        <class>Namespace_Modulename_Model</class>
     </groupname>
    </models>
  </global>
</config>

In our specific case
<config>
    <global>
        <models>
            <catalog>
                <class>Mage_Catalog_Model</class>
     </catalog>
     </models>
     </global>
</config>

Creating models objects, we can also try to use Mage::getSingleton() method. The method differs from getModel() by using different Design Pattern called Singleton, which means that only one instantiation of a class is used during the runtime process. However, when calling getModel(), a new class will be created every time the method is called.

Helpers

Instantiate a helper class the same way you do models. We call Mage::helper(‘group/entity’) and it calls getHelperClassName() in its turn, which has a default value ‘data’ in entity name. It means that if you pass only a group name, for example Mage::helper(‘catalog’), it will create an object of Mage_Catalog_Helper_Data class.

   /**
     * Retrieve helper object
     *
     * @param string $name the helper name
     * @return Mage_Core_Helper_Abstract
     */
    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);
    }


From the code above you may notice that Magento registers a variable when the helper is being created. So helper instance can be created only one time during runtime.

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


<config>
  <global>
    <helpers>
     <groupname>
        <class>Namespace_Modulename_Helper</class>
     </groupname>
    </helpers>
  </global>
</config>


Blocks

The proccess of instantiating blocks is similar to the one of creating models and helpers. Blocks can be instantiated via layout xml (

<config>
  <global>
    <blocks>
     <groupname>
        <class>Namespace_Modulename_Block</class>
     </groupname>
    </blockss>
  </global>
</config>


P.S. In Magento 2.0 this process will be changed as follows: Support to receive and process direct class names was added to all basic Magento methods that create objects. Support for class-id strings was retained to ensure a smooth transition (Framework Changes from 1.x to 2.x).

So instead of Mage::getModel(‘catalog/product’) it will be Mage::getModel(‘Mage_Catalog_Model_Product’). But this would be a completely new story;)

Courtesy: http://blog.belvg.com/

No comments :

Post a Comment