# 快速入门(六):路由 ThinkPHP框架对URL有一定的规范,所以如果你希望定制你的URL格式的话,就需要好好了解下内置的路由功能了,它能让你的URL变得更简洁和有内涵。 ## 路由定义 路由定义一般包括三个配置参数: |参数|描述| |------|-----------| |URL_ROUTER_ON|开启路由,设置为true后路由规则定义生效| |URL_ROUTE_RULES|路由规则定义| |URL_MAP_RULES|静态路由(URL映射)定义| 要使用路由功能,前提是你的URL支持PATH_INFO(或者兼容URL模式也可以,采用普通URL模式的情况下不支持路由功能),并且在应用(或者模块)配置文件中开启路由: ~~~ 'URL_ROUTER_ON' => true, //开启路由 ~~~ 然后就是配置路由规则了,使用**URL_ROUTE_RULES**参数进行配置,配置格式是一个数组,每个元素都代表一个路由规则,例如: ~~~ 'URL_ROUTE_RULES'=>array( 'news/:year/:month/:day' => array('News/archive', 'status=1'), 'news/:id' => 'News/read', 'news/read/:id' => '/news/:1', ), ~~~ 系统会按定义的顺序依次匹配路由规则,一旦匹配到的话,就会定位到路由定义中的模块、控制器和操作方法去执行(可以传入其他的参数),并且后面的规则不会继续匹配。 路由规则一般是配置在模块的配置文件中(`模块名/Conf/config.php`),这样的话路由规则仅针对当前模块,并且URL中匹配的路由不能省略当前模块名(除了默认模块外)。如果需要定义全局的路由规则,就需要把路由配置定义在公共模块的配置文件中(`Common/Conf/config.php`),但全局路由定义的路由规则中必须包含明确的模块。 路由规则的定义方式如下: ~~~ '路由表达式'=>'路由地址和额外参数' ~~~ ## 路由表达式 路由表达式包括规则路由和正则路由的定义表达式,只能使用字符串。 |表达式 |示例| |-------|-------| |正则表达式 |/^blog\/(\d+)$/| |规则表达式 |blog/:id| **正则表达式** 路由表达式支持的正则定义必须以“/”开头,否则就视为规则表达式。也就是说如果采用 ~~~ '#^blog\/(\d+)$#' ~~~ 方式定义的正则表达式不会被支持,而会被认为是规则表达式进行解析,从而无法正确匹配。 ~~~ '/^new\/(\d{4})\/(\d{2})$/' => 'News/achive?year=:1&month=:2', ~~~ 对于正则表达式中的每个变量(即正则规则中的子模式)部分,如果需要在后面的路由地址中引用,可以采用:1、:2这样的方式,序号就是子模式的序号。 更多的关于如何定义正则表达式就不在本文的描述范畴了。 **规则表达式** 规则路由比正则路由更方便定义和容易理解,规则表达式通常包含静态地址和动态地址,或者两种地址的结合,例如下面都属于有效的规则表达式: ~~~ 'my' =>'Member/myinfo', // 静态地址路由 类似于之前版本的简单路由 'blog/:id' =>'Blog/read', // 静态地址和动态地址结合 'new/:year/:month/:day'=>'News/read', // 静态地址和动态地址结合 ':user/:blog_id'=>'Blog/read',// 全动态地址 ~~~ 规则表达式的定义以“/”为参数分割符(无论你的URL_PATHINFO_DEPR设置是什么,请确保在定义规则表达式的时候统一使用“/”进行URL参数分割)。 每个参数中以“:”开头的参数都表示动态参数,并且会自动对应一个GET参数,例如`:id`表示该处匹配到的参数可以使用$_GET['id']方式获取,`:year :month :day` 则分别对应`$_GET['year'] $_GET['month'] $_GET['day']`。 **数字约束** 支持对变量的类型检测,但仅仅支持数字类型的约束定义,例如 ~~~ 'blog/:id\d'=>'Blog/read', ~~~ 表示只会匹配数字参数,如果你需要更加多的变量类型检测,请使用正则表达式定义来解决。 **规则排除** 非数字变量支持简单的排除功能,主要是起到避免解析混淆的作用,例如: ~~~ 'news/:cate^add-edit-delete'=>'News/category' ~~~ 因为规则定义的局限性,恰巧我们的路由规则里面的news和实际的news模块是相同的命名,而:cate并不能自动区分当前URL里面的动态参数是实际的操作名还是路由变量,所以为了避免混淆,我们需要对路由变量cate进行一些排除以帮助我们进行更精确的路由匹配,格式^add|edit|delete表示,匹配除了add edit 和delete之外的所有字符串,我们建议更好的方式还是改进你的路由规则,避免路由规则和模块同名的情况存在,例如 ~~~ 'new/:cate'=>'News/category' ~~~ 就可以更简单的定义路由规则了。 **完全匹配** 规则匹配检测的时候只是对URL从头开始匹配,只要URL地址包含了定义的路由规则就会匹配成功,如果希望完全匹配,可以使用$符号,例如: ~~~ 'new/:cate$'=> 'News/category', ~~~ `http://serverName/index.php/new/info` 会匹配成功 而 `http://serverName/index.php/new/info/2 ` 则不会匹配成功 如果是采用 ~~~ 'new/:cate'=> 'News/category', ~~~ 方式定义的话,则两种方式的URL访问都可以匹配成功。 ## 路由地址和参数 路由地址和参数表示前面的路由表达式最终需要路由到的地址并且允许隐式传入URL里面没有的一些参数,这里允许使用字符串或者数组方式定义,支持下面6种方式定义: |定义方式 |定义格式| |-----------|------------| |方式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;}| 如果路由地址以“/”或者“http”开头则会认为是一个重定向地址或者外部地址,例如: ~~~ 'blog/:id'=>'/blog/read/id/:1' ~~~ 和 ~~~ 'blog/:id'=>'blog/read/' ~~~ 虽然都是路由到同一个地址,但是前者采用的是301重定向的方式路由跳转,这种方式的好处是URL可以比较随意(包括可以在URL里面传入更多的非标准格式的参数),而后者只是支持模块和操作地址。 举个例子,如果我们希望`avatar/123`重定向到 `/member/avatar/id/123_small`的话,只能使用: ~~~ 'avatar/:id'=>'/member/avatar/id/:1_small' ~~~ > 路由地址采用重定向地址的话,如果要引用动态变量,也是采用:1、:2 的方式。 采用重定向到外部地址通常对网站改版后的URL迁移过程非常有用,例如: ~~~ 'blog/:id'=>'http://blog.thinkphp.cn/read/:1' ~~~ 表示当前网站(可能是http://thinkphp.cn)的 `blog/123`地址会直接重定向到 `http://blog.thinkphp.cn/read/123`。 在路由跳转的时候支持额外传入参数对(额外参数指的是不在URL里面的参数,隐式传入需要的操作中,有时候能够起到一定的安全防护作用,后面我们会提到),支持“`参数1=值1&参数2=值2`”或者`array('参数1'=>'值1','参数2'=>'值2'...)`这样的写法,可以参考不同的定义方式选择。例如: ~~~ 'blog/:id'=>'blog/read/?status=1&app_id=5', 'blog/:id'=>array('blog/read/?status=1&app_id=5'), 'blog/:id'=>array('blog/read/','status=1&app_id=5'), 'blog/:id'=>array('blog/read/',array('status'=>1,'app_id'=>5)), ~~~ 上面的路由规则定义中额外参数的传值方式都是等效的。`status`和`app_id`参数都是URL里面不存在的,属于隐式传值,当然并不一定需要用到,只是在需要的时候可以使用。 ## 闭包支持 我们可以使用闭包的方式定义一些特殊需求的路由,而不需要执行控制器的操作方法了,例如: ~~~ 'URL_ROUTE_RULES'=>array( 'test' => function(){ echo 'just test'; }, 'hello/:name' => function($name){ echo 'Hello,'.$name; } ) ~~~ **参数传递** 闭包定义的参数传递在规则路由和正则路由的两种情况下有所区别。 规则路由的参数传递比较简单: ~~~ 'hello/:name' => function($name){ echo 'Hello,'.$name; } ~~~ 规则路由中定义的动态变量的名称 就是闭包函数中的参数名称,不分次序。 因此,如果我们访问的URL地址是: `http://serverName/Home/hello/thinkphp` 则浏览器输出的结果是: `Hello,thinkphp` 如果多个参数可以使用: ~~~ 'blog/:year/:month' => function($year,$month){ echo 'year='.$year.'&month='.$month; } ~~~ 如果是正则路由的话,闭包函数中的参数就以正则中出现的参数次序来传递,例如: ~~~ '/^new\/(\d{4})\/(\d{2})$/' => function($year,$month){ echo 'year='.$year.'&month='.$month; } ~~~ 如果我们访问: `http://serverName/Home/new/2013/03` 浏览器输出结果是: `year=2013&month=03` ## 静态路由 静态路由其实属于规则路由的静态简化版(又称为URL映射),路由定义中不包含动态参数,静态路由不需要遍历路由规则而是直接定位,因此效率较高,但作用也有限。 如果我们定义了下面的静态路由 ~~~ 'URL_ROUTER_ON' => true, 'URL_MAP_RULES'=>array( 'new/top' => 'news/index?type=top' ) ~~~ > 注意:为了不影响动态路由的遍历效率,静态路由采用URL_MAP_RULES定义和动态路由区分开来 定义之后,如果我们访问: `http://serverName/Home/new/top` 其实是访问: `http://serverName/Home/news/index/type/top` 静态路由是完整匹配,所以如果访问: `http://serverName/Home/new/top/var/test` 尽管前面也有new/top,但并不会被匹配到news/index/type/top。 静态路由定义不受URL后缀影响,例如: `http://serverName/Home/new/top.html` 也可以正常访问。 > 静态路由的路由地址 只支持字符串,格式:[控制器/操作?]参数1=值1&参数2=值2 ## 总结 通过本篇的学习,我们大概掌握了如何定义路由,后面我们将会学习如何定义视图和模板赋值。