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


    原文见这里:http://www.cnblogs.com/ohmygirl/p/CIRead-7.html

    一个灵活可控的应用程序中,必定会存在大量的可控參数(我们称为配置),比如在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中,便于之后的訪问和处理:

    1
    $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的形式:

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

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

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

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

    1
    $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循环:

    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
    /*  对于config_paths中的路径循环查找 */
    foreach ($this->_config_paths as $path)
    {  
      /* 对每一个location查找,也就是分别对ENVIRONMENT/config/ 和 config/ 文件夹查找  */
      foreach ($check_locations as $location)
      {
        /* 实际的配置文件名称 */
        $file_path = $path.'config/'.$location.'.php';
        <br>    /* 假设已经载入,则跳至最外层循环,其实,因为_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參数决定文件不存在的处理方式;假设文件存在。则须要对配置文件的格式检查:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /* 引入配置文件 */
    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同样的键,则会覆盖主配置文件里的项:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    /* 启用单独的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做对应的错误处理:

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

    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组件初始化的时候被载入):

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

    然后在控制器中获取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");

    实际的获取结果:

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

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

    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 = '')
    {  
      /* 没有设置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这两个配置项的处理:

    该方法的实现源代码:

    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], '/').'/';
    }

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

    的结果各自是:

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

    详细的方法实现,这里不赘述了。

    直接贴出源代码:

    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
    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()
    {   <br>    /* 获取系统文件夹.   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不好)的形式。改方法的实现源代码:

    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)
    {  
        /* 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用于设置配置项。假设配置项已经存在。则会被覆盖:

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

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

    到这里,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组件,会省不少事啊。

  • 相关阅读:
    centos7 yum安装配置Lnmp和负载配置
    Linux搭建lamp(Apache+PHP+Mysql环境)centos7.2版详细教程
    php的移位符运算
    Mysql的注入与防御
    介绍10个非常有用的PHP函数
    PHP性能的分析
    Netty 4源码解析:服务端启动
    centOS6.6升级gcc4.8
    kafka 0.8.x producer Example(scala)
    spark standalone zookeeper HA部署方式
  • 原文地址:https://www.cnblogs.com/lytwajue/p/6768735.html
Copyright © 2020-2023  润新知