Router
在 web 程序中,Router 负责解析用户的 uri,匹配路由表。再根据路由表信息,选择相对应的函数(function)或方法(method)。
一、入口函数
index.php
Typecho_Widget::widget('Widget_Init');
Typecho_Router::dispatch();
代码分析: 1. 初始化路由器 2. 开始路由分发
二、初始化路由表
typecho/var/Widget/Init.php
class Widget_Init extends Typecho_Widget
{
public function execute()
{
/** 对变量赋值 */
$options = $this->widget('Widget_Options');
if (defined('__TYPECHO_PATHINFO_ENCODING__')) {
$pathInfo = $this->request->getPathInfo(__TYPECHO_PATHINFO_ENCODING__, $options->charset);
} else {
$pathInfo = $this->request->getPathInfo();
}
Typecho_Router::setPathInfo($pathInfo);
/** 初始化路由器 */
Typecho_Router::setRoutes($options->routingTable);
}
}
class Widget_Options extends Typecho_Widget
{
public function execute()
{
$this->db->fetchAll($this->db->select()->from('table.options')
->where('user = 0'), array($this, 'push'));
/** 自动初始化路由表 */
$this->routingTable = unserialize($this->routingTable);
if (!isset($this->routingTable[0])) {
/** 解析路由并缓存 */
$parser = new Typecho_Router_Parser($this->routingTable);
$parsedRoutingTable = $parser->parse();
$this->routingTable = array_merge(array($parsedRoutingTable), $this->routingTable);
$this->db->query($this->db->update('table.options')->rows(array('value' => serialize($this->routingTable)))
->where('name = ?', 'routingTable'));
}
}
}
class Typecho_Router
{
public static $_routingTable = array();
public static function setRoutes($routes)
{
if (isset($routes[0])) {
self::$_routingTable = $routes[0];
} else {
/** 解析路由配置 */
$parser = new Typecho_Router_Parser($routes);
self::$_routingTable = $parser->parse();
}
}
}
代码分析:
1. 组件Widget_Options读取数据库表typecho_options中name的值为routingTable的记录,反序列化该记录中的value列,得到一个路由表数组。
2. 默认情况下,这个路由表数组的有效路由保存在索引值为0的元素里面,如果不存在该索引,则解析路由并将结果更新到数据库。
3. 最后,将 $options->routingTable
的值保存到 Typecho_Router::$_routingTable
中。
二、开始路由分发
typecho/var/Typecho/Router.php
class Typecho_Router
{
public static function dispatch()
{
/** 获取PATHINFO */
$pathInfo = self::getPathInfo();
foreach (self::$_routingTable as $key => $route) {
if (preg_match($route['regx'], $pathInfo, $matches)) {
self::$current = $key;
try {
/** 载入参数 */
$params = NULL;
if (!empty($route['params'])) {
unset($matches[0]);
$params = array_combine($route['params'], $matches);
}
$widget = Typecho_Widget::widget($route['widget'], NULL, $params);
if (isset($route['action'])) {
$widget->{$route['action']}();
}
return;
} catch (Exception $e) {
if (404 == $e->getCode()) {
Typecho_Widget::destory($route['widget']);
continue;
}
throw $e;
}
}
}
/** 载入路由异常支持 */
throw new Typecho_Router_Exception("Path '{$pathInfo}' not found", 404);
}
}
// 路由表长这个样子:
Array
(
[index] => Array
(
[url] => /
[widget] => Widget_Archive
[action] => render
[regx] => |^[/]?$|
[format] => /
[params] => Array()
)
[archive] => Array
(
[url] => /blog/
[widget] => Widget_Archive
[action] => render
[regx] => |^/blog[/]?$|
[format] => /blog/
[params] => Array()
)
)
代码分析:
1. 获取 PATHINFO,比如用户访问的URL为:http://localhost/typecho/index.php/123/456?a=1,则PATHINFO为:/123/456。
2. 根据 PATHINFO 的值,在路由表中遍历,寻找是否有匹配的路由规则。(主要代码:preg_match($route['regx'], $pathInfo, $matches
)
3. 以 PATHINFO 为 / 为例。遍历路由表,最终找到匹配项:index。接着,调用 Typecho_Widget::widget("Widget_Archive", NULL, $params)
生成一个 Widget_Archive 实例。如果匹配值存在 action 索引,则获取该匹配值索引号为 action 的值,这里为 render。接着,调用 Widget_Archive 的 render 方法。(部分代码:$widget->{$route['action']}();
)
相关资料
备注:官方的设计文档很久没更新(最后编辑时间:2013/10/21 14:55),和现在最新的 typecho 代码有很大出入。