[TOC] 路由功能 [ThinkPHP6.0 开发手册](https://www.kancloud.cn/manual/thinkphp6_0) 做了详细的介绍,并且官方还出了一本教程 [完全指南](https://www.kancloud.cn/thinkphp/route-master) (虽然指南是介绍 ThinkPHP5 的,但其中大部分功能也适用于 6.0 ) 。 所以,本文不想过多重复介绍官方文档里的内容,只是简单介绍一些笔者在本书开发过程中遇到的问题或解决方法。 ## 为什么在教程里我们优先使用标识路由获取 URL 而不是用资源路由? 下面,我们通过一个完整的 Demo 来演示说明一下这个问题。 1. 把前台应用的路由定义修改成以下内容: *route/index/app.php* ```php <?php use think\facade\Route; // 话题管理 Route::get('blog/<id>', 'topic/read')->name('blog.read'); Route::get('blog', 'topic/index')->name('blog.index'); // RESTFul 资源路由 Route::resource('article', 'Topic')->only(['index', 'read']); ``` 我们在上面定义的这些路由目的是想实现用 `index/blog.html` 和 `index/article.html` 这两个 URL 地址访问话题列表页,并且希望话题详情页的 URL 地址格式是 `index/blog/<id>.html` 或 `index/article/<id>.html` 。另外,我们在上面并没有定义除 `index` 和 `read` 方法以外的控制方法访问路由,所以也不希望它们能被访问。 2. 我们用命令行工具查看一下定义的路由列表 ![Routes List](http://tbs.zhanghong.info/images/chapters/11/010_route_list.png) 我们在终端执行 `php think route:list index` (参数 `index` 的意思是指列出 `route/index` 目录内定义的路由列表),可以看到上图结果。从上图中我们可以发现以下两点: - 使用 `resource` 方法声明时我们限定只定义 `index` 和 `read` 两个控制方法,但最终七个控制方法都定义了; - 使用 `resource` 方法声明的路由标识(Name)格式是 `控制器名/操作方法名` ,和定义规则(Rule)无关。 3. 我们用 `url` 函数来生成声明的四个路由访问 URL 我们把 `Index` 控制器改成以下代码: *app/index/controller/Index.php* ```php <?php declare (strict_types = 1); namespace app\index\controller; class Index extends Base { public function index() { $list = [ [ 'path' => 'name', // 生成URL方式-Name 'name' => 'blog.index', // Name String 'url' => (string) url('[blog.index]'), 'match' => 'index/blog.html', // 预期生成URL地址 ], [ 'path' => 'name', // 生成URL方式-Name 'name' => 'User/index', // Name String 'url' => (string) url('[User/index]'), 'match' => 'index/member.html', // 预期生成URL地址 ], [ 'path' => 'path', // 生成URL方式-Rule 'name' => 'member/index', // Rule String 'url' => (string) url('member/index'), 'match' => 'index/member.html', // 预期生成URL地址 ], [ 'mode' => 'name', 'name' => 'blog.read', 'url' => (string) url('[blog.read]', ['id' => 1]), 'match' => 'index/blog/1.html', ], [ 'mode' => 'name', 'name' => 'User/read', 'url' => (string) url('[User/read]', ['id' => 1]), 'match' => 'index/member/1.html', ], [ 'path' => 'path', 'name' => 'member/read', 'url' => (string) url('member/read', ['id' => 1]), 'match' => 'index/member/1.html', ], ]; return json($list); } } ``` 以下是生成的URL地址结果: ![URL Matches](http://tbs.zhanghong.info/images/chapters/11/010_route_list.png) 从上图中,我们可以看到使用路由规则(Rule)或资源路由标识生成的URL地址 **有Bug** ,所以在本项目里我们用自定义标识路由方式生成页面访问URL地址。 >[info] 当使用路由标识生成 URL 时,ThinkPHP 框架允许给标识名前后添加上 `[]`,所以 `redirect('[page.root]')` 和 `redirect('page.root')` 这两种写法效果是等同的( [详见源码](https://github.com/top-think/framework/blob/6.0/src/think/route/Url.php#L356) )。所以,我们为了区分在生成 URL 时使用的是 **规则表达式** 还是 **路由标识** 才给所有路由标识名添加上 `[]` 。 ## 把前台应用中件间注册也改成路由中间件 1. 修改渲染模板路径 要把前台中间件改成路由中间件注册时,首先 **必须** 把控制方法(action)的渲染模板名改成 `控制器名/控制方法名` 格式,如以下示例: *app/index/controller/Topic.php* ```php <?php . . . public function index() { $param = $this->request->only(['order'], 'get'); return $this->fetch('topic/index', [ // 把 'index' 改成 'topic/index' 'paginate' => TopicModel::minePaginate($param), 'active_users' => UserModel::getActiveUsers(), 'links' => LinkModel::selectAll(), // 资源推荐 ]); } . . . ``` 否则,会报以下错误: ![Not Found View Page](http://tbs.zhanghong.info/images/chapters/11/010_no_index_template.png) 2. 修改路由定义文件 在这里,我们还是拿部分路由定义做为 Demo 。 *route/index/app.php* ```php <?php use think\facade\Route; Route::group(function(){ // 话题管理 Route::get('topic/create', 'topic/create')->name('topic.create'); Route::post('topic', 'topic/save')->name('topic.save'); Route::get('topic/<id>/edit', 'topic/edit')->name('topic.edit'); Route::put('topic/<id>', 'topic/update')->name('topic.update'); Route::delete('topic/<id>', 'topic/delete')->name('topic.delete'); })->middleware(['auth']); Route::group(function(){ Route::get('/', 'topic/index')->name('page.root'); // 用户登录与退出 Route::get('login', 'login/create')->name('page.login'); Route::post('login', 'login/save')->name('page.login.save'); Route::post('logout', 'login/delete')->name('page.logout'); // 话题管理 Route::get('topic/<id>', 'topic/read')->name('topic.read'); Route::get('topic', 'topic/index')->name('topic.index'); Route::get('category/<id>', 'category/read')->name('category.read'); }); ``` 注意事项: - 带中间件的路由组 **必须** 定义在前面; - 不带中间件的路由组的 `group` 方法不能去掉,否则带中间件的路由组不会优先匹配。 ## 怎样去掉前台应用所有URL里的应用名前辍(/index) 在本节里,我们把话题列表的 URL 地址由 `http://bbs.test/index/topic.html` 改成 `http://bbs.test/topic.html` 的实现方法,由于修改的代码比较多,我们在这里列出修改内容清单,详细代码请查看 [Git Commit](https://github.com/zhanghong/thinkbbs/commit/d534517ec7da999307a2baf328df1604ccc81c76) 。 - 把 `app/index/config/middleware.php` 注册的中间件移到 `config/middleware.php` ; - 把 `route/index/app.php` 文件里定义的路由全部迁移到 `route/app.php` 文件; - 把 `config/view.php` 文件里的模板目录名(view_dir_name)改成`index/view`; - 把 `app/index/common.php` 文件里定义的助手函数全部移到 `app/common.php` 文件; - 修改 首页(`index/index`)操作方法代码; - 把前台应用其它控制方法(action)的渲染模板名`控制器名/控制方法名`。 经过以上步骤,我们采用非 **增加应用入口** 方式实现去掉前台URL里的应用名( `/index` ),但这样实现有一个缺点是默认首页( `/` )必须跳转到话题列表页(`/topic.html`),笔者在实现过程中也尝试了下面两种方法,但都有问题。 1) 把首页操作方法改成以下代码,这样必须在方法内把视图模板目录重新设置成 view 目录( `"view_dir_name"=>'view'` ),另外这样生成的渲染页面里的 URL 地址都不正确; *app/index/controller/Index.php* ```php <?php declare (strict_types = 1); namespace app\index\controller; use app\common\model\Link as LinkModel; use app\common\model\User as UserModel; use app\common\model\Topic as TopicModel; class Index extends Base { public function index() { // 把视图目录重新设置成view $view_config = [ 'view_dir_name' => 'view', ]; $this->app->config->set($view_config, 'view'); $param = $this->request->only(['order'], 'get'); return $this->fetch('topic/index', [ 'paginate' => TopicModel::minePaginate($param), 'active_users' => UserModel::getActiveUsers(), 'links' => LinkModel::selectAll(), // 资源推荐 ]); } } ``` 2) 把应用的默认控制器修改成话题控制器( `'default_controller'=>'Topic'` ),修改后 `Topic/index` 方法的代码必须修改成和方法一相同的代码,但还是存在渲染页面里的URL地址都不正确; >[info] 以上是笔者发现的几个问题,大家如果对这些问题有好的解决方案,请不吝赐教。另外,大家也可以尝试用[增加应用入口](https://www.kancloud.cn/manual/thinkphp6_0/1297876)想去除URL里的应用名。