Certificated magento

1.8) Explain how Magento loads and manipulates configuration information

by Pavel Novitsky

http://blog.belvg.com/magento-certified-developer-exam-explaining-how-magento-loads-and-manipulates-configuration-information-part2.html - be sure to check out comments on this page too!
 
Basically, Magento configuration is spread among dozens of .xml files. So this is a reasonable question – how does Magento operate all these files and find proper settings for each particular extension?
 
 
Let’s refresh some key points of the Magento structure
 
• Magento is a modular system, in which functionality is located in separate modules.
 
• There are 3 code pools in Magento – local, community and core.
 
• The structure of each module includes app/code/[codePool]/Namespace/Modulename/config.xml (this file contains all basic module settings) and app/etc/modules/Namespace_Modulename.xml (this file contains information about code pool and extension activation flag).
 
• Global settings for Magento installation, including database connection data and admin panel address, are stored in app/etc/config.xml и app/etc/local.xml.
 
 
If we trace the code performance starting from index.php, we’ll get the following outcome:
 
 
Index.php
Mage::run()
        self::$_config = new Mage_Core_Model_Config($options); (in CE 1.6.* and previous versions)
        self::_setConfigModel($options); (in СE 1.7.*)
        self::$_app    = new Mage_Core_Model_App();
        self::$_app->run(…);
 
AND
 
Mage::app()
        self::$_app    = new Mage_Core_Model_App();
        self::$_app-> init (…);
 
 
Mage::run()
 
public static function run($code = '', $type = 'store', $options = array())
    {
        try {
            Varien_Profiler::start('mage');
            self::setRoot();
            if (isset($options['edition'])) {
                self::$_currentEdition = $options['edition'];
            }
            self::$_app    = new Mage_Core_Model_App();
            if (isset($options['request'])) {
                self::$_app->setRequest($options['request']);
            }
            if (isset($options['response'])) {
                self::$_app->setResponse($options['response']);
            }
            self::$_events = new Varien_Event_Collection();
            self::_setIsInstalled($options);
            self::_setConfigModel($options);
            self::$_app->run(array(
                'scope_code' => $code,
                'scope_type' => $type,
                'options'    => $options,
            ));
            Varien_Profiler::stop('mage');
        } catch (Mage_Core_Model_Session_Exception $e) {
            header('Location: ' . self::getBaseUrl());
            die();
        } catch (Mage_Core_Model_Store_Exception $e) {
            require_once(self::getBaseDir() . DS . 'errors' . DS . '404.php');
            die();
        } catch (Exception $e) {
            if (self::isInstalled() || self::$_isDownloader) {
                self::printException($e);
                exit();
            }
            try {
                self::dispatchEvent('mage_run_exception', array('exception' => $e));
                if (!headers_sent() && self::isInstalled()) {
                    header('Location:' . self::getUrl('install'));
                } else {
                    self::printException($e);
                }
            } catch (Exception $ne) {
                self::printException($ne, $e->getMessage());
            }
        }
    }
 
 
Later on we have a similar order of the class methods loading in both versions. In case of the Mage::run() all wrappers for processing configuration loading are located in Mage_Core_Model_App and refer to the Mage_Core_Model_Config methods. Calling Mage::app(), we immediately call Mage_Core_Model_Config::init(), which contains the process of configuration loading.
 
Finally, we come to Mage_Core_Model_Config, inherited from Mage_Core_Model_Config_Base and Varien_Simplexml_Config. At this stage the call method doesn’t matter, so let’s refer to Mage_Core_Model_Config:: init():
 
 
<?php
    /**
     * Initialization of core configuration
     *
     * @return Mage_Core_Model_Config
     */
    public function init($options=array())
    {
        // lets skip cache init and non-standard options stuff
        $this->loadBase();
        $cacheLoad = $this->loadModulesCache();
        if ($cacheLoad) {
            return $this;
        }
        $this->loadModules();
        $this->loadDb();
        $this->saveCache();
        return $this;
    }
Similar to Mage_Core_Model_Config::init, loadBase, loadModules, loadDb, ... also loaded in Mage_Core_Model_App:run() and they are always loaded when call run method, (Mage_Core_Model_Config::init is just loaded when we call Mage:app())

Mage_Core_Model_App:run()

public function run($params)

    {

        $options = isset($params['options']) ? $params['options'] : array();

        $this->baseInit($options);

        Mage::register('application_params', $params);

 

        if ($this->_cache->processRequest()) {

            $this->getResponse()->sendResponse();

        } else {

            $this->_initModules();

            $this->loadAreaPart(Mage_Core_Model_App_Area::AREA_GLOBAL, Mage_Core_Model_App_Area::PART_EVENTS);

 

            if ($this->_config->isLocalConfigLoaded()) {

                $scopeCode = isset($params['scope_code']) ? $params['scope_code'] : '';

                $scopeType = isset($params['scope_type']) ? $params['scope_type'] : 'store';

                $this->_initCurrentStore($scopeCode, $scopeType);

                $this->_initRequest();

                Mage_Core_Model_Resource_Setup::applyAllDataUpdates();

            }

 

            $this->getFrontController()->dispatch();

        }

        return $this;

    }

Let’s analyze this method line by line.
 
$this->loadBase();
 
 
    /**
     * Load base system configuration (config.xml and local.xml files)
     *
     * @return Mage_Core_Model_Config
     */
    public function loadBase()
    {
        $etcDir = $this->getOptions()->getEtcDir();
        $files = glob($etcDir.DS.'*.xml');
        $this->loadFile(current($files));
        while ($file = next($files)) {
            $merge = clone $this->_prototype;
            $merge->loadFile($file);
            $this->extend($merge);
        }
        if (in_array($etcDir.DS.'local.xml', $files)) {
            $this->_isLocalConfigLoaded = true;
        }
        return $this;
    }
 
From the very beginning we define the absolute path to app/etc directory. Then we get a list of all .xml files from this catalog, read their content and merge into a single simpleXmlElement object. If local.xml file has been loaded (which generally means that Magento has already been installed), set flag $this->_isLocalConfigLoaded = true;. It will be used later to initialize store and load module setup scripts.
 
As far as this method doesn’t limit names and the amount of loaded files, we can add our custom .xml file into app/etc, if necessary. It can be helpful for specifying database connection data on a dev-server without the use of local.xml file, containing production-server information only.
 
 
<?php
        $cacheLoad = $this->loadModulesCache();
        if ($cacheLoad) {
            return $this;
        }
 
This piece of code speaks for itself. If the configuration cache is enabled and contains the required information, we load the entire config. We also replace the config that has already been loaded from app/etc with the one, loaded from the cache (Mage_Core_Model_Config:: loadCache()) and return back to Mage_Core_Model_App.
 
There is a reasonable question you might ask at this point: if the whole configuration can be loaded from cache, why haven’t developers made this verification the first step, before scanning app/etc directory?
 
The thing is that Magento cache can be stored not only as files in var/cache, but in apc, memcached and xcache as well. This particular step with files loading from app/etc allows to define the type and configuration of the cache storage used.
 
The next step is loading of the most extensive configuration part – modules configuration.
 
$this->loadModules();
 
 
    /**
     * Load modules configuration
     *
     * @return Mage_Core_Model_Config
     */
    public function loadModules()
    {
        Varien_Profiler::start('config/load-modules');
        $this->_loadDeclaredModules();
        $resourceConfig = sprintf('config.%s.xml', $this->_getResourceConnectionModel('core'));
        $this->loadModulesConfiguration(array('config.xml',$resourceConfig), $this);
        /**
         * Prevent local.xml directives overwriting
         */
        $mergeConfig = clone $this->_prototype;
        $this->_isLocalConfigLoaded = $mergeConfig->loadFile($this->getOptions()->getEtcDir().DS.'local.xml');
        if ($this->_isLocalConfigLoaded) {
            $this->extend($mergeConfig);
        }
        $this->applyExtends();
        Varien_Profiler::stop('config/load-modules');
        return $this;
    }
 
$this->_loadDeclaredModules();
 
_getDeclaredModuleFiles(): At the beginning we scan app/etc/modules directory and collect the list of paths to all .xml files, indicating all modules in the system. We form an associative array with “base”, “mage” and “custom” keys. Only Mage_All.xml path goes to the “base” section. Modules from the Magento basic pack (located in app/code/core/Mage – code pool “core”) go to the «mage» section. «Custom» section collects the rest of modules. At the end we merge everything into a single array. Due to initial splitting into keys, data is stored in the following sequence: Mage_All.xml, modules with Mage namespace, all other modules.
 
If you haven’t figured out why Magento developers paid so much attention to Mage_All.xml file, it’s a high time for you to look inside of it. Mage_All.xml contains all information required for loading modules that are crucial for the proper system operation.
 
Next, information from the list of collected .xml files is loaded into Mage_Core_Model_Config_Base $unsortedConfig. Then we get the$moduleDepends array, based on <depends>, <active> flags and module name.
 
$this->_sortModuleDepends($moduleDepends): is necessary for checking dependencies of all existing modules. After the verification a new array is formed, where modules are arranged regarding their dependencies on one another.
 
At the end of _loadDeclaredModules() we create simpleXmlElement object again and merge it with the one that has been created earlier from app/etc/*.xml.
 
Let’s get back to loadModules() method.
 
 
<?php
        $resourceConfig = sprintf('config.%s.xml', $this->_getResourceConnectionModel('core'));
        $this->loadModulesConfiguration(array('config.xml',$resourceConfig), $this);
 
$resourceConfig contains config.mysql4.xml line. Thus, our next step will be loading configuration from config.mysql4.xml and config.mysql4.xml files. It is still not clear, what config.mysql4.xml was planned to be used for, but I really hope that further releases will exclude reference to this file or eventually it will prove to be needed.
 
First this method checks whether modules could be loaded from local code pool (pay attention at <disable_local_modules>false</disable_local_modules> in app/etc/local.xml). If only modules from community and core are permitted, Magento will change include_path() correspondingly.
 
 
<?php
        if ($disableLocalModules && !defined('COMPILER_INCLUDE_PATH')) {
            set_include_path(
                // excluded '/app/code/local'
                BP . DS . 'app' . DS . 'code' . DS . 'community' . PS .
                BP . DS . 'app' . DS . 'code' . DS . 'core' . PS .
                BP . DS . 'lib' . PS .
                Mage::registry('original_include_path')
            );
        }
After this simple verification Magento ensures that we already have some config – simpleXmlElement (if we don’t – it creates it) and delete from the loaded list of modules ones that have keys <active>false</active> and <codePool>local</codePool>.
 
Then for each of remained modules config.xml and config.mysql4.xml files are loaded. Next, as you’ve most likely figured out by yourselves, the loaded .xml is added to the existing one. If the last loaded file already contains the older xpath, system will use the last value.
 
The $this->applyExtends(); call originally was a way to change (override) data in config with the help of a custom extension. But this functionality is not in use at the moment.
 
If someone has managed to read everything down to these lines, please, pay attention to the following piece of code:
 
 
<?php
        /**
         * Prevent local.xml directives overwriting
         */
        $mergeConfig = clone $this->_prototype;
        $this->_isLocalConfigLoaded = $mergeConfig->loadFile($this->getOptions()->getEtcDir().DS.'local.xml');
        if ($this->_isLocalConfigLoaded) {
            $this->extend($mergeConfig);
        }
As we see, developers took care of no module could modify system data from app/etc/local.xml (the default database connection, outer cache and proxy-servers settings).
 
Finally, we’ve approached $this->loadDb();
 
 
<?php
    /**
     * Load config data from DB
     *
     * @return Mage_Core_Model_Config
     */
    public function loadDb()
    {
        if ($this->_isLocalConfigLoaded && Mage::isInstalled()) {
            Varien_Profiler::start('config/load-db');
            $dbConf = $this->getResourceModel();
            $dbConf->loadToXml($this);
            Varien_Profiler::stop('config/load-db');
        }
        return $this;
    }
 
This method uses resource Mage_Core_Model_Resource_Config model, related to core_config_data table. At this step we load data from core_config_data into our configuration:
 
1. We add data about websites (see core_website table)
2. We add data about stores for the existing websites (see core_store table)
3. We add data from core_config_data according to the scope
a. Create <default> block first
b. Then create <websites> block
c. And, finally – <stores>
d. Each iteration replaces data from more general area of the scope with more specific one (Do you remember these amazing «use default», «use website» and «configuration scope» in the backend?)
4. Self-configuration (if Magento meets data related to no longer existing website, these data will be deleted).
 
Explain how Magento loads and manipulates configuration information
 
by Pavel Novitsky
http://blog.belvg.com/magento-certified-developer-exam-explaining-how-magento-loads-and-manipulates-configuration-information-part2.html
 
 
We started explaining how Magento loads and manipulates configuration information here.
 
The next step is writing config into cache, if it is enabled.
 
Our configuration is formed by now, but besides compiling this .xml file, we need to obtain data from it somehow. Let’s go back to Mage.php.
 
 
Here are the methods:
 

<?php
    /**
     * Retrieve config value for store by path
     *
     * @param string $path
     * @param mixed $store
     * @return mixed
     */
    public static function getStoreConfig($path, $store = null)
    {
        return self::app()->getStore($store)->getConfig($path);
    }
    /**
     * Retrieve config flag for store by path
     *
     * @param string $path
     * @param mixed $store
     * @return bool
     */
    public static function getStoreConfigFlag($path, $store = null)
    {
        $flag = strtolower(self::getStoreConfig($path, $store));
        if (!empty($flag) && 'false' !== $flag) {
            return true;
        } else {
            return false;
        }
    }
 
 
Their only difference is that getStoreConfig() will return the exact value while getStoreConfigFlag(), as its name suggests, returns boolean true or false. Both methods send us to Mage_Core_Model_Store::getConfig()
 
<?php
    /**
     * Retrieve store configuration data
     *
     * @param   string $path
     * @return  string|null
     */
    public function getConfig($path)
    {
        if (isset($this->_configCache[$path])) {
            return $this->_configCache[$path];
        }
        $config = Mage::getConfig();
        $fullPath = 'stores/' . $this->getCode() . '/' . $path;
        $data = $config->getNode($fullPath);
        if (!$data && !Mage::isInstalled()) {
            $data = $config->getNode('default/' . $path);
        }
        if (!$data) {
            return null;
        }
        return $this->_processConfigValue($fullPath, $path, $data);
    }
 
If the requested information is not found in a local cache, this method will use the path stores/[store code]/[requested path]. If data is still not found, the method will use another path default/[requested path] to search in the loaded configuration. When nothing is found, method will return null.
 
Found data is processed via _processConfigValue() method:
 
• If the returned node has children, data will be stored in a local cache recursively. By the next call within a current user session there will be no need to look for a found data in the configuration.
• If the node has backend_model, this model should be requested to bring data to the required format.
• Variable of {{unsecure_base_url}}, {{unsecure_base_url}}, {{base_url}}sort are replaced by the correspondent values.
 
As a conclusion I would like to sum everything up and point out the basics.
 
1. All .xml files are collected into one big simpleXmlElement object
 
2. First, data is loaded from app/etc/*.xml and then from app/etc/modules/*.xml. Based on the module loaded information, config.xml is loaded from the etc directory of the module. If we load backend to check ACL and build menu elements, adminhtml.xml and system.xml are loaded as well. Configuration data from the database is the last one to load.
 
3. Any parameters, except the ones stored in app/etc/local.xml, can be overridden in config.xml of a custom module.
 
P.S. Even though Magento provides us with such convenient methods as Mage:: getStoreConfig() and Mage:: getStoreConfigFlag(), we can reach any element of the configuration tree with the help of Mage::getConfig()->getNode($path, $scope, $scopeCode);
 
Look at your final config of the Magento installation by running the following piece of code in the site root directory:
 

 

 

0 Bình luận

Trở về
  • 1.12) Set up a cron job
  • 1.12) Set up a cron job

    Let’s start with server setup.   As any complex system, Magento has a lot of tasks that need to be executed...