Typecho 源码笔记 路由类
这篇文章,我们来分析typecho的路由部分,在我看来路由一直是很NB的东西,现在我们一起来分析一下typecho的路由到底是怎么个东西。
相关文件
typecho中的路由相关的文件不多,只存放于/var/Typecho/Router.php
和/var/Typecho/Router/
文件夹下面。
/var/Typecho/Router.php
一个很普通的Typecho_Router
类,看到TODO里写着以后要增加cache
,这样速度应该会提高不少吧。
这个类没有构造函数,所以我们就从头开始看吧,有三个变量,分别是$current
存储当前的路由名称,$_routeingTable
存储解析完成的路由表配置,最后是$_pathInfo
存储全路径。
先来看解析路径的函数,match()
,它接受两个参数,分别是全路径以及输入参数。
public static function match($pathInfo, $parameter = NULL)
{
foreach (self::$_routingTable as $key => $route) {
if (preg_match($route['regx'], $pathInfo, $matches)) {
// 如果在路由表中匹配到了,就将current赋值为该路由的名称
// 猜测$_routingTable的第一维存储的是每个路由的名称,然后有一个'regx'字段存储的是路由规则
// 即$_routingTable['routeName']
// 其中每一项的内容:$routingTable['routeNameA']['regx'] => '/archive/123.html'
self::$current = $key;
try {
/** 载入参数 */
$params = NULL;
if (!empty($route['params'])) { // 如果$route['params']非空
unset($matches[0]); // 撤销变量
// 创建一个新的数组,$route['params']作为新数组的key,$matches作为新数组的value
$params = array_combine($route['params'], $matches);
}
// 这里用到了Typecho_Widget的工厂方法widget,这个我们后面在分析Widget的时候再说
// widget是typecho很重要的组成部分,是基本元素,除了抽象出来的类库,其他的几乎所有功能
// 都会通过widget完成。
$widget = Typecho_Widget::widget($route['widget'], $parameter, $params);
// 会返回一个关于路由的widget?
return $widget;
} catch (Exception $e) {
// 没有匹配到的话就返回404吧大概
if (404 == $e->getCode()) {
Typecho_Widget::destory($route['widget']);
continue;
}
throw $e;
}
}
}
return false;
}
我自己的一些理解写到了注释里,可能有些地方不太对,等后面看完了widget
的部分再回来修改。
接下来我们看一看dispatch()路由分发函数,这个和上面的match()函数基本一致,唯一不同的是增加了一段关于widget的代码:
if (isset($route['action'])) {
$widget->{$route['action']}();
}
应该是调用了这个widget的暂时还不知道啥意思,后面再看。
下面会看到一个路由反解析函数,猜测最后应该是将路由解析成URL吧。但是其中遇到了一个Typecho_Common::url
,也只能等分析到Common
类的时候再看吧。
最后还剩下两个函数,分别是设置路由默认配置,以及获取路由信息的函数,获取路由信息的函数十分简单,没啥好说的了。而在设置路由默认配置中又遇到了Typecho_Router_Parser
这个类,下面我们就去分析这个文件。
/var/Typecho/Router/Parser.php
这个类叫做路由器解析器,先从构造函数开始看吧。
public function __construct(array $routingTable)
{
$this->_routingTable = $routingTable;
$this->_defaultRegx = array(
'string' => '(.%s)',
'char' => '([^/]%s)',
'digital'=> '([0-9]%s)',
'alpha' => '([_0-9a-zA-Z-]%s)',
'alphaslash' => '([_0-9a-zA-Z-/]%s)',
'split' => '((?:[^/]+/)%s[^/]+)',
);
}
这个函数接受一个参数,为路由器映射表。其实就是给两个本类的变量赋了值。
继续向下看,有一个_match()
函数,这个函数的作用是局部匹配并替换正则字符串。这个函数根据获得的参数数量,返回不同的内容,意义还不是很明显,我们结合下面的parse()
函数来看(parse()
函数中会使用_match
函数)。
parse()
函数的作用是解析路由表,其中preg_replace_callback
的作用是执行一个正则表达式搜索并且使用一个回调进行替换,这里就是使用的_match
作为cb
函数来使用的,接下来就是一大堆正则的匹配了。
public function parse()
{
$result = array();
foreach ($this->_routingTable as $key => $route) {
$this->_params = array();
// 先将$route['url']字符串中的'[',']',':'替换为'%','%',' '.
// 然后在这个字符串中搜索"/%([^%]+)%/",然后将结果作为_match的参数并调用cb函数
// 这样也就能理解在_match函数中开始的操作了
$route['regx'] = preg_replace_callback("/%([^%]+)%/", array($this, '_match'),
preg_quote(str_replace(array('[', ']', ':'), array('%', '%', ' '), $route['url'])));
/** 处理斜线 */
// 删除末尾的斜杠
$route['regx'] = rtrim($route['regx'], '/');
// 然后前后加上一部分东西
$route['regx'] = '|^' . $route['regx'] . '[/]?$|';
$route['format'] = preg_replace("/\[([^\]]+)\]/", "%s", $route['url']);
$route['params'] = $this->_params;
$result[$key] = $route;
}
return $result;
}
一些说明已经写到注释中了,再结合前面的一大堆正则,看的好蛋疼。再回过头来看_match()
函数的后半部分,按照参数个数操作的地方,这里用了sprintf
,然后刚刚在$_defaultRegx初始化的时候,其中有包含格式化控制符%s,所以这里的第二个参数可以直接替换掉其中%s的地方,例如return sprintf($this->_defaultRegx['char'], '+');
,实际上返回的就是([^/]+)
,其他的一个道理,只是复杂些。如果想获得详细的变量内容,还是var_dump出来看一下比较好。
到这里就分析完了路由的部分,接下来可能分析一些比较杂乱的部分,由于本系列文章没有按照执行流程来分析,而是一个部分一个部分的分析,所以可以不按照顺序来阅读,不会有影响,等所有的模块都分析完后,我会分析typecho的执行流程等机制。