CodeIgniter框架源码笔记(5)——识别多种URI风格:地址解析类URI.php

URI类主要处理地址字符串,将uri分解成对应的片段,存到segments数组中。querystring分解后存到$_GET数组
ROUTER路由类在之后的解析路由动作中,也主要依靠URI类的segments属性数组来获取当前上下文的请求URI信息。

在CI中如果允许传统的querystirng模式, 即设置$config['enable_query_strings'] = TRUE,URI类将不做任何处理,ROUTER类也只会匹配目录、控制器、方法。

CI体系中的方法参数都是从URI片段中取的,并按顺序传递给方法参数。不支持将querstring中的变量通过方法参数名传给方法,只能用$_GET获取。


以下分析默认config['enable_query_strings'] = FALSE。
在开始之前,先看配置文件
/*
|--------------------------------------------------------------------------
| URI PROTOCOL
|--------------------------------------------------------------------------
|
| This item determines which server global should be used to retrieve the
| URI string.  The default setting of 'REQUEST_URI' works for most servers.
| If your links do not seem to work, try one of the other delicious flavors:
|
| 'REQUEST_URI'    Uses $_SERVER['REQUEST_URI']
| 'QUERY_STRING'   Uses $_SERVER['QUERY_STRING']
| 'PATH_INFO'      Uses $_SERVER['PATH_INFO']
|
| WARNING: If you set this to 'PATH_INFO', URIs will always be URL-decoded!
*/
$config['uri_protocol']    = 'REQUEST_URI';

从应用层面上来说:
设置为QUERY_STRING解析的地址的用例如下:
http://www.nc.com/index.php?/news/list/fruit?page=2

设置为AUTO,REQUEST_URI,PATH_INFO解析的地址的用例如下:

http://www.nc.com/index.php?/news/list/fruit?page=2(和上面一样,兼容这种用例)
http://www.nc.com/index.php/news/list/fruit?page=2
http://www.nc.com/news/list/fruit?page=2


$config['uri_protocol']配置不但决定以哪个函数处理URI,同时决定了从哪个全局变量里获取当前上下文的uri地址。对应关系是:

'REQUEST_URI'    使用 $_SERVER['REQUEST_URI']

'QUERY_STRING'   使用 $_SERVER['QUERY_STRING']
'PATH_INFO'      使用 $_SERVER['PATH_INFO']
那么这三个变量有什么区别呢?
$_SERVER['REQUEST_URI']获取的是url地址中主机头后面所有的字符
$_SERVER['QUERY_STRING']获取的url地址中"?"后面的部分
$_SERVER['PATH_INFO']获取的是url地址中脚本文件($_SERVER['SCRIPT_NAME'])之后"?"之前的字符内容

例:index.php中代码:
echo((isset($_SERVER['PATH_INFO'])? "\$_SERVER['PATH_INFO']:{$_SERVER['PATH_INFO']}" :"PATH_INFO is NULL")."</br>");
echo((isset($_SERVER['REQUEST_URI'])? "\$_SERVER['REQUEST_URI']:{$_SERVER['REQUEST_URI']}" :"REQUEST_URI is NULL")."</br>");
echo((isset($_SERVER['QUERY_STRING'])? "\$_SERVER['QUERY_STRING']:{$_SERVER['QUERY_STRING']}" :"QUERY_STRING is NULL")."</br>");

1、访问http://www.example.twm/index.php?c=mall&a=lists&page=2
结果如下:
PATH_INFO is NULL
$_SERVER['REQUEST_URI']:/index.php?c=mall&a=lists&page=2
$_SERVER['QUERY_STRING']:c=mall&a=lists&page=2

2、访问http://www.example.twm/index.php/mall/lists/2?searchkey=apple
结果如下:
$_SERVER['PATH_INFO']:/mall/lists/2
$_SERVER['REQUEST_URI']:/index.php/mall/lists/2?searchkey=apple
$_SERVER['QUERY_STRING']:searchkey=apple

3、在本地测试时,设置.htaccess,重写隐藏index.php
<IfModule mod_rewrite.c>
    <IfModule mod_negotiation.c>
        Options -MultiViews
    </IfModule>

    RewriteEngine On

    # Redirect Trailing Slashes...
    RewriteRule ^(.*)/$ /$1 [L,R=301]

    # Handle Front Controller...
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f

    RewriteRule ^ index.php [L]
    #RewriteRule ^(.*)$ /index.php?/$1 [L]
    #注意以上两种RewriteRule情况下的$_SERVER['PATH_INFO']的差别
</IfModule>
访问:http://www.example.twm/mall/lists/2?searchkey=apple
显示如下(注意,这种情况下$_SERVER['PATH_INFO']为空):
PATH_INFO is NULL
$_SERVER['REQUEST_URI']:/mall/lists/2?searchkey=apple
$_SERVER['QUERY_STRING']:searchkey=apple


下面分析URI类中的几个重要函数

一、构造函数_construct():

功能及流程:解析命令行或url地址,获取querystring存入_GET全局数组中,并返回删除了入口文件的url地址;再调用$this->_set_uri_string($uri)生成segments数组。URI类对于存入GET中的参数,并不做安全处理(安全处理在INPUT类中实现)
1、处理命令行参数$uri = $this->_parse_argv();
使用场景:我们用命令执行PHP脚本:[root@twm ~]#php index.php mall lists
即:$uri="mall/lists"
protected function _parse_argv()
{
    $args = array_slice($_SERVER['argv'], 1);
    return $args ? implode('/', $args) : '';
}
2、获取querystring存入_GET全局数组中,并返回删除入口文件的url地址
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;
可以看出:
_parse_request_uri方法处理 AUTO,REQUEST_URI,PATH_INFO
_parse_query_string方法只处理QUERY_STRING

3、调用$this->_set_uri_string($uri)生成segments数组。

二、_parse_request_uri方法注释

protected function _parse_request_uri()
{
    if ( ! isset($_SERVER['REQUEST_URI'], $_SERVER['SCRIPT_NAME']))
    {
        return '';
    }
    // parse_url() returns false if no host is present, but the path or query string
    // contains a colon followed by a number
    //从$_SERVER['REQUEST_URI']取值,解析成$uri和$query两个字符串,分别存储请求的路径和get请求参数
    $uri = parse_url('http://dummy'.$_SERVER['REQUEST_URI']);
    $query = isset($uri['query']) ? $uri['query'] : '';
    $uri = isset($uri['path']) ? $uri['path'] : '';

    //去掉uri包含的$_SERVER['SCRIPT_NAME'],比如uri是http://www.example.twm/index.php/news/view/crm,经过处理后就变成/news/view/crm了
    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'])));
        }
    }
    // This section ensures that even on servers that require the URI to be in the query string (Nginx) a correct
    // URI is found, and also fixes the QUERY_STRING server var and $_GET array.
    
    //对于请求服务器的具体URI包含在查询字符串这种情况。
    //例如$uri以?/开头的 ,实际上if条件换种写法就是if(strncmp($uri, '?/', 2) === 0)),类似:
    //http://www.example.twm/index.php?/welcome/index
    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; 如下面这种请求uri:
        //http://www.example.twm/mall/lists?page=7
        $_SERVER['QUERY_STRING'] = $query;
    }
    //将查询字符串按键名存入_GET数组
    parse_str($_SERVER['QUERY_STRING'], $_GET);

    if ($uri === '/' OR $uri === '')
    {
        return '/';
    }
    // Do some final cleaning of the URI and return it
    //调用 _remove_relative_directory($uri)函数作安全处理,移除$uri中的../相对路径字符和反斜杠
    return $this->_remove_relative_directory($uri);
}

三、_parse_query_string()方法注释

protected function _parse_query_string()
{
    //从$_SERVER['QUERY_STRING']取值
    $uri = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : @getenv('QUERY_STRING');
    //对于没有实际内容的,直接返回空。
    if (trim($uri, '/') === '')
    {
        return '';
    }
    elseif (strncmp($uri, '/', 1) === 0)
    {
        //对应生成$_SERVER['QUERY_STRING']和$uri
        //最后将$_SERVER['QUERY_STRING']解析于_GET数组parse_str($_SERVER['QUERY_STRING'], $_GET);
        $uri = explode('?', $uri, 2);
        $_SERVER['QUERY_STRING'] = isset($uri[1]) ? $uri[1] : '';
        $uri = $uri[0];
    }

    parse_str($_SERVER['QUERY_STRING'], $_GET);
    调用 _remove_relative_directory($uri)函数作安全处理,移除$uri中的../相对路径字符和反斜杠
    return $this->_remove_relative_directory($uri);
}

四、 _set_uri_string($str)方法,解析$url填充到$this->segments数组中去

protected function _set_uri_string($str)
{
    // Filter out control characters and trim slashes
    //移除$str不可见字符: $this->uri_string=trim(remove_invisible_characters($str, FALSE), '/')
    //这样做的意义在于防止在字符中间夹入空字符造成漏洞,比如Java\0script 
    $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;
        // Populate the segments array
        //解析$url,用"/"分段,填充到$this->segments数组中去
        foreach (explode('/', trim($this->uri_string, '/')) as $val)
        {
            $val = trim($val);
            // Filter segments for security
            $this->filter_uri($val);

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

五、to_assoc函数簇:uri_to_assoc($n = 3, $default = array()) , ruri_to_assoc($n = 3, $default = array())

该方法用于将 URI 的段转换为一个包含键值对的关联数组。如下 URI:
http://example.twm/user/search/name/joe/location/UK/gender/male
使用这个方法可以将 URI 转为如下的数组原型:
[array]
(
    'name'      => 'joe'
    'location'  => 'UK'
    'gender'    => 'male'
)
可以通过第一个参数设置一个位移,默认值为 3 ,这是因为URI 的前两段通常都是控制器和方法。 例如:
$array = $this->uri->uri_to_assoc(3);
echo $array['name'];
第二个参数用于设置默认的键名,这样即使 URI 中缺少某个键名,也能保证返回的数组中包含该索引。 例如:
$default = array('name', 'gender', 'location', 'type', 'sort');
$array = $this->uri->uri_to_assoc(3, $default);
按照CI体系,segments前两个对应的分别是控制器名和方法名,所以默认从第三个取

六、segment函数簇

1、segment($n, $no_result = NULL) rsegment($n, $no_result = NULL)
用于从 URI 中获取指定段。参数 n 为你希望获取的段序号,URI 的段从左到右进行编号。 例如,如果你的完整 URL 是这样的:
http://example.twm/index.php/news/local/metro/crime_is_up
那么每个分段如下:
news
local
metro
crime_is_up
$product_id = $this->uri->segment(3, 0);返回metro,不存在时返回0

2、segment_array() , rsegment_array()
返回 URI 所有的段组成的数组。例如:
$segs = $this->uri->segment_array();
foreach ($segs as $segment)
{
    echo $segment;
    echo '<br />';
}

3、total_segments() , total_rsegments()
返回 URI 的总段数

4、slash_segment($n, $where = 'trailing') , slash_rsegment($n, $where = 'trailing')
该方法和 segment() 类似,只是它会根据第二个参数在返回结果的前面或/和后面添加斜线。 如果第二个参数未设置,斜线会添加到后面
根据源代码看,如果第二个参数不是trailing,也不是leading,将会在头尾都加斜杠。
protected function _slash_segment($n, $where = 'trailing', $which = 'segment')
{
    $leading = $trailing = '/';
    if ($where === 'trailing')
    {
        $leading    = '';
    }
    elseif ($where === 'leading')
    {
        $trailing    = '';
    }
    return $leading.$this->$which($n).$trailing;
}

七、uri_string() , ruri_string()

返回一个相对的 URI 字符串,例如,如果你的完整 URL 为:
http://example.twm/index.php/news/local/345
该方法返回:
news/local/345





  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值