compatible
上一节大家说url的央浼精神上是调用调整器的不二等秘书诀,大家分析了下真的也是如此,最后是在CodeIgniter.php中从load_class函数中生成的路由对像$RTCRUISER上得到了调节器,方法和参数,并使用call_user_func_array贯彻了对调节器的艺术的调用。可是$RT凯雷德对象上的调控器和办法到底是怎么分析来的?
路由那有个别形似在事实上海工业作中从不怎么规划过,只是在用默许的安装,在手册里面看到部分,艰涩难懂。
对此配置pathinfo的支撑,在Nginx作服务器、无数种系统要同不时候运转的条件,实在是1项很麻烦的事务,而又不想很low的五个参数(像m、c、a)构造路由参数,作者急需这种不必强制行使pathinfo的还能伪pathinfo(用二个路由参数如s=/abc/ddd/ddd.html,参数名如route、s、r等)的框架。
那1节大家进入Router.php中看看CI框架的url剖判和路由管理。在深入分析路由事先,先说下CI补助的url风格以及路由配置。
方今TP的v伍版本下,仅可支撑路由解析时的Compat格局,而不支持路由营造时的Compat形式。
CI框架的url
CI框架支持二种档案的次序的url分别为 :
- url分段(pathinfo)模式,例如
example.com/Test/index/param_value,这种办法是CI暗中认可帮助的url情势,这种url极度直观,域名后边的是url分段音讯- Test对应的是决定器类
- index对应的是我们要调用的类措施
- param_value对应的是大家传递给艺术的参数
- 伪静态的方式,例如
example.com/Test/index.shtml,这种url形式让页面看起来像静态页面同样,这种url有利于SEO优化 - 询问字符串格局,例如
example.com?c=test&m=index¶m=param_value,这种url形式必要在config.php文件中开启($config[‘enable_query_strings’]
true),并可安插相应的查询字段用来收获调整器,方法等,查询字符串字段的意思如下
- c为垄断(monopoly)器类
- m为类方法
- param为参数
一.路由定义
日增3个布局项U奥德赛L_MODE,找到创设Url的类\think\Url::build方法,在参数组装的片段和脚本名与参数连接处做小说。
CI框架的路由配置
是因为一时候是因为某个目标大家并不想暴光大家的调控器和章程,只怕大家想对走访的能源做一些有别于,那时候我们就能够借助路由体制来完毕我们的这种供给,路由安插文件位于applications/config/routes.php中,我们能够配备相关的路由和默许调整器
CI框架的路由支持路由安插形式分别为(这个url风格都属于pathinfo):
- 通配符形式,举个例子 $route[‘index/(:any)’] = ‘Test/index/$1’;
- (:any)是url中的参数,它是末端$一的值。
- 正则表达式,举例 $route[‘([a-z]+)/(\d+)’] =
‘Test/$1/$2/’;借使大家走访二个形如
example.com/index/2那样的url,利用正则表明式的规则,依旧会深入分析出pathinfo形式的url(Test/index/贰)- $壹,$2是对正则表明式子匹配的逆向引用
- 回调函数,举个例子 $route[”([a-z]+)/(\d+)”] = callback($method,
$id),不一致于通配符和正则表达式的硬编码映射url,大家得以在回调函数中动态再次回到要映射的url - REST风格的路由,比方 $route[‘index’][‘put’] =
‘Test/index’;这种路由格局相似用在Rest API中
好了,通过地点的叙说大家发现url的风骨和路由的布置形形色色,想想路由深入分析都感到好复杂,大家进入Router.php中看看CI框架的url深入分析和路由管理是何许的
要使用路由效用须求援助PATH_INFO,PATH_INFO是怎么吧?手册中涉嫌“要动用路由成效,前提是您的ULacrosseL协理PATH_INFO(也许包容U汉兰达L形式也能够,选拔一般U途胜L形式的气象下不支持路由成效),”
,
url帮助path_info,不是apache要支持path_info么,度娘讲的还算清楚一些,见下文:
// 参数组装
if (!empty($vars)) {
// 添加参数
if (Config::get('url_common_param')) {
$vars = urldecode(http_build_query($vars));
$url .= $suffix . ((Config::get('URL_MODE') == static::MODE_COMPAT) ? '&' : '?') . $vars . $anchor;
} else {
$paramType = Config::get('url_param_type');
foreach ($vars as $var => $val) {
if ('' !== trim($val)) {
if ($paramType) {
$url .= $depr . urlencode($val);
} else {
$url .= $depr . $var . $depr . urlencode($val);
}
}
}
$url .= $suffix . $anchor;
}
} else {
$url .= $suffix . $anchor;
}
// 检测域名
$domain = self::parseDomain($url, $domain);
// URL组装
$path_sep = '/';
if (Config::get('URL_MODE') == static::MODE_COMPAT) {// 兼容模式判断
$path_sep = '?' . Config::get('var_pathinfo') . '=';
}
$url = $domain . rtrim(self::$root ?: Request::instance()->root(), '/') . $path_sep . ltrim($url, '/');
Router.php
我们上节使用反射也看到这些$RT昂科雷正是CI_Router这么些类,也来占星关的调控器和章程都是在路由目的$RT奔驰M级上取的,所以在构造方法中应当深入分析了全部,咱俩的指标唯有1个就是弄清楚class和method是怎么剖判的?进去Router.php观看构造方法。
首先观看构造方法有个$routing参数,上边包车型客车注释说的很清楚了,它是在index.php中设置并用来重写路由的,什么看头?就是无论你是这种路由,都将您隐射到同一个地方去,这一个$routing是在index.php定义的中,那是CI提供的二个可选的方案,所以它是被批注掉的;
在index.php中$routing的设置被解说掉了
随即往下见到,加载了config对象和Url指标,接着决断url是还是不是启用了查询字符串形式,然后调用set_routing方法
构造方法
进入set_routing方法,该方法的逻辑由两片段组成,查询字符串格式的路由剖判和pathinfo格式的路由深入分析
image.png
pathinfo
(PHP 4 >= 4.0.3, PHP 5)
pathinfo — 重回文件路线的音信
说明
array pathinfo ( string path [, int options] )
pathinfo() 返回一个涉及数组包括有 path
的消息。包蕴以下的数组单元:dirname,basename 和 extension。
能够因而参数 options
内定要回到哪些单元。它们包含:PATHINFO_DIRNAME,PATHINFO_BASENAME 和
PATHINFO_EXTENSION。私下认可是回去全部的单元。
例子 1. pathinfo() 例子
<?php
$path_parts = pathinfo(“/www/htdocs/index.html”);
echo $path_parts[“dirname”] . “\n”;
echo $path_parts[“basename”] . “\n”;
echo $path_parts[“extension”] . “\n”;
?>
上例将出口:
/www/htdocs
index.html
html
查询字符串格式的路由深入分析
那块是询问字符串风格的url路由解析的最大旨的地点了,并且终于能够解答我们壹开始的疑团class和method到底是怎么来的?看代码
/*
通过$this->enable_query_strings为true进入查询字符串路由解析,我们前面说了这种url的风格为:
example.com?c=test&m=index¶m=param_value,
接下来通过读取配置directory_trigger值来判断url中是否指定了要访问的目录,
如果设置了就将其中的一些非打印字符给去掉,如果指定了访问的目录,那么就通过set_directory方法设置目录
*/
if ($this->enable_query_strings)
{
$_d = $this->config->item('directory_trigger');
$_d = isset($_GET[$_d]) ? trim($_GET[$_d], " \t\n\r\0\x0B/") : '';
if ($_d !== '')
{
$this->uri->filter_uri($_d); //过滤非法字符
$this->set_directory($_d);
}
/*
接着读取控制器字段的配置值,通过该字段值读取url中要访问的控制器,终于看到了class和method是怎么解析得到的了
*/
$_c = trim($this->config->item('controller_trigger'));
if ( ! empty($_GET[$_c])) //url中的控制器是否存在
{
$this->uri->filter_uri($_GET[$_c]); //和上面一样,校验是否有非法字符
/*
如果从url中读取的控制器类不为控制,那么就设置我们要访问的类,
原来$TRT->class读的class就是在这个方法中设置的
*/
$this->set_class($_GET[$_c]); //set_class和set_method下文有截图
$_f = trim($this->config->item('function_trigger'));//获取方法字段的配置值
if ( ! empty($_GET[$_f]))
{
$this->uri->filter_uri($_GET[$_f]); //校验非法字符
/*
如果从url中读取的控制器类不为控制,那么就设置我们要访问的类,
原来$TRT->method读的method就是在这个方法中设置的
*/
$this->set_method($_GET[$_f]);
}
/*
| 这一块是路由解析中非常重要的一段逻辑,在后面的分析中,我们始终看到凡是
| 跟路由解析中url对象相关联的代码,其始终都在维护这样一个片段数组,第一个元素是控制器类,第二个是方法
*/
//在url对象中存储相关的类和方法
$this->uri->rsegments = array(
1 => $this->class,
2 => $this->method
);
}
else //如果url中控制器是空的,那就设置默认的控制器类和方法
{
$this->_set_default_controller();
}
return;
}
关于_set_default_controller()方法设置私下认可调节器的逻辑有不可或缺看下,因为当用户只是输入域名的境况必须有所管理吧,进入_set_default_controller方法看下
_set_default_controller()
地点的代码中set_directory(),set_class(),set_method()方法体如下图
//设置url映射的控制器类
public function set_class($class)
{
$this->class = str_replace(array('/', '.'), '', $class);
}
//设置url映射的方法
public function set_method($method)
{
$this->method = $method;
}
//设置要加载控制器类的目录
public function set_directory($dir, $append = FALSE)
{
if ($append !== TRUE OR empty($this->directory))
{
$this->directory = str_replace('.', '', trim($dir, '/')).'/';
}
else
{
$this->directory .= str_replace('.', '', trim($dir, '/')).'/';
}
}
询问字符串风格的路由解析就到此地了,接下去看看pathinfo风格的。
本身去写这段的时候在自己的机器上一直不出口任何内容,后来作者在php安装目录下找到php.ini开启了cgi.fix_pathinfo=1,然后真的能够出口上边的内容。
pathinfo风格的路由分析
看代码
/*
如果存在路由配置文件,加载路由配置和特定环境(例如有些url可能只在测试环境能访问)的路由配置
*/
if (file_exists(APPPATH.'config/routes.php'))
{
include(APPPATH.'config/routes.php');
}
if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/routes.php'))
{
include(APPPATH.'config/'.ENVIRONMENT.'/routes.php');
}
/*
如果路由配置存在,获取到相应的默认控制器和url短线转换的标识,然后从路由数组中删除这两个,最后将路由数组赋值给routes变量,
关于translate_uri_dashes见:
https://codeigniter.org.cn/user_guide/general/routing.html?highlight=translate_uri_dashes#id6
*/
if (isset($route) && is_array($route))
{
isset($route['default_controller']) && $this->default_controller = $route['default_controller'];
isset($route['translate_uri_dashes']) && $this->translate_uri_dashes = $route['translate_uri_dashes'];
unset($route['default_controller'], $route['translate_uri_dashes']);
$this->routes = $route;
}
/* 接下来根据uri_string是否为空来决定走配置配置文件或者默认路由*/
if ($this->uri->uri_string !== '')
{
$this->_parse_routes();
}
else
{
$this->_set_default_controller();
}
地方的代码最后1段逻辑中来看$this->uri->uri_string,该属性到底是如何?因为后文的路由剖析和此变量关联非常大,大家先不用往下深入分析parse_toutes()方法了,进入url对象对应的文本U牧马人I.php中看下uri_string是什么?
而是那只是讲了1个函数的用法,正是pathinfo那些函数会回去三个数组,里面有多个成分分别是
URI.php
进去后观望构造方法
public function __construct()
{
//加载配置对象用来读配置信息
$this->config =& load_class('Config', 'core');
// 只处理来自命令行请求或者pathinfo风格的url请求
if (is_cli() OR $this->config->item('enable_query_strings') !== TRUE)
{
$this->_permitted_uri_chars = $this->config->item('permitted_uri_chars');
if (is_cli()) //解析命令行的url请求
{
$uri = $this->_parse_argv();
}
else //pathinfo格式的请求
{
/*
这里读取uri_protocol,并且我们可以在配置文件中看到uri_protocol 的默认值是REQUEST_URI,
接下来根据uri_protocol的类型在switch...case从句来决定使用哪种解析方式, 总之就两种解析uri的
方式
*/
$protocol = $this->config->item('uri_protocol');
empty($protocol) && $protocol = 'REQUEST_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;
}
}
//当url解析完成后,调用_set_uri_string($uri),我们猜这里会不会设置了$this->uri->uri_string?
$this->_set_uri_string($uri);
}
log_message('info', 'URI Class Initialized');
}
这段代码大家剖判完了,但照旧有八个问号
第三个疑问,_亚洲必赢,parse_request_uri()和_parse_query_string()重返的变量$uri是怎么着,那多个措施有何样分化?
_parse_request_uri()方法
protected function _parse_request_uri()
{
//判断浏览器域名的原生url和脚本名是否存在,如果不存在就返回一个空$uri,本质上是判断用户是否只是访问了我们的网站域名
if ( ! isset($_SERVER['REQUEST_URI'], $_SERVER['SCRIPT_NAME']))
{
return '';
}
//如果是带有url访问,那么解析这个url,得到查询字符串和相关的path
$uri = parse_url($_SERVER['REQUEST_URI']);
$query = isset($uri['query']) ? $uri['query'] : '';
$uri = isset($uri['path']) ? $uri['path'] : '';
/*解析处理类似example.com/index.php/Test/index这种携带有入口文件的url,本质上是去掉REQUEST_URI中的SCRIPT_NAME,
注意:REQUEST_URI是浏览器原生url,也就是域名后面的url段*/
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'])));
}
}
/*解析处理类似example.com/index.php/?/Test/index?name=tcl这种path位于查询字符串的url,
解析得到uripath和查询字符串,并将原生的查询字符串重写*/
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;
}
/*
将原生查询字符串解析并设置到当前作用域,一旦解析到当前作用域,就可以使用php内置超全局预定义变量访问,
此时解析原生查询字符串,我们就可以通过超全局预定义变量$_GET[]来获取查询字符串的值,关于超全局预定义变量见:
http://php.net/manual/zh/language.variables.superglobals.php
*/
parse_str($_SERVER['QUERY_STRING'], $_GET);
//用户访问的只是根域名就返回
if ($uri === '/' OR $uri === '')
{
return '/';
}
//最后对path做一个清洗,因为url的格式可能为example.com/index.php/.././Test/index这种含有相对路径或绝对路径的url
return $this->_remove_relative_directory($uri);
}
_parse_query_string()方法
protected function _parse_query_string()
{
//检测是否只是访问了域名
$uri = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : @getenv('QUERY_STRING');
if (trim($uri, '/') === '')
{
return '';
}
//检测path是否位于查询字符串中,例如example.com/index.php/?/Test/index?name=tcl
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);
//对url做清洗
return $this->_remove_relative_directory($uri);
}
上述多个方法都用了_remove_relative_directory()对url中的相对路径做保洁,看下此措施
/*
* 由于url可能是example.com/test../test./other/这种,
* 此函数就是用来去除url中的相对路径
*
* */
protected function _remove_relative_directory($uri)
{
$uris = array();
//strtok和str_split类似也都是切割字符串,
//不同处在于strtok会在第一次切割之后记住切割字符的位置,
//之后就不再需要传原字符串了
$tok = strtok($uri, '/');
while ($tok !== FALSE)
{
if (( ! empty($tok) OR $tok === '0') && $tok !== '..')
{
$uris[] = $tok;
}
$tok = strtok('/');
}
return implode('/', $uris);
}
通过对_parse_request_uri()和_parse_query_string()那两个章程深入分析,我们看看_parse_query_string其实是对uri_protocol不支持REQUEST_U牧马人I的降级处理而已,那多个法子都回去了url中的path部分,那样大家就知晓原本构造方法中的变量$uri正是url中path,该变量作为参数字传送递给了$this->_set_uri_string($uri)那个措施。
第贰个问号,$this->_set_uri_string($uri)那一个法子会不会安装了我们深入分析pathinfo风格路由时见到的$this->uri->uri_string那个个性?
_set_uri_string()方法
protected function _set_uri_string($str)
{
//很显然uri_string这个属性是在这被设置的,它只是移除了一些非打印字符的$uri变量而已,
//很清楚了,uri_string就是url中path部分
$this->uri_string = trim(remove_invisible_characters($str, FALSE), '/');
/*
判断path是否为空,为空的$this->uri_string会导致路由解析走默认控制器,这个我们在解读_parse_routes()方法时会看到
*/
if ($this->uri_string !== '')
{
//由于我们的url可能是example.com/Test/test.html这种静态页面,要想解析到正确的path就需要我们把后缀名去掉
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);
}
}
/*
将path分割依然维护一个片段数组,第一个元素是控制器类,第二个是方法
*/
$this->segments[0] = NULL;
foreach (explode('/', trim($this->uri_string, '/')) as $val)
{
$val = trim($val);
// 和之前一样,校验是否有非法字符
$this->filter_uri($val);
if ($val !== '')
{
$this->segments[] = $val;
}
}
unset($this->segments[0]);
}
}
因此解读该方法,大家看来其也可以有限协助了八个url片段数组,并且$this->uri->uri_string那么些天性便是在那设置的,$this->uri->uri_string就是url中的path部分。
其余,大家在前文的分析中解析蒙受了几个工具被一再使用的工具函数,大家有必不可缺看下它们
先是个,移除非打字与印刷字符的的函数remove_invisible_characters()
/*
* 此函数主要移除ascii码表中非打印字符,
* 包含0-31,以及127的删除符
*
* */
function remove_invisible_characters($str, $url_encoded = TRUE)
{
$non_displayables = array();
//由于回车\r(13),换行\n(10),制表\t(9)这些用于文本格式的字符不需要过滤,
//所以下面的处理排除了这三个字符
if ($url_encoded)
{
$non_displayables[] = '/%0[0-8bcef]/'; // url encoded 00-08, 11, 12, 14, 15
$non_displayables[] = '/%1[0-9a-f]/'; // url encoded 16-31
}
$non_displayables[] = '/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S'; // 00-08, 11, 12, 14-31, 127
do
{
$str = preg_replace($non_displayables, '', $str, -1, $count);
}
while ($count);
return $str;
}
ascii码表非打印字符见这里
换行符”\n”和回车符”\r”,回车符应该适量来讲叫做回车换行符
● 换行符就是另起一行 — “\n” 10 换行(newline)
● 回车符正是回去1行的发端 — “\r” 13 回车(return)
● Windows系统里面,每行结尾是回车+换行(CRubicon+LF),即”\r\n”;
● Unix系统里,每行结尾唯有换行LF,即”\n”;
● Mac系统里,每行结尾是回车C牧马人 即”\r”。
第3个,过滤恶意字符的函数filter_uri()
/*
* 这个函数主要过滤一些恶意的字符,因为恶意的攻击者可能从url中发起xss攻击,
* 所以CI在配置文件config.php中的permitted_uri_chars指定了允许url允许的字符,
* 一旦检测到恶意字符就报404
*
* permitted_uri_chars本身是一段正则
* $config['permitted_uri_chars'] = 'a-z 0-9~%.:_\-';
*
* */
public function filter_uri(&$str)
{
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);
}
}
从那之后整个U奔驰M级I对象(CI_UEscortI)的解读就做到了,虽说比较麻烦,但见到,U奥迪Q伍I对象就做了两件事,第一是在拜访的url中分析出path,那些path将会用在路由安顿中索求真正映射的调节器和格局,第3是在path的根底上体贴一个片段数组,这些数组存款和储蓄了url中的类和办法
dirname:小编的掌握,服务器名称加上路线,
basename:访问的公文名,
extension:文件的扩张名
接轨pathinfo风格的路由分析
最近我们精晓上文中pathinfo风格的路出分析中我们看看的代码最终边的$this->uri->uri_string原来便是url中的path部分
if ($this->uri->uri_string !== '')
{
$this->_parse_routes();
}
else
{
$this->_set_default_controller();
}
既然如此获得了path,这就去路由安顿文件中相称真正映射的调控器类和办法,那总体在_parse_routes()方法中贯彻,在该方法中我们看看其管理了作者们此前在CI框架的路由配置提到的种种配备格局
protected function _parse_routes()
{
//从url片段数组中组装出path,因为我们知道该数组保存了两个元素,一个是控制器类,另一个是方法
$uri = implode('/', $this->uri->segments);
// 这里得到请求方法是因为rest风格的路由配置中需要指明请求的动作,这将帮助我们匹配到rest路由
$http_verb = isset($_SERVER['REQUEST_METHOD']) ? strtolower($_SERVER['REQUEST_METHOD']) : 'cli';
/*
* 这里匹配我们在配置文件中硬编码配置的路由和硬编码的rest风格的路由
* */
if (isset($this->routes[$uri]))
{
if (is_string($this->routes[$uri]))
{
$this->_set_request(explode('/', $this->routes[$uri]));
return;
}
elseif (is_array($this->routes[$uri]) && isset($this->routes[$uri][$http_verb]))
{
$this->_set_request(explode('/', $this->routes[$uri][$http_verb]));
return;
}
}
/*
* 这里匹配我们没有硬编码的动态路由,从CI框架的路由配置中知道,这几种动态路由为:
* 通配符路由,正则路由,回调函数路由,这几种路由都转换成了正则路由然后进行了匹配
* */
foreach ($this->routes as $key => $val)
{
//检查是否为rest风格的动态路由,并获取真正的映射值
if (is_array($val))
{
if (isset($val[$http_verb]))
{
$val = $val[$http_verb];
}
else
{
continue;
}
}
//将通配符路由转换成正则路由
$key = str_replace(array(':any', ':num'), array('[^/]+', '[0-9]+'), $key);
//根据正则路由去匹配
if (preg_match('#^'.$key.'$#', $uri, $matches))
{
/*
* 如果正则路由的映射值$val是回调函数,因为该函数的参数是正则表达式的子匹配,通过array_shift($matches)
* 来获取子匹配作为参数,然后通过call_user_func_array($val, $matches);实现对该回调函数的调用并返回真正的映射值
* */
if ( ! is_string($val) && is_callable($val))
{
array_shift($matches);
$val = call_user_func_array($val, $matches);
}
/* 由于在正则路由中,我们在映射部分还能使用逆向引用,例如$route['login/(.+)'] = 'auth/login/$1';
那么我们就需要将path中配置到的这部分换成解析逆向引用后的映射值*/
elseif (strpos($val, '$') !== FALSE && strpos($key, '(') !== FALSE)
{
$val = preg_replace('#^'.$key.'$#', $val, $uri);
}
$this->_set_request(explode('/', $val));
return;
}
}
//如果走到这里还没有匹配到的话,只能将url片段传给_set_request做最后的处理
$this->_set_request(array_values($this->uri->segments));
}
在地点的_parse_routes()方法中大家看出从url片段数组中得到path去路由安顿文件去相称真正的映射值,可是当中有个_set_request()方法是个什么样鬼?大家来看其将真正映射值分割并传播在那之中,这一个法子已经不行清楚了保卫安全3个瓜分了确实映射值的url片段数组
protected function _set_request($segments = array())
{
/*
* 因为即使我们拿到了真正映射的url片段数组,我们还是依然无法确保映射值对应的
* 控制器类是否存在,这就需要调用_validate_request方法去验证下,并重置url片段数组
* */
$segments = $this->_validate_request($segments);
if (empty($segments))
{
$this->_set_default_controller();
return;
}
//短线转下划线,感觉这玩意很鸡肋,最好还是别设置为true
if ($this->translate_uri_dashes === TRUE)
{
$segments[0] = str_replace('-', '_', $segments[0]);
if (isset($segments[1]))
{
$segments[1] = str_replace('-', '_', $segments[1]);
}
}
/*
* 设置类和方法,这里有一点要注意,如果没有方法,将会以控制器类中的index方法作为默认,
* 这也是为什么我们访问example.com/Test/ 时发现进入index方法的原因
* */
$this->set_class($segments[0]);
if (isset($segments[1]))
{
$this->set_method($segments[1]);
}
else
{
$segments[1] = 'index';
}
//重置url的片段数组的索引
array_unshift($segments, NULL);
unset($segments[0]);
$this->uri->rsegments = $segments;
}
下面是_validate_request()方法
/*
*从url片段数组中尝试去加载控制器类,
* 一旦载入成功后设置目录,并重置url片段数组,
* */
protected function _validate_request($segments)
{
$c = count($segments);
while ($c-- > 0)
{
$test = $this->directory
.ucfirst($this->translate_uri_dashes === TRUE ? str_replace('-', '_', $segments[0]) : $segments[0]);
if ( ! file_exists(APPPATH.'controllers/'.$test.'.php') && is_dir(APPPATH.'controllers/'.$this->directory.$segments[0]))
{
$this->set_directory(array_shift($segments), TRUE);
continue;
}
return $segments;
}
//该数组存储了最终映射的控制器类和方法以及相关的参数
return $segments;
}
下边用流程图理一下总体路由解析的历程
image.png
迄今停止整个路由对象对路由分析的代码解读就截至了,大家开采路由对象就做了四件事
- 安装目录set_directory()
- 设置调整器类set_class()
- 设置类方式set_method()
- 爱惜二个url片段数组。
服务器帮助后还须求在配备文件中开荒相应的配备,
// 开启路由
'URL_ROUTER_ON' => true,
以此布局在ThinkPHP\Conf\convention.php文件中也有定义,可是能够在模块的计划文件中覆盖它。然后便是定义路由规则了,路由的合营是根据先到先得的各样来展开的,在第壹遍相配成功之后就不会重复相称了,而是访问那些路由中的调整器和艺术,并传播参数,得到结果。
路由规则定义的格式是:‘路由说明式’=>’路由地点和扩散参数’
或 array(‘路由表明式’,’路由地点’,’传入参数’)**
路由表达式包括平整路由和正则路由
表达式 示例
正则表明式 /^blog\/(\d+)$/
规则表明式 blog/:id
路由地址表示近期路由表明式须求路由到的地址,包含个中地址和表面地址,并允许隐式传入url里面没有的参数,允许接纳字符串大概数组的主意定义,特殊意况下可使用闭包函数定义路由作用,帮衬上面陆中方法定义,
定义方式 | 定义格式 |
---|---|
方式1:路由到内部地址(字符串) | ‘[分组/模块/操作]?额外参数1=值1&额外参数2=值2…’ |
方式2:路由到内部地址(数组)参数采用字符串方式 | array(‘[分组/模块/操作]’,’额外参数1=值1&额外参数2=值2…’) |
方式3:路由到内部地址(数组)参数采用数组方式 | array(‘[分组/模块/操作]’,array(‘额外参数1’=>’值1’,’额外参数2’=>’值2’…)[,路由参数]) |
方式4:路由到外部地址(字符串)301重定向 | ‘外部地址’ |
方式5:路由到外部地址(数组)可以指定重定向代码 | array(‘外部地址’,’重定向代码'[,路由参数]) |
方式6:闭包函数 | function($name){ echo ‘Hello,’.$name;} |
一心不懂啊!内部地址,外部地址?什么概念呢局域网中的地址吗?1玖2.168.0.1这么的?大学学的都遗忘了,百度了须臾间,貌似那样的,有3类内部地址,用来三番五次局域网中的设备,包涵打字与印刷机等?
路由以http可能/初叶的话会被认为是二个外部地址可能重定向地址,比如
‘blog/:id’=>’/blog/read/id/:1’
施用30一重定向的点子路由跳转,这种办法的便宜是url能够相比较随便,包涵在url里面传播更加多的非规范格式参数,擦完全不懂的,非标准格式参数为什么物?太tm职业了。
‘blog/:id’=>’blog/read’:只匡助模块和操作地址,举个例子大家期待avatar/123重定向到/member/avatar/id/123_small只能使用'avatar/:id'=>'/member/avatar/id/:1_small' 貌似:id代表一个参数,参数名字是id。路由地址采用重定向地址的话,如果要引用动态变量,也是采用 :1、:2
的点子。选拔重定向到表面地址平时对网址改版后的U奇骏L迁移进度十分有用,举个例子:’blog/:id’=>’http://blog.thinkphp.cn/read/:1‘ 表示近日网址(大概是http://thinkphp.cn)的
blog/123
地址会直接重定向到 http://blog.thinkphp.cn/read/123
。
二.规则路由
规则表明式包罗静态地址和动态地址,也许是三种地址的叁结合,如下:
“my” => “Member/myinfo”, //静态路由地址
“blog/:id” => “blog/read”, //静态地址和动态地址结合,
“new/:year/:month/:day” => “news/read”, //静态地址和动态地址的组合
“:user/:blog_id” => “Blog/read”, //全动态地址
参数里面以“:”起头的正是动态参数,自动对应贰个get参数,举例”:id”表示此处能够动用$_GET[‘id’]来博取参数,而”:year”,”:month”,”:day”分别对应$_GET[‘year’],$_GET[‘month’],$_GET[‘day’]来获取。
数字约束:支持对变量类型检验,近日单纯协助对数字类型的牢笼定义,譬如’blog/:id\d’=>’blog/read’,表示只优秀数字参数,
函数协助:能够帮忙对路由变量的函数过滤,举例’blog/:id\d|md5′ =>
‘blog/read’,表示对郎才女貌到的变量实行md伍管理,实际传入read操作方法的$_GET[‘id’],其实是md5($_GET[‘id’]),不支持对变量使用频仍函数管理和函数额外参数管理。