CI框架源码解析六之配置类文件Config.php

        一个灵活可控的应用程序中,必然会存在大量的可控参数(我们称为配置),例如在CI的主配置文件中(这里指Application/Config/Config.php文件),不仅如此,CI框架还允许你将配置参数放到主配置文件之外。例如,你可以定义自己的配置文件为Config_app.php, 然后在你的应用程序控制器中加载你的配置文件,如此纷繁多样的配置项和配置文件,CI框架是如何进行管理的?这便是我们今天要剖析解说的内容:CI框架的配置管理类文件-Config.php。

         首先,我们先对CI框架的配置管理类文件-Config.php进行一个简要的类结构及说明:

  • config:Config类所有配置项都存储在 $config 的数组中,iterm()方法取值也是从这里取。
  • is_loaded:is_loaded是个数组,用当前上下文加载过的文件路径做数组元素。load文件过程中如果路径存在,则说明已加载该配置文件,就不重复加载了。
  • _config_paths:默认值array(APPPATH)。配置文件存放路径。程序循环加载。
  • _config_paths:默认值array(APPPATH)。配置文件存放路径。程序循环加载。
  • __construct():加载默认config.php中的配置。如果config["base_url']不存在,则重新根据当前$_SERVER中信息计算并设置。
  • load():加载自定义配置文件。
  • item():获取配置项。
  • slash_item():获取配置项,并在后面加“/”。
  • set_item():设置配置项。
  • base_url()、site_url()、system_url()、_uri_string():主要用于URL辅助函数调用。system_url()已经弃用。

综上,我们可以发现Config类作为配置管理类,主要完成下几个主要功能:

        ① 加载配置文件

        ② 获取配置项值

        ③ 设置配置项(临时)

        ④ url路由处理(不明白为什么放在配置类文件中)

下面我们去剖析各个功能方法的具体实现:

1、加载配置文件(_construct()、load())

① _construct()

    public function __construct()
    {
        $this->config =& get_config();

        //在config/config.php里面有个配置项是base_url,它并不是必须配置项,如果没有配置,则系统就在这个地方
        //自己去给它进行赋值。
        if (empty($this->config['base_url'])) {
            //一般来说,如果通过http访问网站的话,这个值都会有的
            if (isset($_SERVER['SERVER_ADDR'])) {
                if (strpos($_SERVER['SERVER_ADDR'], ':') !== FALSE) {
                    $server_addr = '[' . $_SERVER['SERVER_ADDR'] . ']';
                } else {
                    $server_addr = $_SERVER['SERVER_ADDR'];
                }
                //判断是否通过https方式访问。
                //去掉文件名部分。
                $base_url = (is_https() ? 'https' : 'http') . '://' . $server_addr
                    . substr($_SERVER['SCRIPT_NAME'], 0, strpos($_SERVER['SCRIPT_NAME'], basename($_SERVER['SCRIPT_FILENAME'])));
            } else {
                //如果发现没有$_SERVER['HTTP_HOST'],则直接设置为localhost
                $base_url = 'http://localhost/';
            }
            //保存到base_url中,以后像辅助函数uri_helper就可以通过base_url()调用出Config组件此值。
            $this->set_item('base_url', $base_url);
        }

        log_message('info', 'Config Class Initialized');
    }

        之前我们在分析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设置为空的情况:如果设置了$_SERVER[‘HTTP_HOST’],则base_url被设置为Protocal(http或者https) + $_SERVER['HTTP_HOST'] + SCIRPT_PATH的形式;否者,直接被设置为http://localhost/;同时将base_url配置项映射到配置数组中,方便之后的访问(set_item方法我们稍后会将,这里只需要知道,它是添加到配置项且会覆盖旧值)。之后我们会看到,base_url这个配置项对于很多组件都是必须的,因此,CI框架花费一定的精力来保证base_url的正确性,也是可以理解的。

② load()

    public function load($file = '', $use_sections = FALSE, $fail_gracefully = FALSE)
    {
        $file = ($file === '') ? 'config' : str_replace('.php', '', $file);
        $loaded = FALSE;
        //这个$this->_config_paths默认只有应用目录application/
        foreach ($this->_config_paths as $path) {
            //分别从某特定环境的配置目录和默认的配置目录里面寻找。
            foreach (array($file, ENVIRONMENT . DIRECTORY_SEPARATOR . $file) as $location) {
                //组合成文件路径
                $file_path = $path . 'config/' . $location . '.php';
                //判断$this->is_loaded,如果加载过,直接返回TRUE
                if (in_array($file_path, $this->is_loaded, TRUE)) {
                    return TRUE;
                }
                //如果文件不存在,跳入下一个循环
                if (!file_exists($file_path)) {
                    continue;
                }
                //如果文件存在,则会执行到这里,加载进来
                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.');
                }
                //正常加载到定义的$config数组后
                if ($use_sections === TRUE) {
                    $this->config[$file] = isset($this->config[$file]) ? array_merge($this->config[$file], $config) : $config;
                } else {
                    $this->config = array_merge($this->config, $config);
                }
                //将配置文件路径放入$this->is_loaded数组,表明已经加载。
                $this->is_loaded[] = $file_path;
                $config = NULL;
                $loaded = TRUE;
                log_message('debug', 'Config file loaded: ' . $file_path);
            }
        }
        if ($loaded === TRUE) {
            return TRUE;
        } elseif ($fail_gracefully === TRUE) {
            return FALSE;
        }
        show_error('The configuration file ' . $file . '.php does not exist.');
    }

这是Config组件中较核心的方法之一,所有的参数都是可选参数,我们这里简单解释一下各形参的含义:

  • $file:需要加载的配置文件,可以包含后缀名也不可以不包含,如果未指定该参数,则默认加载Config.php文件;
  • $user_sections:是否为加载的配置文件使用独立的section,这么说可能还是不明白,试想,如果你定义了自己的配置文件,而你的配置文件中的配置项可能与Config.php文件中的配置项冲突,通过指定$section为true可以防止配置项的覆盖;
  • $fail_gracefully:要load的配置文件不存在时的处理。Gracefully意为优雅的,如果该参数设置为true,则在文件不存在时只会返回false,而不会显示错误。

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

Ⅰ 配置文件名预处理

    $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的主键。

Ⅱ 查找和加载配置文件

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

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

Ⅲ 具体的查找加载过程是一个双重的foreach循环

        //这个$this->_config_paths默认只有应用目录application/
        //这一层循环是设置存放路径 ,其定义在public $_config_paths = array(APPPATH);
        foreach ($this->_config_paths as $path) {
            //分别从某特定环境的配置目录和默认的配置目录里面寻找。
            //这层循环用于设置当前文件和环境ENVIRONMENT下的文件两重路径
            foreach (array($file, ENVIRONMENT . DIRECTORY_SEPARATOR . $file) as $location) {
                //组合成文件路径
                $file_path = $path . 'config/' . $location . '.php';
                //判断$this->is_loaded,如果加载过,直接返回TRUE
                //如果是已经加载过了,那么在Config::$config里面理应当有,所以直接跳出了最外层循环。
                if (in_array($file_path, $this->is_loaded, TRUE)) {
                    return TRUE;
                }
                //如果文件不存在,跳入下一个循环
                if (!file_exists($file_path)) {
                    //如果找到了一个,就不再找了。所以相同的配置文件仅会有一个有效。
                    continue;
                }
                //如果文件存在,则会执行到这里,加载进来
                include($file_path);
                //如果文件中没有定义$config数组,则产生报错,根据$fail_gracefully来判断是否做出错提示
                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.');
                }
                //正常加载到定义的$config数组后
                //下面就是$use_sections的作用,根据它来规定当前加载的配置信息的保存形式。
                if ($use_sections === TRUE) {
                    $this->config[$file] = isset($this->config[$file]) ? array_merge($this->config[$file], $config) : $config;
                } else {
                    $this->config = array_merge($this->config, $config);
                }
                //将配置文件路径放入$this->is_loaded数组,表明已经加载。
                //保存哪些文件已经加载过,下次再调用此load方法的时候,通过它来避免重复加载,减少不必要的操作。
                $this->is_loaded[] = $file_path;
                $config = NULL;
                $loaded = TRUE;
                log_message('debug', 'Config file loaded: ' . $file_path);
            }
        }

        到这里,如果配置文件不存在,则$found和$loaded都为false,CI会根据fail_gracefully参数决定文件不存在的处理方式;如果文件存在,则需要对配置文件的格式检查;前面说过,use_secitons参数如果为true,则CI_Config会对该配置文件启用独立的key存储。例如,我们在controller中这样加载配置文件:$this->config->load("config_app",true);这也意味着,在不启用user_secitons的情况下,如果你的配置文件中有与主配置文件Config.php相同的键,则会覆盖主配置文件中的项;双层循环完成后,如果loaded为false,也就是未成功加载任何配置,则根据fail_gracefully做相应的错误处理。

2、获取配置项值(item()、slash_item())

① item()

    public function item($item, $index = '')
    {
        if ($index == '') {
            return isset($this->config[$item]) ? $this->config[$item] : NULL;
        }
        return isset($this->config[$index], $this->config[$index][$item]) ? $this->config[$index][$item] : NULL;
    }

        item方法用于在配置中获取特定的配置项。注意,如果你在load配置文件的时候启用了use_sections,则在使用item()获取配置项的时候需要指定第二个参数,也就是加载的配置文件的文件名(不包含后缀)。

② slash_item()

    /**
     * 此方法仅仅是对配置信息进行一些修剪处理而已
     */
    public function slash_item($item)
    {
        //如果此配置项仅仅是包含一些对配置无效的字符,则直接返回空。
        if (!isset($this->config[$item])) {
            return NULL;
        } elseif (trim($this->config[$item]) === '') {
            return '';
        }
        //保证以一条/结尾。
        return rtrim($this->config[$item], '/') . '/';
    }

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

3、设置配置项(临时)

    //主要用于URL辅助函数调用,因为与conifg体系无关
    public function set_item($item, $value)
    {
        $this->config[$item] = $value;
    }

        这个时临时的,就是通过函数访问并写入$this->config数组。与item()相反,set_item用于设置配置项。如果配置项已经存在,则会被覆盖。

4、url路由处理(site_url()、base_url()、_uri_string()、system_url())

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

    echo "site_url  : ",$this->config->site_url("index/hello"),"</br>";
    //site_url : http://www.citest.com/index/hello.html
    echo "base_url  : ",$this->config->base_url("index/hello"),"<br/>";
    //base_url : http://www.citest.com/index/hello
    echo "system_url: ",$this->config->system_url();
    //system_url: http://www.citest.com/system/

        注:我们可以通过输出的结果,看出它们之间的区别。site_url是添加了suffix,base_url则是没有添加suffix的url地址,而system_url这个东西很奇怪,是获取系统的url路径。但实际上,由于system路径并没有直接执行的脚本,所以这个方法的实际用途是什么,暂时不知。有知道的童鞋麻烦告知。

        site_url和base_url都调用了_uri_string。这个函数是做什么用的呢?按理来说, _uri_string的功能应该由URI组件来完成,这里却放在了Config组件中,似乎有些不妥(实际上,_uri_string是为base_url和site_url专属服务的)。在这里就不多说了。


        最后,按照惯例,贴一下整个配置类Config.php文件的源码(注释版):

    <?php
    
    /**
     * =======================================
     * Created by Pocket Knife Technology.
     * User: ZhiHua_W
     * Date: 2016/10/19 0013
     * Time: 下午 2:40
     * Project: CodeIgniter框架—源码分析
     * Power: Analysis for Config.php
     * =======================================
     */
    
    //不说
    defined('BASEPATH') OR exit('No direct script access allowed');
    
    class CI_Config
    {
        //配置变量列表
        public $config = array();
        //保存已加载的配置文件列表
        public $is_loaded = array();
        //配置路径
        public $_config_paths = array(APPPATH);
    
        /**
         * 构造函数
         * 加载默认config.php中的配置。如果config["base_url']不存在,
         * 则重新根据当前$_SERVER中信息计算并设置。
         */
        public function __construct()
        {
            $this->config =& get_config();
    
            //在config/config.php里面有个配置项是base_url,它并不是必须配置项,如果没有配置,则系统就在这个地方
            //自己去给它进行赋值。
            if (empty($this->config['base_url'])) {
                //一般来说,如果通过http访问网站的话,这个值都会有的
                if (isset($_SERVER['SERVER_ADDR'])) {
                    if (strpos($_SERVER['SERVER_ADDR'], ':') !== FALSE) {
                        $server_addr = '[' . $_SERVER['SERVER_ADDR'] . ']';
                    } else {
                        $server_addr = $_SERVER['SERVER_ADDR'];
                    }
                    //判断是否通过https方式访问。
                    //去掉文件名部分。
                    $base_url = (is_https() ? 'https' : 'http') . '://' . $server_addr
                        . substr($_SERVER['SCRIPT_NAME'], 0, strpos($_SERVER['SCRIPT_NAME'], basename($_SERVER['SCRIPT_FILENAME'])));
                } else {
                    //如果发现没有$_SERVER['HTTP_HOST'],则直接设置为localhost
                    $base_url = 'http://localhost/';
                }
                //保存到base_url中,以后像辅助函数uri_helper就可以通过base_url()调用出Config组件此值。
                $this->set_item('base_url', $base_url);
            }
    
            log_message('info', 'Config Class Initialized');
        }
    
        /**
         * 加载配置文件
         * 先解释一下load方法的参数,$file就是配置文件名。配置文件目录一般为应用目录(application)/config/下
         * 下面会有很多个针对不同方面配置的文件,而我们通过Config组件加载的配置信息都会保存在Config::$config这个属性里面,
         * 所以第二个参数$use_sections就是设置是否当前配置文件是否以独立一个数组的形式充当Config::$config的一个元素加入,
         * 这个其实很简单,例如你加载test_config.php配置文件,配置信息是以$configp['test_config'][×××]还是直接$config[×××]保存;
         * 如果为true,则$config是一个两层的数组,如果为false,则单纯将配置文件里面的配置信息合并。
         * 例如配置文件abc.php,如果为true,则会以$config['abc']['xxx']的形式保存,否则直接合并即会有$config['xxx']。
         * 第三个参数只是设置要不要报错而已,如果为true,则只会返回false,如果为false则直接在函数执行时报错。
         */
        public function load($file = '', $use_sections = FALSE, $fail_gracefully = FALSE)
        {
            //接下来这一行代码是为了方便我们调用的时候既可以以xxx.php的形式传参,也可以只以xxx(无后缀)的形式。
            //当没有参数时,默认加载文件为config.php
            //删除文件名中的".php"部分
            $file = ($file === '') ? 'config' : str_replace('.php', '', $file);
            $loaded = FALSE;
            //这个$this->_config_paths默认只有应用目录application/
            //这一层循环是设置存放路径 ,其定义在public $_config_paths = array(APPPATH);
            foreach ($this->_config_paths as $path) {
                //分别从某特定环境的配置目录和默认的配置目录里面寻找。
                //这层循环用于设置当前文件和环境ENVIRONMENT下的文件两重路径
                foreach (array($file, ENVIRONMENT . DIRECTORY_SEPARATOR . $file) as $location) {
                    //组合成文件路径
                    $file_path = $path . 'config/' . $location . '.php';
                    //判断$this->is_loaded,如果加载过,直接返回TRUE
                    //如果是已经加载过了,那么在Config::$config里面理应当有,所以直接跳出了最外层循环。
                    if (in_array($file_path, $this->is_loaded, TRUE)) {
                        return TRUE;
                    }
                    //如果文件不存在,跳入下一个循环
                    if (!file_exists($file_path)) {
                        //如果找到了一个,就不再找了。所以相同的配置文件仅会有一个有效。
                        continue;
                    }
                    //如果文件存在,则会执行到这里,加载进来
                    include($file_path);
                    //如果文件中没有定义$config数组,则产生报错,根据$fail_gracefully来判断是否做出错提示
                    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.');
                    }
                    //正常加载到定义的$config数组后
                    //下面就是$use_sections的作用,根据它来规定当前加载的配置信息的保存形式。
                    if ($use_sections === TRUE) {
                        $this->config[$file] = isset($this->config[$file]) ? array_merge($this->config[$file], $config) : $config;
                    } else {
                        $this->config = array_merge($this->config, $config);
                    }
                    //将配置文件路径放入$this->is_loaded数组,表明已经加载。
                    //保存哪些文件已经加载过,下次再调用此load方法的时候,通过它来避免重复加载,减少不必要的操作。
                    $this->is_loaded[] = $file_path;
                    $config = NULL;
                    $loaded = TRUE;
                    log_message('debug', 'Config file loaded: ' . $file_path);
                }
            }
            //如果循环完都找不到文件,则$loaded为FALSE,找到了则为TRUE
            if ($loaded === TRUE) {
                return TRUE;
            } elseif ($fail_gracefully === TRUE) {
                return FALSE;
            }
            show_error('The configuration file ' . $file . '.php does not exist.');
        }
    
        /**
         * 取得某一配置项的内容,如果知道上面Config::load($file, $use_sections, $fail_gracefully);方法
         * 中$use_sections的意义的话,那个下面的$index意义就很容易理解了
         */
        public function item($item, $index = '')
        {
            if ($index == '') {
                return isset($this->config[$item]) ? $this->config[$item] : NULL;
            }
            return isset($this->config[$index], $this->config[$index][$item]) ? $this->config[$index][$item] : NULL;
        }
    
        /**
         * 此方法仅仅是对配置信息进行一些修剪处理而已
         */
        public function slash_item($item)
        {
            //如果此配置项仅仅是包含一些对配置无效的字符,则直接返回空。
            if (!isset($this->config[$item])) {
                return NULL;
            } elseif (trim($this->config[$item]) === '') {
                return '';
            }
            //保证以一条/结尾。
            return rtrim($this->config[$item], '/') . '/';
        }
    
        /**
         * 我们经常通过url_helper的site_url获得我们在项目中想要的路径,
         * 其实真正执行的是Config::site_url()这个方法。
         */
        public function site_url($uri = '', $protocol = NULL)
        {
            $base_url = $this->slash_item('base_url');
            if (isset($protocol)) {
                if ($protocol === '') {
                    $base_url = substr($base_url, strpos($base_url, '//'));
                } else {
                    $base_url = $protocol . substr($base_url, strpos($base_url, '://'));
                }
            }
            //$uri参数实质可以是数组的
            if (empty($uri)) {
                return $base_url . $this->item('index_page');
            }
            $uri = $this->_uri_string($uri);
            //根据当前的路由格式返回相应的uri_string
            if ($this->item('enable_query_strings') === FALSE) {
                $suffix = isset($this->config['url_suffix']) ? $this->config['url_suffix'] : '';
    
                if ($suffix !== '') {
                    if (($offset = strpos($uri, '?')) !== FALSE) {
                        $uri = substr($uri, 0, $offset) . $suffix . substr($uri, $offset);
                    } else {
                        $uri .= $suffix;
                    }
                }
                return $base_url . $this->slash_item('index_page') . $uri;
            } elseif (strpos($uri, '?') === FALSE) {
                $uri = '?' . $uri;
            }
            return $base_url . $this->item('index_page') . $uri;
        }
    
        /**
         * 此函数和上个函数还有下个函数以及下下个函数都可以认为是路由处理
         * 不是特别明白为什么要放到配置文件中
         */
        public function base_url($uri = '', $protocol = NULL)
        {
            $base_url = $this->slash_item('base_url');
            if (isset($protocol)) {
                if ($protocol === '') {
                    $base_url = substr($base_url, strpos($base_url, '//'));
                } else {
                    $base_url = $protocol . substr($base_url, strpos($base_url, '://'));
                }
            }
            return $base_url . $this->_uri_string($uri);
        }
    
        /**
         * 按当前规定路由格式,返回正确的uri_string.
         * 主要是如果当参数$uri是数组的时候的一些处理。
         */
        protected function _uri_string($uri)
        {
            if ($this->item('enable_query_strings') === FALSE) {
                is_array($uri) && $uri = implode('/', $uri);
                return ltrim($uri, '/');
            } elseif (is_array($uri)) {
                return http_build_query($uri);
            }
            return $uri;
        }
    
        //已经弃用
        public function system_url()
        {
            //下面这行这么奇葩的代码,其实只是为拿到系统目录的路径而已。
            //正则部分是首先去掉BASEPATH中多余重复的“/”,然后再拆分为数组。最后通过end()函数来拿到系统目录名。
            $x = explode('/', preg_replace('|/*(.+?)/*$|', '\\1', BASEPATH));
            return $this->slash_item('base_url') . end($x) . '/';
        }
    
        //主要用于URL辅助函数调用,因为与conifg体系无关,暂不做阐述
        public function set_item($item, $value)
        {
            $this->config[$item] = $value;
        }
    
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值