Day 2 Continued
Explain how Magento loads and manipulates configuration information
Magento的配置基本上散布在很多个.xml文件中,那么很自然地就有一个疑问,Magento是如何操作这些文件又从而为每个插件找到相应的配置文件的呢?下面我们先来梳理一下Magento结构的核心要点:
• Magento是一个模块化系统,功能分布在各个模块中;
• 有三个代码池,分别是local, community和core;
• 每个模块中包含app/code/[codePool]/Namespace/Modulename/config.xml (该文件 中存放基本模块配置)和app/etc/modules/Namespace_Modulename.xml (该文件存放代码池信息以及插件激活标记);
• Magento安装时的全局配置(包括数据库连接系统和后台地址)存放在app/etc/config.xml和app/etc/local.xml文件中;
如是我们从index.php开始追踪代码的话,会看到如下内容(在1.9.*的index.php中未找到这部分内容,使用的是Mage::run($mageRunCode, $mageRunType)):
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(…);
或者
Mage::app() self::$_app = new Mage_Core_Model_App(); self::$_app-> init (…);
之后在两个版本中都有相似的类方法加载顺序。在Mage::run()中所有处理配置加载的内容都在Mage_Core_Model_App中并引用Mage_Core_Model_Config方法。调用Mage::app()会立即调用Mage_Core_Model_Config::init(),其中包含配置加载的进程。
最后会到达Mage_Core_Model_Config, 继承自Mage_Core_Model_Config_Base和Varien_Simplexml_Config。在这个层面调用方法无关紧要,让我们一起来看看Mage_Core_Model_Config:: init()方法(app/code/core/Mage/Core/Model/Config.php):
<?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; }
让我们来逐行分析一下这个方法吧
$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; }
在最前面定义了一具app/etc目录的绝对路径,然后获取了此目录下的.xml文件列表,读取其中的内容并并入一个simpleXmlElement对象中。如果local.xml文件被载入了的话(也就意味着Magento已经完成了安装),则设置$this->_isLocalConfigLoaded = true;这会在后面用于实例化店铺并加载模块设置脚本。
只要这个方法不限制名称和载入文件数量,我们就可以在需要时载入app/etc中的自定义.xml文件。在开发服务器上不通过local.xml文件来指定数据库连接信息会比较有帮助,只使用该文件包含生产服务器的信息
<?php $cacheLoad = $this->loadModulesCache(); if ($cacheLoad) { return $this; }
这部分代码不言自明,如果配置中激活了缓存并包含所要求的内容,就加载整个配置。我们还会用这一配置替换app/etc中已载入的配置,从缓存中载入(Mage_Core_Model_Config:: loadCache())并返回到Mage_Core_Model_App。
您可能会问如果所有的配置都能从缓存中载入,为什么不在扫描app/etc目录前就进和这一验证呢?因为Magento的缓存不仅可作为文件存放在var/cache中,也可以存放在apc, memcached和xcache中。从app/etc中载入的步骤允许设定用于存储缓存的类型和配置。
接下来是加载最为广泛的配置部分-模块配置。
$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; }
Day 3
$this->_loadDeclaredModules();
_getDeclaredModuleFiles(): 首先扫描app/etc/modules目录获取指向系统所有模块的.xml文件路径列表。建立一个以base, mage和custom为键名的关联数组,仅有Mage_All.xml路径指向base版块,而Magento的基础包(core代码池app/code/core/Mage下)指向mage版块,custom版块涵盖其它的模块。最终会将所有内容并入一个数组,由于前面按键名拆分,最终数据的存储顺序是: Mage_All.xml, Mage命名空间里的模块,其它所有模块。
如果还没有明白Mage_All.xml的重要性的话,现在应该查看其中的内部结构了。Mage_All.xml包含了所有保证系统正常运行需要加载的模块。
然后collected.xml文件会加载到Mage_Core_Model_Config_Base $unsortedConfig。就会获得一个$moduleDepends数组,它是基于<depends>, <active>标记和模块名称。
$this->_sortModuleDepends($moduleDepends)在查看现存模块间的依赖性时会用到,在验证后会形成一个按照模块依赖关系排序的新的数组。
在_loadDeclaredModules()的最后会再次创建一个simpleXmlElement对象,并合并入前面创建的app/etc/*.xml中。
下面我们再加到loadModules()方法中:
<?php $resourceConfig = sprintf('config.%s.xml', $this->_getResourceConnectionModel('core')); $this->loadModulesConfiguration(array('config.xml',$resourceConfig), $this);
$resourceConfig中包含 config.mysql4.xml内容,因而下一步会载入config.mysql4.xml的配置以及config.mysql4.xml文件本身,目前尚不清楚config.mysql4.xml具体有什么作用。
这个方法首先会检测是否可以从local代码池中加入模块(注意app/etc/local.xml中false的值是否为true)。如果只允许使用community和core中的模块,Magento也会相应地修改include_path()。
<?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') ); }
通过这个简短的验证,Magento确保有一些simpleXmlElement设置(没有的话Magento会自动创建),然后会删除载入的配置为false和local的模块。
接着config.xml中剩余的模块以及config.mysql4.xml文件会被加载,然后loaded .xml会被加入到现有的文件中。如果最后加载的文件包含老的xpath, 系统会使用最后的值。
以前用$this->applyExtends();的调用配合自定义插件来改变/重载配置中的数据,但后续版本中并未采用。如果您已看到这部分的话,请仔细阅读一下下面的代码吧:
<?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); }
不操作模块的话可以通过修改app/etc/local.xml中的数据,最后,调用$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; }
这个方法使用了和core_config_data table相关的Mage_Core_Model_Resource_Config model模型资源,在这步我们从core_config_data载入数据到配置中:
1. 向站点中加入数据(查看core_website table)
2. 为现有站点加入各商店(store)数据(查看core_store table)
3. 根据域(scope)向core_config_data中加入数据
a. 先创建<default>块
b. 再创建<websites>块
c. 最后创建<stores>块
d. 每次迭代把域中的基本数据替换成更具针对性的数据 (还记得后台中的«use default», «use website»和«configuration scope» 吗?)
4. 自身配置 (如果遇到与现有网站不相关的数据,会进行删除)。
紧接着如果使用了缓存的话就会向缓存中写入配置。现在配置已经生成,但除了编译.xml文件外,我们还经常要从中读取数据,那么让我们回到Mage.php中查看相关方法:
<?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; } }
两个方法的唯一差别是 getStoreConfig() 会返回具体的值,而getStoreConfigFlag() 仅返回布尔值true或false。两个方法最终都调用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); }
如果请求的信息无法在本地缓存中找到,该方法会使用stores/[store code]/[requested path]路径查找,如果依然没有结果,会使用另一个路径default/[requested path]在加载的配置中搜索。如果未查找到,则返回null。
查找到的数据由_processConfigValue()方法来进一步处理:
• 如果返回的是子节点,数据会循环存入本地缓存中。用户在当前会话再次调用时无需重新到配置中查找数据。
• 如果节点包含backend_model, 这个模型应按照指定格式获取数据。
• {{unsecure_base_url}}, {{unsecure_base_url}}, {{base_url}}的变量将由对应的数据进行替换。
最后总结一下
1. 所有.xml文件会汇集到一个大的simpleXmlElement 对象中
2. 数据首先从app/etc/*.xml中载入然后是app/etc/modules/*.xml。基于需加载模块的信息,会载入各模块etc目录下的config.xml。如果载入后台检测ACL权限并创建菜单元素,同时还会加载adminhtml.xml和system.xml。最后一步才是从数据库中载入配置数据。
3. 除开app/etc/local.xml中的其它所有参数都可以在自建模块的config.xml中进行重载。
P.S. 虽然Magento中提供了便捷的Mage:: getStoreConfig()和Mage:: getStoreConfigFlag()方法,我们还是可以通过Mage::getConfig()->getNode($path, $scope, $scopeCode);来获取配置树中的任意元素。
可通过在根目录下运行如下代码来查看Magento安装后的最终配置信息:
<?php header("Content-Type: text/xml"); define('MAGENTO_ROOT', getcwd()); $mageFilename = MAGENTO_ROOT . '/app/Mage.php'; require_once $mageFilename; umask(0); /* 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::app($mageRunCode, $mageRunType); $config = Mage::getConfig()->getNode()->asXml(); //file_put_contents('config.xml', $config); echo $config;
Describe class group configuration and use in factory methods
Magento通过工厂方法来实例化..
注:考虑到时间的紧迫性,决定内容在后续补入,目前仅录入学习进度,上班族伤不起啊!!!
Describe the process and configuration of class overrides in Magento
Register an Observer
Identify the function and proper use of automatically available events, including *_load_after, etc.
Day 4
Set up a cron job
Describe how to plan for internationalization of a Magento site
Describe the use of Magento translate classes and translate files
Describe the advantages and disadvantages of using subdomains and subdirectories in internationalization
Day 5
Describe the steps for application initialization
Describe the role of the system entrypoint, index.php
Describe the role of the front controller
Identify uses for events fired in the front controller
Day 6
Describe URL structure/processing in Magento
Describe the URL rewrite process
Describe request routing/request flow in Magento
Describe how Magento determines which controller to use and how to customize route-to-controller resolution
Day 7
Describe the steps needed to create and register a new module
Describe the effect of module dependencies
Describe different types of configuration files and the priorities of their loading
Identify the steps in the request flow in which
1. Design data is populated
2. Layout configuration files are parsed
3. Layout is compiled
4. Output is rendered