一个灵活可控的应用程序中,必然会存在大量的可控参数(我们称为配置),例如在CI的主配置文件中(这里指Application/Config/Config.php文件),有如下多项配置:

$config['base_url']   = 'http://test.xq.com';
$config['index_page'] = '';
$config['uri_protocol']     = 'AUTO';
$config['url_suffix'] = '.html';
$config['language']  = 'english';
$config['charset'] = 'UTF-8';
$config['enable_hooks'] = FALSE;
…………………………

不仅如此,CI还允许你将配置参数放到主配置文件之外。例如,你可以定义自己的配置文件为Config_app.php, 然后在你的应用程序控制器中这样加载你的配置文件:

$this->config->load('config_app');

如此纷繁多样的配置项和配置文件,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中,便于之后的访问和处理:

$this->config =& get_config();

由于我们应用程序很多时候需要获取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的形式:

$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']);

(2).    否者,直接被设置为http://localhost/:

$base_url = 'http://localhost/';

(3).    同时将base_url配置项映射到配置数组中,方便之后的访问(set_item方法我们稍后会将,这里只需要知道,它是添加到配置项且会覆盖旧值):

$this->set_item('base_url', $base_url);

之后我们会看到,base_url这个配置项对于很多组件都是必须的,因此,CI花费一定的精力来保证base_url的正确性,也是可以理解的。

2.  加载配置文件 load

这是Config组件中较核心的方法之一,该函数的签名:

function load($file = '', $use_sections = FALSE, $fail_gracefully = FALSE)

所有的参数都是可选参数。

我们这里简单解释一下各形参的含义:

  $file 需要加载的配置文件,可以包含后缀名也不可以不包含,如果未指定该参数,则默认加载Config.php文件

  $user_sections: 是否为加载的配置文件使用独立的section,这么说可能还是不明白,试想,如果你定义了自己的配置文件,而你的配置文件中的配置项可能与Config.php文件中的配置项冲突,通过指定$section为true可以防止配置项的覆盖。

  $fail_gracefully: 要load的配置文件不存在时的处理。Gracefully意为优雅的,如果该参数设置为true,则在文件不存在时只会返回false,而不会显示错误。

下面看该方法的具体实现:

(1). 配置文件名预处理:

$file = ($file == '') ? 'config' : str_replace('.php', '', $file);

这个$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).    查找和加载配置文件。

在跟踪实现之前,先解释几个查找和加载过程中比较重要的参数:

  1. $found  这个参数实际上是个flag,用于标识配置文件是否查找到,一旦查找到配置文件,则停止任何搜索。
  2. $loaded  同$found参数类似,这个$loaded也是一个flag,用于标识请求的配置文件是否被加载。一般情况下,被加载的配置文件会被CI_Config:: is_loaded变量追踪
  3. $_config_path  要查找的配置路径,这个变量由于是写死在Config组件中的,且没有提供添加或者更改的接口。因此我们可以认为_config_path就是APPPATH.也就是,配置文件的load一定是在APPPATH目录下查找的。
  4. $check_locations  这个参数是要查找的位置(具体文件)。同样,如果定了ENVIRONMENT且存在相应ENVIRONMENT下的配置文件,优先加载该文件。

(3).具体的查找过程是一个双重的foreach循环:

/*  对于config_paths中的路径循环查找 */
foreach ($this->_config_paths as $path)
{   /* 对每个location查找,也就是分别对ENVIRONMENT/config/ 和 config/ 目录查找  */foreach ($check_locations as $location){/* 实际的配置文件名 */$file_path = $path.'config/'.$location.'.php'; /* 如果已经加载,则跳至最外层循环,事实上,由于_config_paths的设定,会跳出整个循环 */if (in_array($file_path, $this->is_loaded, TRUE)){$loaded = TRUE;continue 2;}/* 若文件存在,跳出当前循环 */if (file_exists($file_path)){$found = TRUE;break;}}/* 如果没有找到配置文件,继续下一次循环。同样,由于_config_path的设定,会跳出整个循环 */if ($found === FALSE){continue;}
}

(4).引入配置文件

到这里,如果配置文件不存在,则$found和$loaded都为false,CI会根据fail_gracefully参数决定文件不存在的处理方式;如果文件存在,则需要对配置文件的格式检查:

/* 引入配置文件 */
include($file_path);/* 配置文件的格式检查,这同时也说明,配置文件中最起码应该包含$config数组 */
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.');
}

(5).对use_sections参数的处理

前面说过,use_secitons参数如果为true,则CI_Config会对该配置文件启用独立的key存储。例如,我们在controller中这样加载配置文件:

$this->config->load("config_app",true);

则config数组是这样的格式:

[config] => Array
([base_url] => http://test.xq.com[index_page] =>[uri_protocol] => AUTO[url_suffix] => .html[proxy_ips] =>[web_akey] => yyyyyyyyyyyy[config_app] => Array([web_akey] => xxxxxxx[web_skey] => xxxxxxxxxxxxxxxxxxx[web_callback_url] => http://test.xq.com/[sess_pre] => WEB_APP[cart_min] => 1[cart_max] => 999)
)

相反,如果我们不指定use_sections,则数组是这样存储的:

[config] => Array
([base_url] => http://test.xq.com[index_page] =>[uri_protocol] => AUTO[url_suffix] => .html[web_akey] => xxxxxxx[web_skey] => xxxxxxxxxxxxxxxxxxx[web_callback_url] => http://test.xq.com/[sess_pre] => WEB_APP[cart_min] => 1[cart_max] => 999
)

这也意味着,在不启用user_secitons的情况下,如果你的配置文件中有与主配置文件Config.php相同的键,则会覆盖主配置文件中的项:

/* 启用单独的key存放加载的config */
if ($use_sections === TRUE)
{if (isset($this->config[$file])){$this->config[$file] = array_merge($this->config[$file], $config);}else{$this->config[$file] = $config;}
}
else
{/* 执行merge,更改CI_Config::config */$this->config = array_merge($this->config, $config);
}

(6).错误处理

双层循环完成后,如果loaded为false,也就是未成功加载任何配置,则根据fail_gracefully做相应的错误处理:

/* 未成功加载任何配置 */
if ($loaded === FALSE)
{if ($fail_gracefully === TRUE){return FALSE;}show_error('The configuration file '.$file.'.php does not exist.');
}

3.  获取配置项item,slash_item

item方法用于在配置中获取特定的配置项,改方法的签名:

function item($item, $index = '')

注意,如果你在load配置文件的时候启用了use-sections,则在使用item()获取配置项的时候需要指定第二个参数,也就是加载的配置文件的文件名(不包含后缀)。为了更清楚这一点,我们假设现在Config/目录下有配个配置文件:config.php和config_app.php,这两个配置文件中含有一个相同的键web_akey, 在config.php中,该配置为:

$config['web_akey']  = 'yyyyyyyyyyyy';

而config_app.php中,该配置为:

$config['web_akey'] = 'xxxxxxx';

现在,通过use-sections的方法加载config_app配置文件(config.php会在Config组件初始化的时候被加载):

$this->config->load("config_app",true);

然后在控制器中获取web_akey配置项:

echo "config_app:web_akey => ",$this->config->item("web_akey","config_app"),"<br/>";
echo "config    :web_akey => ",$this->config->item("web_akey");

实际的获取结果:

config_app:web_akey => xxxxxxx
config :web_akey => yyyyyyyyyyyy

了解原理之后,该方法的实现就比较简单了:

function item($item, $index = '')
{   /* 没有设置use_sections的情况,直接在config中寻找配置项 */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出口 */return $pref;
}

slash_item实际上与item()方法类似,但他不会去用户的配置中寻找,并且,他返回的是主配置文件中的配置项,并在配置项最后添加反斜杠.这个方法,通常用于base_url和index_page这两个配置项的处理:

该方法的实现源码:

function slash_item($item)
{   /* 不存在配置项 */if ( ! isset($this->config[$item])){return FALSE;}/* 配置项为空 */if( trim($this->config[$item]) == ''){return '';}/* 去除最后的多余的"/",并在结尾添加一个"/" */return rtrim($this->config[$item], '/').'/';
}

4.  获取站点site_url, base_url,system_url

这里先澄清这几个含义的区别:

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

的结果分别是:

site_url : http://test.xq.com/index/rain.html
base_url : http://test.xq.com/index/rain
system_url: http://test.xq.com/system/

可以看出,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路径并没有直接执行的脚本,所以这个方法的实际用途是什么,暂时不知。有知道的童鞋麻烦告知。

具体的方法实现,这里不赘述了。直接贴出源码:

function site_url($uri = '')
{/* 没有设置uri,使用base_url + index_page */if ($uri == ''){return $this->slash_item('base_url').$this->item('index_page');}/* enable_query_strings未启用,可以添加suffix后缀 */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;}/* 否者不添加suffix后缀 */else{return $this->slash_item('base_url').$this->item('index_page').'?'.$this->_uri_string($uri);}
}/* 获取base_url,注意与site_url的区别 */
function base_url($uri = '')
{return $this->slash_item('base_url').ltrim($this->_uri_string($uri), '/');
}/* 获取system url */
function system_url()
{/* 获取系统目录.   BASEPATH:/search/xx/phpCode/CI/system/ */$x = explode("/", preg_replace("|/*(.+?)/*$|", "\\1", BASEPATH));return $this->slash_item('base_url').end($x).'/';
}

5.  获取URI String: _uri_string

site_url和base_url都调用了_uri_string。这个函数是做什么用的呢?

按理来说, _uri_string的功能应该由URI组件来完成,这里却放在了Config组件中,似乎有些不妥(实际上,_uri_string是为base_url和site_url专属服务的)。

对于这样的uri:

array('p1' => 'param1','p2' => 'param2'
)

如果enable_query_string为false,则_uri_string处理过后是这样的形式:

param1/param2

而enable_query_string为true,则处理后的形式是这样的:

p1=param1&p2=param2

这是我们常见(虽然很难看且SEO不好)的形式。改方法的实现源码:

protected function _uri_string($uri)
{   /* enable_query_strings 为false,直接implode */if ($this->item('enable_query_strings') == FALSE){if (is_array($uri)){$uri = implode('/', $uri);}$uri = trim($uri, '/');}/* 否者,拼接成类似param1=param1&param2=param2的形式 */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;
}

6.  设置配置项 set_item  _assign_to_config

与item()相反,set_item用于设置配置项。如果配置项已经存在,则会被覆盖:

$this->config[$item] = $value;

_assign_to_config同set_item,该方法提供了数组的设置方式(调用set_item。我们之前在解释CodeIgniter.php文件的时候提到过:改方法允许在index.php中设置独立的配置项,且index.php中的配置具有更高的优先权(会覆盖主配置文件中的配置):

function _assign_to_config($items = array())
{if (is_array($items)){foreach ($items as $key => $val){$this->set_item($key, $val);}}
}

到这里,Config组件的基本解析就算是完成了,我们再次回顾下该组件的基本功能:

  1. set_item和item是Config组件的基本对外接口。也就是常见的setter 和getter,_assign_to_config算是批量的setter,slash_item则是特殊处理的getter
  2. load方法是加载配置文件,如果你自定义了自己的配置文件,需要先load使得你的配置纳入CI_Config的管理之下。
  3. system_url,base_url,site_url,用于获取特定的配置项。
  4. _uri_string是CI_Config中唯一一个Protected的方法。这个方法主要是处理uri,提供给site_url和base_url使用

最后感慨一下,一个好的Config组件,会省不少事啊。

转载于:https://www.cnblogs.com/ohmygirl/p/CIRead-7.html

CI框架源码阅读笔记7 配置管理组件 Config.php相关推荐

  1. ****CI框架源码阅读笔记7 配置管理组件 Config.php

    http://blog.csdn.net/ohmygirl/article/details/41041597 一个灵活可控的应用程序中,必然会存在大量的可控参数(我们称为配置),例如在CI的主配置文件 ...

  2. CI框架源码阅读笔记4 引导文件CodeIgniter.php

    到了这里,终于进入CI框架的核心了.既然是"引导"文件,那么就是对用户的请求.参数等做相应的导向,让用户请求和数据流按照正确的线路各就各位.例如,用户的请求url: http:// ...

  3. CI框架源码阅读笔记8 控制器Controller.php

    最近时间有些紧,源码阅读系列更新有些慢.鉴于Controller中代码比较少,本次Blog先更新该文件的源码分析. 在经过路由分发之后,实际的应用Controller接管用户的所有请求,并负责与用户数 ...

  4. CI框架源码学习笔记7——Utf8.php

    愉快的清明节假期结束了,继续回到CI框架学习.这一节我们来看看Utf8.php文件,它主要是用来做utf8编码,废话不多说,上代码. class CI_Utf8 {/*** Class constru ...

  5. 【Flink】Flink 源码阅读笔记(15)- Flink SQL 整体执行框架

    1.概述 转载:Flink 源码阅读笔记(15)- Flink SQL 整体执行框架 在数据处理领域,无论是实时数据处理还是离线数据处理,使用 SQL 简化开发将会是未来的整体发展趋势.尽管 SQL ...

  6. Yii源码阅读笔记 - 日志组件

    2015-03-09 一 By youngsterxyf 使用 Yii框架为开发者提供两个静态方法进行日志记录: Yii::log($message, $level, $category); Yii: ...

  7. HashMap源码阅读笔记

    HashMap是Java编程中常用的集合框架之一. 利用idea得到的类的继承关系图可以发现,HashMap继承了抽象类AbstractMap,并实现了Map接口(对于Serializable和Clo ...

  8. Transformers包tokenizer.encode()方法源码阅读笔记

    Transformers包tokenizer.encode()方法源码阅读笔记_天才小呵呵的博客-CSDN博客_tokenizer.encode

  9. 源码阅读笔记 BiLSTM+CRF做NER任务 流程图

    源码阅读笔记 BiLSTM+CRF做NER任务(二) 源码地址:https://github.com/ZhixiuYe/NER-pytorch 本篇正式进入源码的阅读,按照流程顺序,一一解剖. 一.流 ...

最新文章

  1. 学习响应式BootStrap来写融职教育网站,Bootsrtap第四天Header处理
  2. cfd计算linux windows,CFD计算分析时常用的数值模拟方法 | 坐倚北风
  3. 计算机组成原理写一个运算器,计算机组成原理运算器的实现实验报告.doc
  4. linux新手入门学习 - linux目录结构
  5. 基于单片机的智能排队叫号系统设计
  6. java 动画 制作_DragonBones简单动画制作实例
  7. 论文排版——Latex正文中参考文献标注方式
  8. android java标准时间_关于android:Java中格林尼治标准时间的毫秒数
  9. Matlab矩阵和向量中的常用函数
  10. 十、【Vue-Router】两个新生命周期钩子 activated/deactivated
  11. RabbitMQ 四种类型发送接收数据方式
  12. 一些著名的软件都用什么语言编写?程序员都熟知这些软件!
  13. 巴西龟饲养日志----巴西龟肺炎治疗情况
  14. Windows安装配置Oracle
  15. 一些语音特征--学习笔记
  16. CP2K入门教程分享
  17. 使用小程序制作一个电子木鱼,功德+1
  18. 聚簇索引和非聚簇索引到底有什么区别?
  19. STC15W201S系列单片机 实现外部电路的软件看门狗程序设计
  20. 更换主板开机logo

热门文章

  1. 玻璃体混浊研究中需要解决的问题(持续更新中)
  2. hadoop+hive+flink+hbase交互的版本兼容性
  3. 获取python工程下面的所需的所有开源依赖包
  4. 网易云出现undefined symbol: fribidi_get_par_embedding_levels_ex
  5. 《机器学习》 周志华学习笔记第五章 神经网络(课后习题) python实现
  6. 是什么职位_为什么说,质量工程师岗位变成了 “寡妇职位”?
  7. for循环的break和continue
  8. 调用模块,加入系统路径
  9. 《数据结构》—— 串,KMP模式算法(转载)
  10. Java用SAX解析XML