注:后台搜索到两篇比较好的介绍这个主题的文章:
http://alanstorm.com/magento_config_tutorial
http://alanstorm.com/magento_config_declared_modules_tutorial
主要类是Mage_Core_Model_Config,先看它们的继承关系:
1
2
3
4
5
6
7
8
9
10
11
12
|
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
|
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
|
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
|
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
|
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
|
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
|
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,这个对应系统是否能继续往下运行提供了开关设置。
另,它们中的继承关系:
这里其实已经涉及到了配置相关内容的大部分了。不过,我们继续回到Mage_Core_Model_App类的run()方法中,它在运行了$this->baseInit($options)后接下来会运行$this->_initModules(),看下这个方法吧:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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
|
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
|
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(
'<config/>'
);
}
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文档树中。本文就不深入跟踪下去了。
永久连接: http://blog.ifeeline.com/411.html