🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] #### 路由地址 前面我们已经掌握了路由规则和路由变量的使用,路由规则的作用主要是用于匹配检查,在匹配到正确的路由规则后,并且通过了所有的条件检查,路由地址就粉墨登场了,不要以为路由地址很简单,其实不然哦,包含了下面几种类型的定义方式。 路由地址的作用就是告诉系统该路由规则应该解析到哪去,是到某个控制器方法还是其它的类方法(或者闭包函数)。而模块/控制器/操作这种路由地址其本质上也是对应到一个类的动态方法,另外也可以路由到一个类的静态方法,函数的方式是通过闭包来完成(目前路由不支持路由到一个函数名)。 要知道路由地址最终是怎么运作的,其实看think\App类的run方法里面的这段代码就明白了: ~~~ switch ($dispatch['type']) { case 'redirect': // 执行重定向跳转 $data = Response::create($dispatch['url'], 'redirect')->code($dispatch['status']); break; case 'module': // 模块/控制器/操作 $data = self::module($dispatch['module'], $config, isset($dispatch['convert']) ? $dispatch['convert'] : null); break; case 'controller': // 执行控制器操作 $data = Loader::action($dispatch['controller']); break; case 'method': // 执行回调方法 $data = self::invokeMethod($dispatch['method']); break; case 'function': // 执行闭包 $data = self::invokeFunction($dispatch['function']); break; case 'response': $data = $dispatch['response']; break; default: throw new \InvalidArgumentException('dispatch type not support'); } ~~~ 这段代码清晰的指出了不同类型的调度是如何执行的,路由类think\Route最终解析后返回的就是一个类似于 ~~~ ['type' => 'module', 'module' => ['index', 'Index', 'hello']] ~~~ 的格式数组,然后App类根据调度类型type来判断实际需要执行什么,从代码可以看出,路由类里面实际上有可能返回6种不同的调度类型(除去非法的调度异常之外),而Response对象其实不支持在路由地址中定义,所以实际上就是5种路由地址的定义类型,下面的主要内容就是来讲解如何定义这5种不同类型的路由地址。 不同的路由类型其实代表了不同的应用架构模式,大多数情况下的应用普遍采用的是基于模块/控制器/操作的架构开发模式,也是ThinkPHP5.0使用的默认模式,之所以说是默认模式,是因为关闭路由后的`URL解析`也是按照`模块/控制器/操作`的方式进行的。 #### 路由到`模块/控制器/操作` 路由地址的第一种定义方式,也是最常用的方式就是路由到 模块/控制器/操作 这种,也就是一种模块化的设计架构,每个URL请求都会最终执行应用的模块(其实就是一个目录)下面的控制器(一个控制器类)的操作(控制器类的方法),例如: ~~~ Route::get('hello/:name','index/Index/hello'); ~~~ 表示路由到index模块下的Index控制器的hello操作,实际上在匹配到路由规则后,会解析为 ~~~ ['type' => 'module', 'module' => ['index', 'Index', 'hello']] ~~~ 返回给think\App类,然后调用App类的module方法执行相关操作。 同时会进行模块的初始化操作(包括配置读取、公共文件载入、行为定义载入、语言包载入等等)。 这种路由类型的路由地址的格式为: ~~~ [模块/控制器/]操作?参数1=值1&参数2=值2... ~~~ 假设一个Blog控制器类定义如下: ~~~ <?php namespace app\index\controller; class Blog { public function read($id){ return 'read:'.$id; } } ~~~ 定义路由如下: ~~~ // 路由到默认模块或者绑定模块 Route::get('blog/:id','blog/read'); // 路由到index模块 Route::get('blog/:id','index/blog/read'); ~~~ 路由地址的解析规则从后往前解析,先解析操作,然后解析控制器,最后解析模块。 虽然路由地址里面的模块不是必须的,但建议是写完整的路由地址,避免受其它配置的影响,另外一个也方便路由地址的生成。 路由地址中支持多级控制器(关于多级控制器的概念请参考官方手册),使用下面的方式进行设置: ~~~ Route::get('blog/:id','index/group.Blog/read'); ~~~ 表示路由到下面的控制器类 ~~~ app\index\controller\group\Blog ~~~ Blog控制器类定义如下: ~~~ <?php namespace app\index\controller\group; class Blog { public function read($id){ return 'read:'.$id; } } ~~~ 需要注意的是,如果控制器使用了驼峰命名的话,在路由地址里面也需要使用驼峰方式定义,例如: ~~~ <?php namespace app\index\controller; class GroupUser { public function read($id){ return 'read:'.$id; } } ~~~ 用例 ~~~ // 错误的定义 Route::get('user/:id','index/groupuser/read'); // 正确的定义 Route::get('user/:id','index/GroupUser/read'); ~~~ 建议在路由地址中定义的控制器名称和控制器类名保持大小写一致,方便查看也方便URL地址的统一生成。 还可以支持路由到动态的模块、控制器或者操作,例如: ~~~ // action变量的值作为操作方法传入 Route::get(':action/blog/:id','index/Blog/:action'); // 路由到动态的控制器和操作 Route::rule(':controller/:action','index/:controller/:action'); ~~~ #### 额外参数 路由地址里面支持额外传入参数(额外参数指的是不在URL里面的参数,隐式传入需要的操作中,有时候能够起到一定的安全防护作用,后面我们会提到)。例如: ~~~ Route::get('blog/:id','index/Blog/read?status=1&app_id=5'); ~~~ 上面的路由规则定义中额外参数status和app_id参数都是URL里面不存在的,属于隐式传值,当然并不一定需要用到,只是在需要的时候可以使用。 > 【5.1须知】 > 可以使用下面的方式 ~~~ Route::get('blog/:id','index/Blog/read) ->append(['status'=>1,'app_id'=>5]); ~~~ #### 路由到操作方法 第二种路由类型是直接路由到控制器类的方法执行,这种方式看起来似乎和第一种是一样的,区别在于直接执行某个控制器类的方法,而不需要去解析 模块/控制器/操作这些,同时也不会去初始化模块(也就意味着不会去加载模块配置和公共文件)。 路由地址的格式为: @[模块/控制器/]操作 例如,定义如下路由后: ~~~ Route::get('blog/:id','@index/Blog/read'); ~~~ 访问 ~~~ http://tp5.com/blog/5 ~~~ 系统会直接执行 ~~~ Loader::action('index/blog/read'); ~~~ 相当于直接调用`\app\index\controller\blog`类的`read方法`。 通常这种方式下面,由于没有定义当前模块名、当前控制器名和当前方法名 ,从而导致视图的默认模板规则失效,所以这种情况下面,如果使用了视图模板渲染,则必须传入明确的参数。 #### 路由到类的方法 第二种路由方式直接执行了控制器类(确切的说是访问控制器)的方法,如果希望直接执行其它类的方法,就需要使用下面的方式: 路由地址的格式为(动态方法): > \带命名空间的完整类名@方法名 或者(静态方法) > \带命名空间完整类名::方法名 这种方式更进一步,可以支持执行任何类的方法,而不仅仅是执行控制器的操作方法,例如: ~~~ Route::get('blog/:id','\app\index\service\Blog@read'); ~~~ 执行的是 `\app\index\service\Blog类`的`read方法`。 也支持执行某个静态方法,例如: ~~~ Route::get('blog/:id','\app\index\service\Blog::read'); ~~~ #### 路由到重定向地址 重定向的外部地址必须以“`/`”或者`http`开头的地址。 如果路由地址以“`/`”开头或者带有“`://`”的地址会认为是一个重定向地址或者外部地址,例如: ~~~ Route::get('blog/:id','http://blog/read/id/:id'); Route::get('blog/:id','https://blog/read/id/:id'); Route::get('blog/:id','/blog/read/id/:id'); ~~~ 和 ~~~ Route::get('blog/:id','blog/read'); ~~~ 虽然都是路由到同一个地址,但是前者采用的是301重定向的方式路由跳转,这种方式的好处是URL可以比较随意(包括可以在URL里面传入更多的非标准格式的参数),而后者只是支持模块和操作地址。举个例子,如果我们希望`avatar/123`重定向到 `/member/avatar/id/123_small`的话,只能使用: ~~~ Route::get('avatar/:id','/member/avatar/id/:id_small'); ~~~ 路由地址采用重定向地址的话,如果要引用动态变量,直接使用动态变量即可。 采用重定向到外部地址通常对网站改版后的URL迁移过程非常有用,例如: ~~~ Route::get('blog/:id','http://blog.thinkphp.cn/read/:id'); ~~~ 表示当前网站(可能是`http://thinkphp.cn `)的 blog/123地址会直接重定向到`http://blog.thinkphp.cn/read/123` > 【5.1须知】 > 可以使用下面的方式 ~~~ Route::redirect('blog/:id','http://blog.thinkphp.cn/read/:id'); ~~~ #### 使用闭包定义 我们可以使用闭包的方式定义一些特殊需求的路由,而不需要执行控制器的操作方法了,例如: ~~~ Route::get('hello',function(){ return 'hello,world!'; }); ~~~ 闭包定义的时候支持参数传递,例如: ~~~ Route::get('hello/:name',function($name){ return 'Hello,'.$name; }); ~~~ 路由规则中定义的变量名称就是闭包函数中的参数名称,不分次序。 因此,如果我们访问的URL地址是: ~~~ http://tp5.com/hello/thinkphp ~~~ 则浏览器输出的结果是: ~~~ Hello,thinkphp ~~~ 小结 相信你已经掌握了路由地址的定义规范了,对路由地址的规范总结如下: |路由类型 |定义格式| |--|--| |路由到模块/控制器/操作| '[模块/控制器/操作]?额外参数1=值1&额外参数2=值2...'| |路由到重定向地址| '外部地址'(默认301重定向) 或者 ['外部地址','重定向代码']| |路由到控制器的方法 |'@[模块/控制器/]操作'| |路由到类的方法 |'\完整的命名空间类::静态方法' 或者 '\完整的命名空间类@动态方法'| |路由到闭包函数| 闭包函数定义(支持参数传入)| 下一篇我会来讲解路由分组的用法。