URI类主要处理地址字符串,将uri分解成对应的片段,存到segments数组中。querystring分解后存到$_GET数组
ROUTER路由类在之后的解析路由动作中,也主要依靠URI类的segments属性数组来获取当前上下文的请求URI信息。
在CI中如果允许传统的querystirng模式, 即设置$config['enable_query_strings'] = TRUE,URI类将不做任何处理,ROUTER类也只会匹配目录、控制器、方法。
在开始之前,先看配置文件
从应用层面上来说:
设置为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
'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
显示如下(注意,这种情况下$_SERVER['PATH_INFO']为空):
PATH_INFO is NULL
$_SERVER['REQUEST_URI']:/mall/lists/2?searchkey=apple
$_SERVER['QUERY_STRING']:searchkey=apple
下面分析URI类中的几个重要函数
1、处理命令行参数$uri = $this->_parse_argv();
使用场景:我们用命令执行PHP脚本:[root@twm ~]#php index.php mall lists
即:$uri="mall/lists"
_parse_request_uri方法处理 AUTO,REQUEST_URI,PATH_INFO
_parse_query_string方法只处理QUERY_STRING
3、调用$this->_set_uri_string($uri)生成segments数组。
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前两个对应的分别是控制器名和方法名,所以默认从第三个取
用于从 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,将会在头尾都加斜杠。
http://example.twm/index.php/news/local/345
该方法返回:
news/local/345
ROUTER路由类在之后的解析路由动作中,也主要依靠URI类的segments属性数组来获取当前上下文的请求URI信息。
在CI中如果允许传统的querystirng模式, 即设置$config['enable_query_strings'] = TRUE,URI类将不做任何处理,ROUTER类也只会匹配目录、控制器、方法。
CI体系中的方法参数都是从URI片段中取的,并按顺序传递给方法参数。不支持将querstring中的变量通过方法参数名传给方法,只能用$_GET获取。
在开始之前,先看配置文件
/*
|--------------------------------------------------------------------------
| 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