我应该在 Laravel 中嵌套资源路由吗?
Should I Nest Routes to Resources in Laravel?
这可能有点主观,但我觉得必须存在最佳实践(或者甚至是 Laravel 应用程序的良好设计)。谷歌搜索结果很多与这个问题的实际要点无关。
假设我正在构建一个包含团队的 Web 应用程序,其中可能有项目,可能有文档。
我应该设计路由,使文档在它们所属的项目的路径中,然后在它们所属的团队的路径中,还是将事情放在顶层?
据我所知,这个范围有两端值得讨论(其他选项只是中间的灰色):
嵌套
Example, Doc C is found at: /teams/team-a/projects/project-b/documents/doc-c
这很容易做到,在路由文件中,我可以使用 route groups 来帮助保持结构整洁。我认为这对用户来说更合乎逻辑,也许更方便(他们可以自己制定 URL!)。我担心的是我将复杂性导入到每个页面请求中:
- 检查路由的完整性(即 doc-c 确实属于项目 b),并且
- 用户有权访问整个路由中的每个嵌套资产。
我是否应该在每个控制器方法、每个路由参数的开头对每个资源进行 gates/policy 检查?不然这个哪里可以抽象出来?
关于路由完整性,我从未见过对此进行测试的示例 - 所以这不是一种常见的方法吗?如果我们不验证路由完整性,那么页面可能会通过破解路由来显示混合信息(例如,/teams/team-a/projects/project-Z/documents/doc-c,将在 doc-c 的页面上显示有关项目 Z 的信息)。
无嵌套
Example, Doc C is found at : /documents/doc-c
在这个例子中,每个资产都有自己的基本路线,我猜更像是 API。
不需要完整性检查,控制器会预先确定显示的其他资产以生成视图。
但是这个用户体验够好吗?我见过的大多数网站都不这样做。
我认为你应该利用角色概念、路由分组、中间件概念来构建这个应用程序
关于角色相关的东西检查https://github.com/spatie/laravel-permission,一个很好的包
例如:
Route::group(['middleware' => ['role:super-admin']], function () {
//
});
你几乎可以使用上面的包做任何角色,权限相关的事情。
担任项目经理、开发人员等角色。
根据角色对路由进行分组并根据需要分配适当的中间件。
对于URL:/teams/team-a/projects/project-b/documents/doc-c
检查经过身份验证的用户在 team-a 和 project-b 中具有角色(您可以根据需要在任何地方的中间件、控制器或自定义服务提供商中检查)。
也只是检查 https://laravelvoyager.com/
谢谢
这一直是开发人员之间的讨论,甚至 Laravel 的创建者也有这种争论,那么好的做法是什么?
最近我看到 Taylor Otwell 的一条推文说他已经弃用了
来自 Laravel 文档的嵌套路由部分,因为他不喜欢它,而当您打开 Laracasts 时,您会看到 Jeffrey 正在实现这个概念,例如 /series/php-testing/episodes/hello-world
.
正如我所说,这是一个安静的争论,当涉及到这样的选择时,我总是做对我来说好的事情。所以,如果我是你,我既不会嵌套 teams
也不会嵌套 projects
但也许我会嵌套 projects/documents
,我并不总是嵌套。
这是一个有趣的问题 - 正如您所提到的,它可能有点主观,但值得讨论。
您提到了几点,所以我将尝试分别解决。
嵌套与不嵌套
我认为首先要弄清楚的是浏览器路由与 API 路由。如果您提供 API - 无论是在您的应用程序内部还是在 public 外部,我都会出于以下几个原因避免嵌套路由:
- resource/id 格式对于 API 的
来说是非常标准和富有表现力的
- 这使得记录更容易
- 这使得消费者应用程序更容易动态构建 API 请求
但是,您的问题似乎确实集中在浏览器路由上。在我看来,浏览器路由可以而且应该是任何读起来很好的东西——url,尤其是现在,可以被认为是 UI 的一部分。例如,您可以从设置页面转到设置(我希望看到 /settings
),如果我要转到 进入 通知设置部分,我会期待看到 /settings/notifications
。
路由在 UX 方面发挥作用并提供帮助 - 它们几乎是一个面包屑,看起来应该如此。
所以,我肯定会嵌套浏览器路由,绝对不会嵌套 APIs。
路由完整性
我认为你问题的真正核心是路由完整性。我认为无论您是否选择嵌套,您都需要在假设有人篡改 urls 的情况下检查您的权限——就像您假设用户篡改了表单输入一样。
本质上,您的路线(嵌套或非嵌套)充当输入,您需要对其进行验证。路由级中间件是一种方法,但通常过于通用而无法解决任何复杂问题,因此您可能会发现使用控制器中间件更容易解决它 (https://laravel.com/docs/5.7/controllers#controller-middleware)。
所以你可以这样做:
public function __construct()
{
$this->middleware('auth');
$this->middleware('canViewProject')->only('show');
$this->middleware('canEditProject')->except('store');
}
是否使用 Gates、Policies 或只是普通的旧中间件可能取决于项目的复杂性 - 但以上内容无论如何都适用 - 将 url 视为输入并相应地进行验证
这可能与最初的问题有点偏差,但我觉得 Adam Wathan 的 Laracon US 2017 演讲可能是本次讨论的有用资源。 (https://www.youtube.com/watch?v=MF0jFKvS4SI)
我是开发新手,因此在 github 上探索了很多代码库。我总是很难理解嵌套路线。因此,作为最佳实践,我宁愿不嵌套也不愿嵌套。
保持东西 "CRUDY by design" 正如 Adam 所说,您实现的一件事是简化路由名称。如果我要在一个团队中工作,那是我更喜欢的模式。
但是,参考你问题的倒数第二段,我很难理解为什么不需要完整性检查。
去年我一直在研究并完善它,最近偶然发现了一个优雅的解决方案。嵌套可以很好地完成,我坚持使用资源控制器和嵌套路由资源的点语法。
为了强制执行路由验证,我使用了如下内容。有一个很好的答案 here,它建议明确绑定有问题的模型。
所以
Route::resource('posts.comments', 'CommentsController');
你会用
Route::bind('comment', function ($comment, $route) {
return Comment::where('post_id', $route->parameter('post'))->findOrFail($comment);
});
这将在任何地方自动工作。如果需要,您可以测试要找到的上游参数,并且仅在这些情况下执行测试(例如,以满足仅指定注释的路由)。
随后在 Laravel 上完成了很多工作。我对此的默认回应是“是的,使用路由嵌套”。我保留路由 restful,尽可能使用(嵌套的)资源控制器,并为奇怪的用例使用单动作控制器。路由范围现在甚至是自动的,if specified correctly.
这可能有点主观,但我觉得必须存在最佳实践(或者甚至是 Laravel 应用程序的良好设计)。谷歌搜索结果很多与这个问题的实际要点无关。
假设我正在构建一个包含团队的 Web 应用程序,其中可能有项目,可能有文档。
我应该设计路由,使文档在它们所属的项目的路径中,然后在它们所属的团队的路径中,还是将事情放在顶层?
据我所知,这个范围有两端值得讨论(其他选项只是中间的灰色):
嵌套
Example, Doc C is found at: /teams/team-a/projects/project-b/documents/doc-c
这很容易做到,在路由文件中,我可以使用 route groups 来帮助保持结构整洁。我认为这对用户来说更合乎逻辑,也许更方便(他们可以自己制定 URL!)。我担心的是我将复杂性导入到每个页面请求中:
- 检查路由的完整性(即 doc-c 确实属于项目 b),并且
- 用户有权访问整个路由中的每个嵌套资产。
我是否应该在每个控制器方法、每个路由参数的开头对每个资源进行 gates/policy 检查?不然这个哪里可以抽象出来?
关于路由完整性,我从未见过对此进行测试的示例 - 所以这不是一种常见的方法吗?如果我们不验证路由完整性,那么页面可能会通过破解路由来显示混合信息(例如,/teams/team-a/projects/project-Z/documents/doc-c,将在 doc-c 的页面上显示有关项目 Z 的信息)。
无嵌套
Example, Doc C is found at : /documents/doc-c
在这个例子中,每个资产都有自己的基本路线,我猜更像是 API。
不需要完整性检查,控制器会预先确定显示的其他资产以生成视图。
但是这个用户体验够好吗?我见过的大多数网站都不这样做。
我认为你应该利用角色概念、路由分组、中间件概念来构建这个应用程序
关于角色相关的东西检查https://github.com/spatie/laravel-permission,一个很好的包
例如:
Route::group(['middleware' => ['role:super-admin']], function () {
//
});
你几乎可以使用上面的包做任何角色,权限相关的事情。
担任项目经理、开发人员等角色。
根据角色对路由进行分组并根据需要分配适当的中间件。
对于URL:/teams/team-a/projects/project-b/documents/doc-c
检查经过身份验证的用户在 team-a 和 project-b 中具有角色(您可以根据需要在任何地方的中间件、控制器或自定义服务提供商中检查)。
也只是检查 https://laravelvoyager.com/
谢谢
这一直是开发人员之间的讨论,甚至 Laravel 的创建者也有这种争论,那么好的做法是什么?
最近我看到 Taylor Otwell 的一条推文说他已经弃用了
来自 Laravel 文档的嵌套路由部分,因为他不喜欢它,而当您打开 Laracasts 时,您会看到 Jeffrey 正在实现这个概念,例如 /series/php-testing/episodes/hello-world
.
正如我所说,这是一个安静的争论,当涉及到这样的选择时,我总是做对我来说好的事情。所以,如果我是你,我既不会嵌套 teams
也不会嵌套 projects
但也许我会嵌套 projects/documents
,我并不总是嵌套。
这是一个有趣的问题 - 正如您所提到的,它可能有点主观,但值得讨论。
您提到了几点,所以我将尝试分别解决。
嵌套与不嵌套
我认为首先要弄清楚的是浏览器路由与 API 路由。如果您提供 API - 无论是在您的应用程序内部还是在 public 外部,我都会出于以下几个原因避免嵌套路由:
- resource/id 格式对于 API 的 来说是非常标准和富有表现力的
- 这使得记录更容易
- 这使得消费者应用程序更容易动态构建 API 请求
但是,您的问题似乎确实集中在浏览器路由上。在我看来,浏览器路由可以而且应该是任何读起来很好的东西——url,尤其是现在,可以被认为是 UI 的一部分。例如,您可以从设置页面转到设置(我希望看到 /settings
),如果我要转到 进入 通知设置部分,我会期待看到 /settings/notifications
。
路由在 UX 方面发挥作用并提供帮助 - 它们几乎是一个面包屑,看起来应该如此。
所以,我肯定会嵌套浏览器路由,绝对不会嵌套 APIs。
路由完整性
我认为你问题的真正核心是路由完整性。我认为无论您是否选择嵌套,您都需要在假设有人篡改 urls 的情况下检查您的权限——就像您假设用户篡改了表单输入一样。
本质上,您的路线(嵌套或非嵌套)充当输入,您需要对其进行验证。路由级中间件是一种方法,但通常过于通用而无法解决任何复杂问题,因此您可能会发现使用控制器中间件更容易解决它 (https://laravel.com/docs/5.7/controllers#controller-middleware)。
所以你可以这样做:
public function __construct()
{
$this->middleware('auth');
$this->middleware('canViewProject')->only('show');
$this->middleware('canEditProject')->except('store');
}
是否使用 Gates、Policies 或只是普通的旧中间件可能取决于项目的复杂性 - 但以上内容无论如何都适用 - 将 url 视为输入并相应地进行验证
这可能与最初的问题有点偏差,但我觉得 Adam Wathan 的 Laracon US 2017 演讲可能是本次讨论的有用资源。 (https://www.youtube.com/watch?v=MF0jFKvS4SI)
我是开发新手,因此在 github 上探索了很多代码库。我总是很难理解嵌套路线。因此,作为最佳实践,我宁愿不嵌套也不愿嵌套。
保持东西 "CRUDY by design" 正如 Adam 所说,您实现的一件事是简化路由名称。如果我要在一个团队中工作,那是我更喜欢的模式。
但是,参考你问题的倒数第二段,我很难理解为什么不需要完整性检查。
去年我一直在研究并完善它,最近偶然发现了一个优雅的解决方案。嵌套可以很好地完成,我坚持使用资源控制器和嵌套路由资源的点语法。
为了强制执行路由验证,我使用了如下内容。有一个很好的答案 here,它建议明确绑定有问题的模型。
所以
Route::resource('posts.comments', 'CommentsController');
你会用
Route::bind('comment', function ($comment, $route) {
return Comment::where('post_id', $route->parameter('post'))->findOrFail($comment);
});
这将在任何地方自动工作。如果需要,您可以测试要找到的上游参数,并且仅在这些情况下执行测试(例如,以满足仅指定注释的路由)。
随后在 Laravel 上完成了很多工作。我对此的默认回应是“是的,使用路由嵌套”。我保留路由 restful,尽可能使用(嵌套的)资源控制器,并为奇怪的用例使用单动作控制器。路由范围现在甚至是自动的,if specified correctly.