Magento的配置对象与全局XML配置文件构建

注:后台搜索到两篇比较好的介绍这个主题的文章:

http://alanstorm.com/magento_config_tutorial

http://alanstorm.com/magento_config_declared_modules_tutorial

主要类是Mage_Core_Model_Config,先看它们的继承关系:
Magento配置类继承结构

Magento配置类继承结构

1
2
3
4
5
6
7
8
9
10
11
12
13

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

Mage_Core_Model_Config_Base 只是修改了继承过来的_elementClass属性,基本是原始的Varien_Simplexml_Config对象。注意,Mage_Core_Model_Config_Element继承自Varien_Simplexml_Element,这个也是继承过来的方法能用的保证(因为Varien_Simplexml_Config很多方法的参数类型是Varien_Simplexml_Element)。

由于Magento实际实例化的是Mage_Core_Model_Config类,看它的构造函数:

1
2
3
4
5
6
7
8
9

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

它用_options保存了一份Mage_Core_Model_Config_Options实例,用_prototype保存了它的父类的一份实例。

这里关键是Mage_Core_Model_Config_Options类实例(它直接继承自Varien_Object类),看它的构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

protected function _construct()
{
$appRoot= Mage::getRoot(); //就是…/public/app目录
$root = dirname($appRoot); //就是…/public

$this->_data['app_dir'] = $appRoot;
$this->_data['base_dir'] = $root;
$this->_data['code_dir'] = $appRoot.DS.'code';
$this->_data['design_dir'] = $appRoot.DS.'design';
$this->_data['etc_dir'] = $appRoot.DS.'etc';
$this->_data['lib_dir'] = $root.DS.'lib';
$this->_data['locale_dir'] = $appRoot.DS.'locale';
$this->_data['media_dir'] = $root.DS.'media';
$this->_data['skin_dir'] = $root.DS.'skin';
$this->_data['var_dir'] = $this->getVarDir();
$this->_data['tmp_dir'] = $this->_data['var_dir'].DS.'tmp';
$this->_data['cache_dir'] = $this->_data['var_dir'].DS.'cache';
$this->_data['log_dir'] = $this->_data['var_dir'].DS.'log';
$this->_data['session_dir'] = $this->_data['var_dir'].DS.'session';
$this->_data['upload_dir'] = $this->_data['media_dir'].DS.'upload';
$this->_data['export_dir'] = $this->_data['var_dir'].DS.'export';
}

一系列的目录信息被保存到了_data数组中。这个类的方法就是获取这些不同的目录。所以当调用Mage_Core_Model_Config类的getOptions方法时就是返回这个Options的引用,而它就是保存了一些的目录。

而对Mage_Core_Model_Config的首次调用是在是在Mage_Core_Model_App类的run()方法中,在这个方法中第一步是运行$this->baseInit($options)方法,这个方法内部最关键的一行代码是$this->_initBaseConfig()方法(还有$this->_initCache($cacheInitOptions)):

1
2
3
4
5
6
7
8

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

看到了吧,$this->_config->loadBase(),它负责加载基础配置文件,同时也是Mage_Core_Model_Config这个对象第一次获取填充($this->_config返回已经保存到APP中的Mage_Core_Model_Config对象的实例),马上,我们看看Mage_Core_Model_Config的loadBase()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

public function loadBase()
{
$etcDir = $this->getOptions()->getEtcDir(); //这个获得app/etc目录
$files = glob($etcDir.DS.'*.xml'); //把app/etc目录下的xml文件用数组返回(不包含子目录的XML文件)
$this->loadFile(current($files)); //把第一个xml文件用loadFile读入
while ($file = next($files)) {
$merge = clone $this->_prototype; //克隆一份配置对象
$merge->loadFile($file); //把第二(第三第四…)个xml读入当前克隆的配置对象
$this->extend($merge); //把它们合并
}
if (in_array($etcDir.DS.'local.xml', $files)) {//如果app/etc/local.xml已经读入,标志一下
$this->_isLocalConfigLoaded = true;
}
return $this;
}

很自然,我想看看loadFie如果load xml:

1
2
3
4
5
6
7
8
9
10
11
12

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); //直接把$fileData返回
return $this->loadString($fileData, $this->_elementClass);
}

Wow, 直接使用file_get_contents读入文件内容。然后返回loadString处理后的内容,注意第二参数$this->_elementClass,它在当前代码里值被替换为Mage_Core_Model_Config_Element。继续看loadString函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

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

Wow,直接使用simplexml_load_string把xml字符串加载到Mage_Core_Model_Config_Element的对象中(simplexml_load_string() 的第二参数可以指定一个类,这个类必须是继承SimpleXMLElement类,这样函数将返回这个类实例。)

然后看生成的这个$xml对象是不是Varien_Simplexml_Element的实例(当然是了,因为$xml是Mage_Core_Model_Config_Element类型,它继承自Varien_Simplexml_Element),如果是就把它保存到Mage_Core_Model_Config类对象的_xml中。

XML就这样读进来了,其它XML也是这样读进来的,回到那段XML合并的代码,它调用extend方法,实际在其内部是调用_xml对象的extend方法,合并细节只要跟踪进去就可以知晓,不过我想我了解到这里已经可以了。

这里先总结一下:

Mage_Core_Model_Config
$_options 保存了Mage_Core_Model_Config_Options对象,主要记录了系统相关的目录
$_xml 保存了Mage_Core_Model_Config_Element对象,完成基础配置的加载后就合并了app/etc/local.xml和app/etc/config.xml文件的内容(后续会继续合并其它配置)。
$_ isLocalConfigLoaded 在local.xml加载后就被标志位true,这个对应系统是否能继续往下运行提供了开关设置。
另,它们中的继承关系:
Magento的XML类继承结构

Magento的XML类继承结构
这里其实已经涉及到了配置相关内容的大部分了。不过,我们继续回到Mage_Core_Model_App类的run()方法中,它在运行了$this->baseInit($options)后接下来会运行$this->_initModules(),看下这个方法吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

protected function _initModules()
{
if (!$this->_config->loadModulesCache()) {
$this->_config->loadModules();
if ($this->_config->isLocalConfigLoaded() && !$this->_shouldSkipProcessModulesUpdates()) {
Varien_Profiler::start('mage::app::init::apply_db_schema_updates');
Mage_Core_Model_Resource_Setup::applyAllUpdates();
Varien_Profiler::stop('mage::app::init::apply_db_schema_updates');
}
$this->_config->loadDb();
$this->_config->saveCache();
}
return $this;
}

在模块缓存没有获取到的情况下,它调用配置对象的loadModules()方法。所以我们在这里继续深入进去,我们马上进入Mage_Core_Module_Config的loadModules()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

public function loadModules()
{
Varien_Profiler::start('config/load-modules');
//先调用_getDeclaredModules()函数,到app/etc/modules里面取出所有XML文件,分成三类,返回array('base'=>对应Mage_All文件,'mage'=>对应Mage_开头的文件,'custom'=>其它自定义模块),然后合并,然后检查依赖关系,最后合并到配置文件的_xml中(全局配置)
$this->_loadDeclaredModules();
//获取资源链接模型 最终得到的串是mysql4,这个在这里没有作用
$resourceConfig = sprintf('config.%s.xml', $this->_getResourceConnectionModel('core'));
//把所有模块的的config.xml都合并进来
$this->loadModulesConfiguration(array('config.xml',$resourceConfig), $this);

//再加载一次app/etc/local.xml,防止被覆盖,这里说明所有模块的配置文件都加载了
$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->loadModulesConfiguration()方法,它展示了模块配置文件合并的细节:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

public function loadModulesConfiguration($fileName, $mergeToObject = null, $mergeModel=null)
{
//_canUseLocalModules()获取配置global/disable_local_modules的值,这是一个禁止使用本地模块的开关
$disableLocalModules = !$this->_canUseLocalModules();

if ($mergeToObject === null) {
$mergeToObject = clone $this->_prototype;
$mergeToObject->loadString('');
}
if ($mergeModel === null) {
$mergeModel = clone $this->_prototype;
}
$modules = $this->getNode('modules')->children();
foreach ($modules as $modName=>$module) {
if ($module->is('active')) {
if ($disableLocalModules && ('local' === (string)$module->codePool)) {
continue;
}
if (!is_array($fileName)) {
$fileName = array($fileName);
}

foreach ($fileName as $configFile) {
$configFile = $this->getModuleDir('etc', $modName).DS.$configFile;
if ($mergeModel->loadFile($configFile)) {
$mergeToObject->extend($mergeModel, true);
}
}
}
}
return $mergeToObject;
}

Mage_Core_Model_App类的run()方法中接下来运行$this->_config->loadDb(),这个运行完毕后,全局的巨大的配置文档树就构建起来了。loadDb()是从数据库加载配置到这个XML文档树中。本文就不深入跟踪下去了。

坚持原创技术分享,您的支持将鼓励我继续创作!