CodeIgniter源码阅读笔记(6)——地址解析类URI.php

URI类的作用主要是处理地址字符串,将URI分成对应的片段保存到segments,路由类也主要是通过segments数组来获取上下文中的URI请求信息
在阅读这段源码的时候,我们主要关心的问题有
1.URI类是如何将地址字符串解析成对应片段?
2.解析后的对应片段保存到变量中是怎样的数据结构?

1.__construct()构造函数

URI类在初始化的时候就会对地址进行解析,构造函数会根据不同的环境调用对应的解析函数,并保存解析结果。
cli模式:调用_parse_argv()进行解析
根据uri_protocol这个配置属性决定使用哪个解析函数
默认REQUEST_URI,使用_parse_request_uri解析函数
QUERY_STRING,使用_parse_query_string解析函数
PATH_INFO或其他参数,都使用_parse_request_uri解析函数
这些解析函数会将地址解析成uri字符串,再由_set_uri_string函数将uri字符串解析成对应片段
我们先看_construct的源码,之后再看具体的解析函数是如何实现的:

public function __construct()
{
    //加载配置文件
    $this->config =& load_class('Config', 'core');
    //enable_query_strings参数为true的时候,直接从_GET数组中取出对应片段,所以不
    //需要解析
    if (is_cli() OR $this->config->item('enable_query_strings') !== TRUE)
    {
        //判断URI是否合法的正则表达式
        $this->_permitted_uri_chars = 
                    $this->config->item('permitted_uri_chars');
        //如果是命令行模式
        if (is_cli())
        {
            //_parse_argv()是实际进行解析的函数,解析完后结果返回给$uri
            $uri = $this->_parse_argv();
        }
        else
        {
            //URI属性参数
            $protocol = $this->config->item('uri_protocol');
            //默认是REQUEST_URI
            empty($protocol) && $protocol = 'REQUEST_URI';
            //根据不同的URI属性调用对应的解析函数,并将结果返回给$uri
            switch ($protocol)
            {
                case 'AUTO': // For BC purposes only
                case 'REQUEST_URI':
                    $uri = $this->_parse_request_uri();
                    break;
                case 'QUERY_STRING':
                    $uri = $this->_parse_query_string();
                    break;
                case 'PATH_INFO':
                default:
                    $uri = isset($_SERVER[$protocol])
                        ? $_SERVER[$protocol]
                        : $this->_parse_request_uri();
                    break;
                }
            }
        //将解析的uri字符串保存到uri_string中,对应片段存到segments变量
        $this->_set_uri_string($uri);
    }
    log_message('info', 'URI Class Initialized');
}

2.命令行模式下_parse_argv()解析函数

这个uri字符串解析函数,是从$_SERVER[‘argv’]中获取参数

protected function _parse_argv()
{
    //取出所有参数
    $args = array_slice($_SERVER['argv'], 1);
    //以'/'连接参数,组成uri字符串
    return $args ? implode('/', $args) : '';
}

3._parse_request_uri()解析函数

从$_SERVER[‘REQUEST_URI’]中获取参数,

    protected function _parse_request_uri()
    {
        //如果$_SERVER['REQUEST_URI']或$_SERVER['SCRIPT_NAME']为空直接返回空
        if ( ! isset($_SERVER['REQUEST_URI'], $_SERVER['SCRIPT_NAME']))
        {
            return '';
        }
        //获取URL的query,path
        $uri = parse_url('http://dummy'.$_SERVER['REQUEST_URI']);
        $query = isset($uri['query']) ? $uri['query'] : '';
        $uri = isset($uri['path']) ? $uri['path'] : '';
        //去掉uri字符串中脚本名即xxx.php开头的字符串部分和脚本名所在目录名开头的字符串部
        //分
        if (isset($_SERVER['SCRIPT_NAME'][0]))
        {
            if (strpos($uri, $_SERVER['SCRIPT_NAME']) === 0)
            {
                $uri = (string) substr($uri,strlen($_SERVER['SCRIPT_NAME']));
            }
            elseif (strpos($uri, dirname($_SERVER['SCRIPT_NAME'])) === 0)
            {
                $uri = (string) substr($uri,
                                strlen(dirname($_SERVER['SCRIPT_NAME'])));
            }
        }
        //当出现http://localhost.com/path?/test?key=val时,只将key=val作为get参
        //数
        if (trim($uri, '/') === '' && strncmp($query, '/', 1) === 0)
        {
            $query = explode('?', $query, 2);
            $uri = $query[0];
            $_SERVER['QUERY_STRING'] = isset($query[1]) ? $query[1] : '';
        }
        else
        {
            $_SERVER['QUERY_STRING'] = $query;
        }
        //将查询字符串解析到$_GET数组中
        parse_str($_SERVER['QUERY_STRING'], $_GET);

        if ($uri === '/' OR $uri === '')
        {
            return '/';
        }
        //删除多余的斜杠,返回uri字符串
        return $this->_remove_relative_directory($uri);
    }

4._parse_query_string()解析函数

根据$_SERVER[‘QUERY_STRING’]的参数解析出Uri字符串

    protected function _parse_query_string()
    {
        //获取$_SERVER['QUERY_STRING']参数
        $uri = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] 
                    : @getenv('QUERY_STRING');
        //之后的代码与_parse_request_uri()函数一样
        if (trim($uri, '/') === '')
        {
            return '';
        }
        elseif (strncmp($uri, '/', 1) === 0)
        {
            $uri = explode('?', $uri, 2);
            $_SERVER['QUERY_STRING'] = isset($uri[1]) ? $uri[1] : '';
            $uri = $uri[0];
        }

        parse_str($_SERVER['QUERY_STRING'], $_GET);

        return $this->_remove_relative_directory($uri);
    }

5._remove_relative_directory()——去掉多余斜杠和相对路径符号

    protected function _remove_relative_directory($uri)
    {
        //以'/'分割uri字符串并保存到$uris数组中,将'..'字符串去掉
        $uris = array();
        $tok = strtok($uri, '/');
        while ($tok !== FALSE)
        {
            if (( ! empty($tok) OR $tok === '0') && $tok !== '..')
            {
                $uris[] = $tok;
            }
            $tok = strtok('/');
        }
        //用'/'重新拼接数组
        return implode('/', $uris);
    }

6.uri解析成对应分段——_set_uri_string()函数

前面解读的几个函数已经将url解析成uri字符串了,但是我们最终需要的是Uri字符串所对应的参数,这样才能根据uri参数路由到正确的位置,set_uri_string()函数的功能便是将uri字符串解析成对应分段

    protected function _set_uri_string($str)
    {
        //移除$str中的隐藏字符串和字符串两侧的'/',并将$str的值赋给$this->uri_string
        $this->uri_string = 
                    trim(remove_invisible_characters($str, FALSE), '/');
        if ($this->uri_string !== '')
        {
            //如果配置文件中配置了url后缀,则移除后缀
            if (($suffix = (string) $this->config->item('url_suffix')) 
                    !== '')
            {
                $slen = strlen($suffix);

                if (substr($this->uri_string, -$slen) === $suffix)
                {
                    $this->uri_string = substr($this->uri_string, 0, -$slen);
                }
            }

            $this->segments[0] = NULL;
            //将uri_string字符串以'/'分割成数组,并将数组的元素存储到segments数组中
            //segments数组下标由1开始
            foreach (explode('/', trim($this->uri_string, '/')) as $val)
            {
                $val = trim($val);
                //判断是否符合参数是否合法
                $this->filter_uri($val);

                if ($val !== '')
                {
                    $this->segments[] = $val;
                }
            }

            unset($this->segments[0]);
        }
    }

7.合法性保障——filter_uri函数

    public function filter_uri(&$str)
    {
        //参数为空或者不符合uri正则表达式,则参数不合法,展示错误界面
        if ( ! empty($str) && ! empty($this->_permitted_uri_chars) && 
                ! preg_match('/^['.$this->_permitted_uri_chars.']+$/i'.
                                (UTF8_ENABLED ? 'u' : ''), $str))
        {
            show_error('The URI you submitted has disallowed characters.
                            ', 400);
        }
    }

总结:

以上几个函数则是URI.php中的主要函数了,还有将uri建立关联数组,获取segments这些函数,我只阅读了一下主要函数的代码,那些有兴趣的时候再看吧。
这里回答一下开头的两个问题:
1.URI类是如何将地址字符串解析成对应片段?
答:URI类首先将URL字符串解析成URI字符串,URI字符串的格式则是我们已经非常熟悉的CI路由地址(查询字符串,SCRIPT_NAME,以及SCRIPT_NAME目录名不会出现在uri字符串中),然后再将URI字符串中的参数解析出来存储到segments数组中,这样就讲url解析成对应的片段了。
2.解析后的对应片段保存到变量中是怎样的数据结构?
答:解析出来的片段从下标1开始依次保存到segments数组中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值