这篇文章,我们来分析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的执行流程等机制。