Url管理

在Web开发中,对于URL有一些共性的需求,如:

  • 统一、简洁的URL创建方式
  • URL的伪静态化(美化)处理
  • 从URL中解析出相应的路由信息,引导应用执行后续处理

这些功能在 前面我们讲的 UrlRule 层面已经得到了一定程度的实现。 但从层次上来讲,UrlRule 更偏向于基础一些,直接使用 UrlRule 相对而言还不是很方便。

比如,针对各种类型的URL,我们需要提供相应的 UrlRule 实例来进行处理。 这些实例如何进行统一管理,相互关系怎么处理?都无法在 UrlRule 自身层面解决。

我们需要更贴近开发的接口。于是Yii把Web应用中对于URL的常用要求抽象到了urlManager中, 并作为Web应用的核心组件,更便于开发者使用。

urlManager概览

urlManager组件由 yii\web\UrlManager 类定义:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
class UrlManager extends Component
{

    // 用于表明urlManager是否启用URL美化功能,在Yii1.1中称为path格式URL,
    // Yii2.0中改称美化。
    // 默认不启用。但实际使用中,特别是产品环境,一般都会启用。
    public $enablePrettyUrl = false;

    // 是否启用严格解析,如启用严格解析,要求当前请求应至少匹配1个路由规则,
    // 否则认为是无效路由。
    // 这个选项仅在 enablePrettyUrl 启用后才有效。
    public $enableStrictParsing = false;

    // 保存所有路由规则的配置数组,并不在这里保存路由规则的实例
    public $rules = [];

    // 指定续接在URL后面的一个后缀,如 .html 之类的。仅在 enablePrettyUrl 启用时有效。
    public $suffix;

    // 指定是否在URL在保留入口脚本 index.php
    public $showScriptName = true;

    // 指定不启用 enablePrettyUrl 情况下,URL中用于表示路由的查询参数,默认为 r
    public $routeParam = 'r';

    // 指定应用的缓存组件ID,编译过的路由规则将通过这个缓存组件进行缓存。
    // 由于应用的缓存组件默认为 cache ,所以这里也默认为 cache 。
    // 如果不想使用缓存,需显式地置为 false
    public $cache = 'cache';

    // 路由规则的默认配置,注意上面的 rules[] 中的同名规则,优先于这个默认配置的规则。
    public $ruleConfig = ['class' => 'yii\web\UrlRule'];

    private $_baseUrl;
    private $_scriptUrl;
    private $_hostInfo;

    // urlManager 初始化
    public function init()
    {
        parent::init();
        // 如果未启用 enablePrettyUrl 或者没有指定任何的路由规则,
        // 这个urlManager不需要进一步初始化。
        if (!$this->enablePrettyUrl || empty($this->rules)) {
            return;
        }

        // 初始化前, $this->cache 是缓存组件的ID,是个字符串,需要获取其实例。
        if (is_string($this->cache)) {
            // 如果获取不到实例,说明应用不提供缓存功能,
            // 那么置这个 $this->cache 为false
            $this->cache = Yii::$app->get($this->cache, false);
        }

        // 如果顺利引用到了缓存组件,那么就将路由规则缓存起来
        if ($this->cache instanceof Cache) {

            // 以当前urlManager类的类名为缓存的键
            $cacheKey = __CLASS__;
            // urlManager所有路由规则转换为json格式编码后的HASH值,
            // 用于确保缓存中的路由规则没有变化。
            // 即外部没有对已经缓存起来的路由规则有增加、修改、
            // 删除、调整前后位置等操作。
            $hash = md5(json_encode($this->rules));

            // cache中是一个数组, 0号元素用于缓存创建好的路由规则,
            // 1号元素用于保存HASH值。这个判断用于确认是否有缓存、且缓存仍有效。
            // 是的话,直接使用缓存中的内容作为当前的路由规则数组。
            if (($data = $this->cache->get($cacheKey)) !== false
                && isset($data[1]) && $data[1] === $hash) {
                $this->rules = $data[0];

            // 如果尚未缓存或路由规则已经被修改导致缓存失效,
            // 那么重新创建路由规则并缓存。
            } else {
                $this->rules = $this->buildRules($this->rules);
                $this->cache->set($cacheKey, [$this->rules, $hash]);
            }

        // 要么是应用不提供缓存功能,要么是开发者将 $this->cache 手动置为false,
        // 总之,就是不使用缓存。那么就直接创建吧,也无需缓存了。
        } else {
            $this->rules = $this->buildRules($this->rules);
        }
    }

    // 增加新的规则
    public function addRules($rules, $append = true){ ... }

    // 创建路由规则
    protected function buildRules($rules){ ... }

    // 用于解析请求
    public function parseRequest($request){ ... }

    // 这2个用于创建URL
    public function createUrl($params){ ... }
    public function createAbsoluteUrl($params, $scheme = null){ ... }
}

在urlManager的使用上,用得最多的配置项就是:

  • $enablePrettyUrl ,是否开启URL美化功能。关于美化功能,我们在 路由(Route) 部分已经介绍过了。 注意如果 $enablePrettyUrl 不开启,表明使用原始的格式,那么所有路由规则都是无效的。
  • $showScriptName ,是否在URL中显示入口脚本。是对美化功能的进一步补充。
  • suffix 设置一个 .html 之类的假后缀,是对美化功能的进一步补充。
  • rules 保存路由规则们的声明,注意并非保存其实例。
  • $enableStrictParsing 是否开启严格解析。该选项仅在开启美化功能后生效。在开启严格解析模式时, 所有请求必须匹配 $rules[] 所声明的至少一个路由规则。 如果未开启,请求的PATH_INFO部分将作为所请求的路由进行后续处理。

UrlManager::init() 初始化过程中,可以发现 urlManager 使用了应用所提供的缓存组件(有果有的话), 对所有路由规则的实例进行缓存。

从架构上来讲,将所有请求交由入口脚本统一接收,再分发到相应模块进行处理的这种方式, 就注定了入口脚本有产生性能瓶颈的可能。但是带来的开发上的便利,却是实实在在的。 可以想像,在由Web Server进行请求分发的情景下,每个接收请求的脚本都要执行相同或类似的代码, 这会造成很冗余。而且会将权限控制、日志记录等逻辑上就应当作为所有请求第一关的的模块都分散到各处去。

因此,目前这种单一入口脚本的设计成为事实上的标准,几乎所有的Web开发框架都采用这种方式。 但这同时也对各框架的性能提出挑战。

在前面讲路由规则时,我们就体会到了初始化过程的繁琐,转换来转换去的。 如果采用简单粗暴的方式,Yii完全可以牺牲一定的开发便利性,在代码层面提高路由规则的性能。 比如,直接使用正则表达式。

但是,Yii没有这样做,而是很好地平稳了性能与开发便利性,通过将路由规则进行缓存来克服这个瓶颈。

TBD

如果觉得《深入理解Yii2.0》对您有所帮助,也请帮助《深入理解Yii2.0》。 谢谢!