HTTP 路由

基本路由

你可以在 app/Http/routes.php 文件中定义应用程序的大多数路由,该文件将会被 App\Providers\RouteServiceProvider 类加载。最基本的 Laravel 路由仅接受 URI 和一个闭包

  1. Route::get('foo', function () {
  2. return 'Hello World';
  3. });

默认路由文件

默认情况下,routes.php 文件包含单个路由和一个路由群组,该路由群组包含的所有路由都使用了中间件组 web,而这个中间件组为路由提供了 Session 状态和 CSRF 保护功能。大部分情况下,我们会将所有路由定义在这个文件中。

可供使用的路由方法

我们可以注册路由来响应任何方法的 HTTP 请求:

  1. Route::get($uri, $callback);
  2. Route::post($uri, $callback);
  3. Route::put($uri, $callback);
  4. Route::patch($uri, $callback);
  5. Route::delete($uri, $callback);
  6. Route::options($uri, $callback);

有时候你可能需要注册一个可响应多个 HTTP 动作的路由。这时可通过 Route facadematch 方法来实现:

  1. Route::match(['get', 'post'], '/', function () {
  2. //
  3. });
  4. Route::any('foo', function () {
  5. //
  6. });

路由参数

必选参数

有时候你可能需要从 URI 中获取一些参数。例如,从 URL 获取用户的 ID。这时可通过自定义路由参数来获取:

  1. Route::get('user/{id}', function ($id) {
  2. return 'User '.$id;
  3. });

你可以依照路由需要,定义任意数量的路由参数:

  1. Route::get('posts/{post}/comments/{comment}', function ($postId, $commentId) {
  2. //
  3. });

路由的参数都会被放在「大括号」内。当运行路由时,参数会通过路由闭包来传递。

注意: 路由参数不能包含 - 字符。请用下划线 (_) 替换。

可选的路由参数

有时候你需要指定可选的路由参数,可以在参数名称后面加上 ? 来实现:

  1. Route::get('user/{name?}', function ($name = null) {
  2. return $name;
  3. });
  4. Route::get('user/{name?}', function ($name = 'John') {
  5. return $name;
  6. });

正则表达式限制参数

你可以使用 where 方法来限制路由参数格式。where 方法接受参数的名称和定义参数应该如何被限制的正则表达式:

  1. Route::get('user/{name}', function ($name) {
  2. //
  3. })
  4. ->where('name', '[A-Za-z]+');
  5. Route::get('user/{id}', function ($id) {
  6. //
  7. })
  8. ->where('id', '[0-9]+');
  9. Route::get('user/{id}/{name}', function ($id, $name) {
  10. //
  11. })
  12. ->where(['id' => '[0-9]+', 'name' => '[a-z]+']);

全局限制

如果你希望路由参数可以总是遵循正则表达式,则可以使用 pattern 方法。你应该在 RouteServiceProviderboot 方法里定义这些模式:

  1. /**
  2. * 定义你的路由模型绑定,模式过滤器等。
  3. *
  4. * @param \Illuminate\Routing\Router $router
  5. * @return void
  6. */
  7. public function boot(Router $router)
  8. {
  9. $router->pattern('id', '[0-9]+');
  10. parent::boot($router);
  11. }

模式一旦被定义,便会自动应用到所有使用该参数名称的路由上:

  1. Route::get('user/{id}', function ($id) {
  2. // Only called if {id} is numeric.
  3. });

命名路由

命名路由让你可以更方便的为特定路由生成 URL 或进行重定向。你可以使用 as 数组键指定名称到路由上:

  1. Route::get('user/profile', ['as' => 'profile', function () {
  2. //
  3. }]);

还可以指定路由名称到控制器动作:

  1. Route::get('user/profile', [
  2. 'as' => 'profile', 'uses' => 'UserController@showProfile'
  3. ]);

除了可以在路由的数组定义中指定路由名称外,你也可以在路由定义后方链式调用 name 方法:

  1. Route::get('user/profile', 'UserController@showProfile')->name('profile');

路由群组和命名路由

如果你使用了 路由群组,那么你可以在路由群组的属性数组中指定一个 as 关键字,这将允许你为路由群组中的所有路由设置相同的前缀名称:

  1. Route::group(['as' => 'admin::'], function () {
  2. Route::get('dashboard', ['as' => 'dashboard', function () {
  3. // 路由名称为「admin::dashboard」
  4. }]);
  5. });

对命名路由生成 URLs

一旦你在指定的路由中分配了名称,则可通过 route 函数来使用路由名称生成 URLs 或重定向:

  1. $url = route('profile');
  2. $redirect = redirect()->route('profile');

如果路由定义了参数,那么你可以把参数作为第二个参数传递给 route 方法。指定的参数将自动加入到 URL 中:

  1. Route::get('user/{id}/profile', ['as' => 'profile', function ($id) {
  2. //
  3. }]);
  4. $url = route('profile', ['id' => 1]);

路由群组

路由群组允许你共用路由属性,例如:中间件、命名空间,你可以利用路由群组统一为多个路由设置共同属性,而不需在每个路由上都设置一次。共用属性被指定为数组格式,当作 Route::group 方法的第一个参数。

为了了解更多路由群组的相关内容,我们可通过几个常用样例来熟悉这些特性。

中间件

指定中间件到所有群组内的路由中,则可以在群组属性数组里使用 middleware 参数。中间件将会依照列表内指定的顺序运行:

  1. Route::group(['middleware' => 'auth'], function () {
  2. Route::get('/', function () {
  3. // 使用 Auth 中间件
  4. });
  5. Route::get('user/profile', function () {
  6. // 使用 Auth 中间件
  7. });
  8. });

命名空间

另一个常见的例子是,指定相同的 PHP 命名空间给控制器群组。可以使用 namespace 参数来指定群组内所有控制器的命名空间:

  1. Route::group(['namespace' => 'Admin'], function()
  2. {
  3. // 控制器在「App\Http\Controllers\Admin」命名空间
  4. Route::group(['namespace' => 'User'], function()
  5. {
  6. // 控制器在「App\Http\Controllers\Admin\User」命名空间
  7. });
  8. });

请记住,默认 RouteServiceProvider 会在命名空间群组内导入你的 routes.php 文件,让你不用指定完整的 App\Http\Controllers 命名空间前缀就能注册控制器路由。所以,我们只需要指定在基底 App\Http\Controllers 根命名空间之后的部分命名空间。

子域名路由

路由群组也可以被用来做处理通配符的子域名。子域名可以像路由 URIs 分配路由参数,让你在路由或控制器中获取子域名参数。使用路由群组属性数组上的 domain 指定子域名变量名称:

  1. Route::group(['domain' => '{account}.myapp.com'], function () {
  2. Route::get('user/{id}', function ($account, $id) {
  3. //
  4. });
  5. });

路由前缀

通过路由群组数组属性中的 prefix,在路由群组内为每个路由指定的 URI 加上前缀。例如,你可能想要在路由群组中将所有的路由 URIs 加上前缀 admin

  1. Route::group(['prefix' => 'admin'], function () {
  2. Route::get('users', function () {
  3. // 符合「/admin/users」URL
  4. });
  5. });

你也可以使用 prefix 参数去指定路由群组中共用的参数:

  1. Route::group(['prefix' => 'accounts/{account_id}'], function () {
  2. Route::get('detail', function ($account_id) {
  3. // 符合 accounts/{account_id}/detail URL
  4. });
  5. });

CSRF 保护

介绍

Laravel 提供简单的方法保护你的应用程序不受到 跨网站请求伪造 攻击。跨网站请求伪造是一种恶意的攻击,破坏份子伪造 已通过身份检验的用户身份 来运行未经授权的命令。

Laravel 会自动生成一个 CSRF token 给每个用户的 Session。该 token 用来验证用户是否为实际发出请求的用户。可以使用 csrf_field 辅助函数来生成一个包含 CSRF token 的 _token 隐藏表单字段:

  1. <?php echo csrf_field(); ?>

csrf_field 辅助函数会生成以下的 HTML:

  1. <input type="hidden" name="_token" value="<?php echo csrf_token(); ?>">

当然,也可以在 Blade 模板引擎 中使用:

  1. {{ csrf_field() }}

你不需要手动验证 POST、PUT 或 DELETE 请求的 CSRF token。VerifyCsrfToken HTTP 中间件 将自动验证请求与 session 中的 token 是否相符。

不受 CSRF 保护的 URIs

有时候你可能会希望一组 URIs 不要被 CSRF 保护。例如,你如果使用 Stripe 处理付款,并且利用他们的 webhook 系统,你需要从 Laravel CSRF 保护中排除 webhook 的处理路由。

可以在 VerifyCsrfToken 中间件中增加 $except 属性来排除 URIs:

  1. <?php
  2. namespace App\Http\Middleware;
  3. use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;
  4. class VerifyCsrfToken extends BaseVerifier
  5. {
  6. /**
  7. * URIs 应被 CSRF 验证执行。
  8. *
  9. * @var array
  10. */
  11. protected $except = [
  12. 'stripe/*',
  13. ];
  14. }

X-CSRF-TOKEN

除了检查 CSRF token 是否被当作 POST 参数之外,在 Laravel VerifyCsrfToken 中间件也会检查请求标头中的 X-CSRF-TOKEN。例如,你可以将其保存在 meta 标签中:

  1. <meta name="csrf-token" content="{{ csrf_token() }}">

一旦你创建了 meta 标签,你就可以使用 jQuery 之类的函数库将 token 加入到所有的请求标头。基于 AJAX 的应用,提供了简单、方便的 CSRF 保护:

  1. $.ajaxSetup({
  2. headers: {
  3. 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
  4. }
  5. });

X-XSRF-TOKEN

Laravel 也会在 XSRF-TOKEN cookie 中保存 CSRF token。你也可以使用 cookie 的值来设置 X-XSRF-TOKEN 请求标头。一些 JavaScript 框架会自动帮你处理,例如:Angular。你不大可能会需要手动去设置这个值。

路由模型绑定

Laravel 路由模型绑定提供了一个方便的方式来注入类实例到你的路由中。例如,除了注入一个用户的 ID,你也可以注入与指定 ID 相符的完整 User 类实例。

隐式绑定

Laravel 会自动解析定义在路由或控制器动作(变量名匹配路由片段)中的 Eloquent 模型类型声明,例如:

  1. Route::get('api/users/{user}', function (App\User $user) {
  2. return $user->email;
  3. });

在这个例子中,由于类型声明了 Eloquent 模型 App\User,对应的变量名 $user 会匹配路由片段中的 {user},这样,Laravel 会自动注入与请求 URI 中传入的 ID 对应的用户模型实例。

如果数据库中找不到对应的模型实例,会会自动生成 HTTP 404 响应。

自定义键名

如果你想要隐式模型绑定使用数据表的其它字段(默认使用 id),可以重写 Eloquent 模型类的 getRouteKeyName 方法:

  1. /**
  2. * 从路由中获取到键
  3. *
  4. * @return string
  5. */
  6. public function getRouteKeyName()
  7. {
  8. return 'slug';
  9. }

显式绑定

要注册显式绑定,需要使用路由的 model 方法来为给定参数指定绑定类。应该在 RouteServiceProvider::boot 方法中定义模型绑定:

绑定参数至模型

  1. public function boot(Router $router)
  2. {
  3. parent::boot($router);
  4. $router->model('user', 'App\User');
  5. }

接着,定义包含 {user} 参数的路由:

  1. $router->get('profile/{user}', function(App\User $user) {
  2. //
  3. });

因为我们已经绑定 {user} 参数至 App\User 模型,所以 User 实例会被注入至该路由。所以,举个例子,一个至 profile/1 的请求会注入 ID 为 1 的 User 实例。

注意:如果符合的模型不存在于数据库中,就会自动抛出一个 404 异常。

自定义解析逻辑

如果你想要使用自定义的解析逻辑,需要使用 Route::bind 方法,传递到 bind 方法的闭包会获取到 URI 请求参数中的值,并且返回你想要在该路由中注入的类实例:

  1. $router->bind('user', function ($value) {
  2. return App\User::where('name', $value)->first();
  3. });

自定义未找到的行为

如果你想要指定自己的模型未找到行为,将封装该行为的闭包作为第三个参数传递给 model 方法:

  1. $router->model('user', 'App\User', function () {
  2. throw new NotFoundHttpException;
  3. });

请求方法伪造

HTML 表单没有支持 PUTPATCHDELETE 动作。所以在从 HTML 表单中调用被定义的 PUTPATCHDELETE 路由时,你将需要在表单中增加隐藏的 _method 字段。跟随 _method 字段送出的值将被作为 HTTP 的请求方法使用:

  1. <form action="/foo/bar" method="POST">
  2. <input type="hidden" name="_method" value="PUT">
  3. <input type="hidden" name="_token" value="{{ csrf_token() }}">
  4. </form>

你也可以使用 methid_field 辅助函数来生成隐藏的输入字段 _method

  1. <?php echo method_field('PUT'); ?>

当然,也可以使用 Blade 模板引擎

  1. {{ method_field('PUT') }}

获取当前路由信息

Route::current() 方法会返回当前请求的 Illuminate\Routing\Route 实例,你可使用此实例进行各种操作:

  1. $route = Route::current();
  2. $name = $route->getName();
  3. $actionName = $route->getActionName();

你可以利用 Route facade 的 currentRouteNamecurrentRouteAction 方法,来获取当前路由的路由命名和控制器动作。

  1. $name = Route::currentRouteName();
  2. $action = Route::currentRouteAction();

更多关于 route 的方法,请查看 API 文档 Route facadeRoute 实例


{note} 欢迎任何形式的转载,但请务必注明出处,尊重他人劳动共创开源社区。

转载请注明:本文档由 Laravel China 社区 [laravel-china.org] 组织翻译。

文档永久地址: http://d.laravel-china.org