CI框架源码阅读笔记7 配置管理组件 Config.php,ciconfig.php
一个灵活可控的应用程序中,必然会存在大量的可控参数(我们称为配置),例如在CI的主配置文件中(这里指Application/Config/Config.php文件),有如下多项配置:
1 2 3 4 5 6 7 8 | <span> $config </span>[ 'base_url' ] = 'http://test.xq.com' <span>;
</span><span> $config </span>[ 'index_page' ] = '' <span>;
</span><span> $config </span>[ 'uri_protocol' ] = 'AUTO' <span>;
</span><span> $config </span>[ 'url_suffix' ] = '.html' <span>;
</span><span> $config </span>[ 'language' ] = 'english' <span>;
</span><span> $config </span>[ 'charset' ] = 'UTF-8' <span>;
</span><span> $config </span>[ 'enable_hooks' ] = <span>FALSE</span><span>;
…………………………</span>
|
Nach dem Login kopieren
不仅如此,CI还允许你将配置参数放到主配置文件之外。例如,你可以定义自己的配置文件为Config_app.php, 然后在你的应用程序控制器中这样加载你的配置文件:
1 | <span> $this </span>->config->load( 'config_app' );
|
Nach dem Login kopieren
如此纷繁多样的配置项和配置文件,CI是如何进行管理的?这便是我们今天要跟踪的内容:CI的配置管理组件-Config.php.
先看该组件的类图:
其中:
_config_paths:要搜索的配置文件的路径,这里指APPPATH目录,你的配置文件也应该位于APPPATH下。
Config: 这个数组用于存放所有的配置项的item
Is_loaded: 存放所有的已经加载的配置文件列表。
_construct: 组件的构造函数,主要是配置base_url
_assign_to_config: 允许index.php中的配置项覆盖主配置文件中的设置
_uri_string,site_url,base_url,system_url: URI, 项目路径等相关处理。
load: 加载配置文件。
item:获取配置项
slash_item:同item,不同的是,在最后加了”\”分隔符,一般只有site_url,base_url等会需要slash_item
下面我们去剖析各个方法的具体实现:
1. 组件初始化 _construct
之前我们在分析Common.php全局函数的时候提到过,在Config组件实例化之前,所有的组配置文件的获取都是由get_config()函数来代理的。在Config组件实例化时,要将所有的配置存放到自己的私有变量$config中,便于之后的访问和处理:
1 | $this ->config =& get_config();
|
Nach dem Login kopieren
由于我们应用程序很多时候需要获取base_url的值,而这个值并不是必填项(config中base_url可以设置为空),但我们又不希望获取到的base_url的值为空。因此,CI在Config组件初始化的时候,对base_url做了一定的处理。这主要出现在Config.php中base_url设置为空的情况:
(1). 如果设置了$_SERVER[‘HTTP_HOST’],则base_url被设置为Protocal(http或者https) + $_SERVER['HTTP_HOST'] + SCIRPT_PATH的形式:
1 2 3 | $base_url = isset( $_SERVER [ 'HTTPS' ]) && strtolower ( $_SERVER [ 'HTTPS' ]) !== 'off' ? 'https' : 'http' ;
$base_url .= '://' . $_SERVER [ 'HTTP_HOST' ];
$base_url .= str_replace ( basename ( $_SERVER [ 'SCRIPT_NAME' ]), '' , $_SERVER [ 'SCRIPT_NAME' ]);
|
Nach dem Login kopieren
(2). 否者,直接被设置为http://localhost/:
1 | $base_url = 'http://localhost/' ;
|
Nach dem Login kopieren
(3). 同时将base_url配置项映射到配置数组中,方便之后的访问(set_item方法我们稍后会将,这里只需要知道,它是添加到配置项且会覆盖旧值):
1 | $this ->set_item( 'base_url' , $base_url );
|
Nach dem Login kopieren
之后我们会看到,base_url这个配置项对于很多组件都是必须的,因此,CI花费一定的精力来保证base_url的正确性,也是可以理解的。
2. 加载配置文件 load
这是Config组件中较核心的方法之一,该函数的签名:
1 | <span> function </span> load(<span> $file </span> = '' , <span> $use_sections </span> = <span>FALSE</span>, <span> $fail_gracefully </span> = <span>FALSE</span>)
|
Nach dem Login kopieren
所有的参数都是可选参数。
我们这里简单解释一下各形参的含义:
$file 需要加载的配置文件,可以包含后缀名也不可以不包含,如果未指定该参数,则默认加载Config.php文件
$user_sections: 是否为加载的配置文件使用独立的section,这么说可能还是不明白,试想,如果你定义了自己的配置文件,而你的配置文件中的配置项可能与Config.php文件中的配置项冲突,通过指定$section为true可以防止配置项的覆盖。
$fail_gracefully: 要load的配置文件不存在时的处理。Gracefully意为优雅的,如果该参数设置为true,则在文件不存在时只会返回false,而不会显示错误。
下面看该方法的具体实现:
(1). 配置文件名预处理:
1 | <span> $file </span> = (<span> $file </span> == '' ) ? 'config' : <span> str_replace </span>( '.php' , '' , <span> $file </span>);
|
Nach dem Login kopieren
这个$file最后只包含文件名,而不包含扩展名。如果该参数为空,则默认加载Config.php配置文件。这同时也说明,我们加载自己的配置文件时:
$this->config->load("");与
$this->config->load("config")效果是一样的,而:
$this->config->load("config_app")与
$this->config->load("config_app.php")的效果也是一样的。
如果启用了$use_sections,这个$file会作为config的主键。
(2). 查找和加载配置文件。
在跟踪实现之前,先解释几个查找和加载过程中比较重要的参数:
(3).具体的查找过程是一个双重的foreach循环:
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 | foreach ( $this ->_config_paths as $path )
{
foreach ( $check_locations as $location )
{
$file_path = $path . 'config/' . $location . '.php' ;
<br />
if (in_array( $file_path , $this ->is_loaded, TRUE))
{
$loaded = TRUE;
continue 2;
}
if ( file_exists ( $file_path ))
{
$found = TRUE;
break ;
}
}
if ( $found === FALSE)
{
continue ;
}
}
|
Nach dem Login kopieren
(4).引入配置文件
到这里,如果配置文件不存在,则$found和$loaded都为false,CI会根据fail_gracefully参数决定文件不存在的处理方式;如果文件存在,则需要对配置文件的格式检查:
1 2 3 4 5 6 7 8 9 10 11 12 | include ( $file_path );
if ( ! isset( $config ) OR ! is_array ( $config ))
{
if ( $fail_gracefully === TRUE)
{
return FALSE;
}
show_error( 'Your ' . $file_path . ' file does not appear to contain a valid configuration array.' );
}
|
Nach dem Login kopieren
(5).对use_sections参数的处理
前面说过,use_secitons参数如果为true,则CI_Config会对该配置文件启用独立的key存储。例如,我们在controller中这样加载配置文件:
1 | <span> $this </span>->config->load( "config_app" ,<span>true</span>);
|
Nach dem Login kopieren
则config数组是这样的格式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | [config] => <span>Array</span><span>
(
[base_url] </span>=> http:<span>
[index_page] =><span>
[uri_protocol] </span>=><span> AUTO
[url_suffix] </span>=> .<span>html
[proxy_ips] </span>=><span>
[web_akey] </span>=><span> yyyyyyyyyyyy
[config_app] </span>=> <span>Array</span><span>
(
[web_akey] </span>=><span> xxxxxxx
[web_skey] </span>=><span> xxxxxxxxxxxxxxxxxxx
[web_callback_url] </span>=> http:<span>
[sess_pre] =><span> WEB_APP
[cart_min] </span>=> 1<span>
[cart_max] </span>=> 999<span>
)
)</span>
|
Nach dem Login kopieren
相反,如果我们不指定use_sections,则数组是这样存储的:
1 2 3 4 5 6 7 8 9 10 11 12 13 | [config] => <span>Array</span><span>
(
[base_url] </span>=> http:<span>
[index_page] =><span>
[uri_protocol] </span>=><span> AUTO
[url_suffix] </span>=> .<span>html
[web_akey] </span>=><span> xxxxxxx
[web_skey] </span>=><span> xxxxxxxxxxxxxxxxxxx
[web_callback_url] </span>=> http:<span>
[sess_pre] =><span> WEB_APP
[cart_min] </span>=> 1<span>
[cart_max] </span>=> 999<span>
)</span>
|
Nach dem Login kopieren
这也意味着,在不启用user_secitons的情况下,如果你的配置文件中有与主配置文件Config.php相同的键,则会覆盖主配置文件中的项:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | if ( $use_sections === TRUE)
{
if (isset( $this ->config[ $file ]))
{
$this ->config[ $file ] = array_merge ( $this ->config[ $file ], $config );
}
else
{
$this ->config[ $file ] = $config ;
}
}
else
{
$this ->config = array_merge ( $this ->config, $config );
}
|
Nach dem Login kopieren
(6).错误处理
双层循环完成后,如果loaded为false,也就是未成功加载任何配置,则根据fail_gracefully做相应的错误处理:
1 2 3 4 5 6 7 8 9 | if ( $loaded === FALSE)
{
if ( $fail_gracefully === TRUE)
{
return FALSE;
}
show_error( 'The configuration file ' . $file . '.php does not exist.' );
}
|
Nach dem Login kopieren
3. 获取配置项item,slash_item
item方法用于在配置中获取特定的配置项,改方法的签名:
1 | <span> function </span> item(<span> $item </span>, <span> $index </span> = '' )
|
Nach dem Login kopieren
注意,如果你在load配置文件的时候启用了use-sections,则在使用item()获取配置项的时候需要指定第二个参数,也就是加载的配置文件的文件名(不包含后缀)。为了更清楚这一点,我们假设现在Config/目录下有配个配置文件:config.php和config_app.php,这两个配置文件中含有一个相同的键web_akey, 在config.php中,该配置为:
1 | <span> $config </span>[ 'web_akey' ] = 'yyyyyyyyyyyy' ;
|
Nach dem Login kopieren
而config_app.php中,该配置为:
1 | <span> $config </span>[ 'web_akey' ] = 'xxxxxxx' ;
|
Nach dem Login kopieren
现在,通过use-sections的方法加载config_app配置文件(config.php会在Config组件初始化的时候被加载):
1 | $this ->config->load( "config_app" ,true);
|
Nach dem Login kopieren
然后在控制器中获取web_akey配置项:
1 2 | echo "config_app:web_akey => " , $this ->config->item( "web_akey" , "config_app" ), "<br/>" ;
echo "config :web_akey => " , $this ->config->item( "web_akey" );
|
Nach dem Login kopieren
实际的获取结果:
1 2 | config_app:web_akey =><span> xxxxxxx
config </span>:web_akey => yyyyyyyyyyyy
|
Nach dem Login kopieren
了解原理之后,该方法的实现就比较简单了:
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 | function item( $item , $index = '' )
{
if ( $index == '' )
{
if ( ! isset( $this ->config[ $item ]))
{
return FALSE;
}
$pref = $this ->config[ $item ];
}
else
{
if ( ! isset( $this ->config[ $index ]))
{
return FALSE;
}
if ( ! isset( $this ->config[ $index ][ $item ]))
{
return FALSE;
}
$pref = $this ->config[ $index ][ $item ];
}
return $pref ;
}
|
Nach dem Login kopieren
slash_item实际上与item()方法类似,但他不会去用户的配置中寻找,并且,他返回的是主配置文件中的配置项,并在配置项最后添加反斜杠.这个方法,通常用于base_url和index_page这两个配置项的处理:
该方法的实现源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | function slash_item( $item )
{
if ( ! isset( $this ->config[ $item ]))
{
return FALSE;
}
if ( trim( $this ->config[ $item ]) == '' )
{
return '' ;
}
return rtrim( $this ->config[ $item ], '/' ). '/' ;
}
|
Nach dem Login kopieren
4. 获取站点site_url, base_url,system_url
这里先澄清这几个含义的区别:
1 2 3 | echo "site_url : " , $this ->config->site_url( "index/rain" ), "</br>" ;
echo "base_url : " , $this ->config->base_url( "index/rain" ), "<br/>" ;
echo "system_url: " , $this ->config->system_url();
|
Nach dem Login kopieren
的结果分别是:
1 2 3 | <span>site_url : http:
base_url : http:
system_url: http:
|
Nach dem Login kopieren
可以看出,site_url是添加了suffix(在Config/config.php中配置)后的url地址(呵呵,如果你的uri中有query string,则Ci总是在最后添加suffix:http://test.xq.com/index/rain?w=ss.html 是不是很奇怪.)
base_url则是没有添加suffix的url地址。
而system_url这个东西很奇怪,是获取系统的url路径。但实际上,由于system路径并没有直接执行的脚本,所以这个方法的实际用途是什么,暂时不知。有知道的童鞋麻烦告知。
具体的方法实现,这里不赘述了。直接贴出源码:
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 34 | function site_url( $uri = '' )
{
if ( $uri == '' )
{
return $this ->slash_item( 'base_url' ). $this ->item( 'index_page' );
}
if ( $this ->item( 'enable_query_strings' ) == FALSE)
{
$suffix = ( $this ->item( 'url_suffix' ) == FALSE) ? '' : $this ->item( 'url_suffix' );
return $this ->slash_item( 'base_url' ). $this ->slash_item( 'index_page' ). $this ->_uri_string( $uri ). $suffix ;
}
else
{
return $this ->slash_item( 'base_url' ). $this ->item( 'index_page' ). '?' . $this ->_uri_string( $uri );
}
}
function base_url( $uri = '' )
{
return $this ->slash_item( 'base_url' ).ltrim( $this ->_uri_string( $uri ), '/' );
}
function system_url()
{
$x = explode ( "/" , preg_replace( "|/*(.+?)/*$|" , "\\1" , BASEPATH));
return $this ->slash_item( 'base_url' ). end ( $x ). '/' ;
}
|
Nach dem Login kopieren
5. 获取URI String: _uri_string
site_url和base_url都调用了_uri_string。这个函数是做什么用的呢?
按理来说, _uri_string的功能应该由URI组件来完成,这里却放在了Config组件中,似乎有些不妥(实际上,_uri_string是为base_url和site_url专属服务的)。
对于这样的uri:
1 2 3 4 | <span> array (
'p1' </span>=> 'param1' ,<span>
'p2' </span>=<span>> 'param2'
)</span>
|
Nach dem Login kopieren
如果enable_query_string为false,则_uri_string处理过后是这样的形式:
而enable_query_string为true,则处理后的形式是这样的:
这是我们常见(虽然很难看且SEO不好)的形式。改方法的实现源码:
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 | protected function _uri_string( $uri )
{
if ( $this ->item( 'enable_query_strings' ) == FALSE)
{
if ( is_array ( $uri ))
{
$uri = implode( '/' , $uri );
}
$uri = trim( $uri , '/' );
}
else
{
if ( is_array ( $uri ))
{
$i = 0;
$str = '' ;
foreach ( $uri as $key => $val )
{
$prefix = ( $i == 0) ? '' : '&' ;
$str .= $prefix . $key . '=' . $val ;
$i ++;
}
$uri = $str ;
}
}
return $uri ;
}
|
Nach dem Login kopieren
6. 设置配置项 set_item _assign_to_config
与item()相反,set_item用于设置配置项。如果配置项已经存在,则会被覆盖:
1 | $this ->config[ $item ] = $value ;
|
Nach dem Login kopieren
_assign_to_config同set_item,该方法提供了数组的设置方式(调用set_item。我们之前在解释CodeIgniter.php文件的时候提到过:改方法允许在index.php中设置独立的配置项,且index.php中的配置具有更高的优先权(会覆盖主配置文件中的配置):
1 2 3 4 5 6 7 8 9 10 | function _assign_to_config( $items = array ())
{
if ( is_array ( $items ))
{
foreach ( $items as $key => $val )
{
$this ->set_item( $key , $val );
}
}
}
|
Nach dem Login kopieren
到这里,Config组件的基本解析就算是完成了,我们再次回顾下该组件的基本功能:
最后感慨一下,一个好的Config组件,会省不少事啊。