Magento Application Initialisation Part 1

Welcome to Magento Application Initialisation Part 1. Magento goes through a few steps to load the application. As with most PHP applications, the main entry point for Magento is an index.php file. Within index.php, a call to Magento’s Mage class’ run() method is made.

/* Store or website code */
$mageRunCode = isset($_SERVER['MAGE_RUN_CODE']) ? $_SERVER['MAGE_RUN_CODE'] : '';

/* Run store or run website */
$mageRunType = isset($_SERVER['MAGE_RUN_TYPE']) ? $_SERVER['MAGE_RUN_TYPE'] : 'store';

Mage::run($mageRunCode, $mageRunType);

The run method exists in app/Mage.php.

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());
        }
    }
}

There’s quite a bit of code here, but the main line to look at is:

self::$_app->run(array(
    'scope_code' => $code,
    'scope_type' => $type,
    'options'    => $options,
));

Here we are taken to the run() method of Mage_Core_Model_App. The run() method here is quite extensive, so we’ll break down the complete application initialisation into separate articles.

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;
}

The first line to look at is $this->baseInit($options).

public function baseInit($options) {
    $this->_initEnvironment();

    $this->_config = Mage::getConfig();
    $this->_config->setOptions($options);

    $this->_initBaseConfig();
    $cacheInitOptions = is_array($options) && array_key_exists('cache', $options) ? $options['cache'] : array();
    $this->_initCache($cacheInitOptions);

    return $this;
}

The first line makes a call to the _initEnvironment() method and this sets the default error handler and timezone.

protected function _initEnvironment() {
    $this->setErrorHandler(self::DEFAULT_ERROR_HANDLER);
    date_default_timezone_set(Mage_Core_Model_Locale::DEFAULT_TIMEZONE);
    return $this;
}

The _initBaseConfig() method is then called which delegates to the loadBase() method of the Mage_Core_Model_Config class.

protected function _initBaseConfig() {
    Varien_Profiler::start('mage::app::init::system_config');
    $this->_config->loadBase();
    Varien_Profiler::stop('mage::app::init::system_config');
    return $this;
}
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;
}

The loadBase() method loads the xml files located in the app/etc directory (This includes Enterprise’s enterprise.xml for EE users).

The first line that we come to before the while loop is the loadFile() method.

public function loadFile($filePath) {
    if (!is_readable($filePath)) {
        //throw new Exception('Can not read xml file '.$filePath);
        return false;
    }

    $fileData = file_get_contents($filePath);
    $fileData = $this->processFileData($fileData);
    return $this->loadString($fileData, $this->_elementClass);
}

The loadFile() method reads the content of the .xml file and returns the string as a Varien_Simplexml_Element Object using the loadString() method in the class Varien_Simplexml_Config class.

public function loadString($string) {
    if (is_string($string)) {
        $xml = simplexml_load_string($string, $this->_elementClass);

        if ($xml instanceof Varien_Simplexml_Element) {
            $this->_xml = $xml;
            return true;
        }
    } else {
        Mage::logException(new Exception('"$string" parameter for simplexml_load_string is not a string'));
    }
    return false;
}

To know more about simplexml_load_string, it is recommended to take a look at the PHP manual. The class name passed in as the second parameter is obtained from the _elementClass property which is found at the top of class:

protected $_elementClass = 'Varien_Simplexml_Element';

But wait! If we take a look at the Mage_Core_Model_Config‘s constructor.

public function __construct($sourceData=null) {
    $this->setCacheId('config_global');
    $this->_options         = new Mage_Core_Model_Config_Options($sourceData);
    $this->_prototype       = new Mage_Core_Model_Config_Base();
    $this->_cacheChecksum   = null;
    parent::__construct($sourceData);
}

We can see that $this->_prototype gets populated with a new class of Mage_Core_Model_Config_Base(). The Mage_Core_Model_Config class extends Mage_Core_Model_Config_Base which in turn extends Varien_Simplexml_Config. If we take a look at the Mage_Core_Model_Config_Base_Class below.

class Mage_Core_Model_Config_Base extends Varien_Simplexml_Config
{
    /**
     * Constructor
     *
     */
    public function __construct($sourceData=null)
    {
        $this->_elementClass = 'Mage_Core_Model_Config_Element';
        parent::__construct($sourceData);
    }
}

We can see that $this->_elementClass gets populated with Mage_Core_Model_Config_Element.

So if we take config.xml as an example, the $string variable would be populated as if you were looking at the xml file normally.

<config>
    <global>
        <install>
        etc...

And after the simplexml_load_string() function, it would get returned as a Mage_Core_Model_Config_Element object.

Mage_Core_Model_Config_Element object

We then arrive at an if statement checking if the xml is an instance of Varien_Simplexml_Element, which should be the case as Mage_Core_Model_Config_Base extends the Varien_Simplexml_Config class.

if ($xml instanceof Varien_Simplexml_Element) {

So heading back to the loadBase() method, we then head into the while loop where a few things happen.

1. Magento clones the $this->_prototype object into the variable $merge, so a copy is made, and then we go to load the current XML file into the new object.

$merge = clone $this->_prototype;
$merge->loadFile($file);

2. So now we have two xml objects to deal with which we need to merge together. This is done using the extend() method, which is the next line to execute within loadBase().

$this->extend($merge);

3. And finally, if check if one of the xml files loaded was local.xml, and if it was, we set a flag to true. This will usually tell Magento that we have an ‘installed’ system:

if (in_array($etcDir.DS.'local.xml', $files)) {
    $this->_isLocalConfigLoaded = true;
}

Continue to Magento Application Initialisation Part 2.

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