Skip to content

HTTP 路由

基本路由

所有的 Laravel 路由都定义在 app/Http/routes.php 文件中,该文件由框架自动加载。最基本的 Laravel 路由只需接受一个 URI 和一个 Closure,提供了一种非常简单且富有表现力的方法来定义路由:

php
Route::get('foo', function () {
    return 'Hello World';
});

默认路由文件

默认的 routes.php 文件由 RouteServiceProvider 加载,并自动包含在 web 中间件组中,该组提供了会话状态和 CSRF 保护。大多数应用程序的路由将在此文件中定义。

可用的路由器方法

路由器允许您注册响应任何 HTTP 动词的路由:

php
Route::get($uri, $callback);
Route::post($uri, $callback);
Route::put($uri, $callback);
Route::patch($uri, $callback);
Route::delete($uri, $callback);
Route::options($uri, $callback);

有时您可能需要注册响应多个 HTTP 动词的路由。您可以使用 match 方法来实现这一点。或者,您甚至可以使用 any 方法注册响应所有 HTTP 动词的路由:

php
Route::match(['get', 'post'], '/', function () {
    //
});

Route::any('foo', function () {
    //
});

路由参数

必需参数

当然,有时您需要在路由中捕获 URI 的片段。例如,您可能需要从 URL 中捕获用户的 ID。您可以通过定义路由参数来实现:

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

您可以根据路由的需要定义任意数量的路由参数:

php
Route::get('posts/{post}/comments/{comment}', function ($postId, $commentId) {
    //
});

路由参数总是用“大括号”括起来的。当路由执行时,参数将传递给路由的 Closure

NOTE

路由参数不能包含 - 字符。请使用下划线 (_) 代替。

可选参数

有时您可能需要指定一个路由参数,但使该路由参数的存在是可选的。您可以通过在参数名称后加上 ? 来实现。确保为路由的对应变量提供一个默认值:

php
Route::get('user/{name?}', function ($name = null) {
    return $name;
});

Route::get('user/{name?}', function ($name = 'John') {
    return $name;
});

正则表达式约束

您可以使用路由实例上的 where 方法来约束路由参数的格式。where 方法接受参数的名称和一个正则表达式,用于定义参数应如何约束:

php
Route::get('user/{name}', function ($name) {
    //
})
->where('name', '[A-Za-z]+');

Route::get('user/{id}', function ($id) {
    //
})
->where('id', '[0-9]+');

Route::get('user/{id}/{name}', function ($id, $name) {
    //
})
->where(['id' => '[0-9]+', 'name' => '[a-z]+']);

全局约束

如果您希望路由参数始终被给定的正则表达式约束,您可以使用 pattern 方法。您应该在 RouteServiceProviderboot 方法中定义这些模式:

php
/**
 * 定义您的路由模型绑定、模式过滤器等。
 *
 * @param  \Illuminate\Routing\Router  $router
 * @return void
 */
public function boot(Router $router)
{
    $router->pattern('id', '[0-9]+');

    parent::boot($router);
}

一旦定义了模式,它将自动应用于所有使用该参数名称的路由:

php
Route::get('user/{id}', function ($id) {
    // 仅在 {id} 为数字时调用。
});

命名路由

命名路由允许为特定路由方便地生成 URL 或重定向。您可以在定义路由时使用 as 数组键来指定路由的名称:

php
Route::get('user/profile', ['as' => 'profile', function () {
    //
}]);

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

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

或者,您可以在路由定义的末尾链接 name 方法,而不是在路由数组定义中指定路由名称:

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

路由组和命名路由

如果您正在使用路由组,您可以在路由组属性数组中指定一个 as 关键字,允许您为组内的所有路由设置一个通用的路由名称前缀:

php
Route::group(['as' => 'admin::'], function () {
    Route::get('dashboard', ['as' => 'dashboard', function () {
        // 路由命名为 "admin::dashboard"
    }]);
});

生成命名路由的 URL

一旦您为给定路由分配了名称,您可以在通过全局 route 函数生成 URL 或重定向时使用路由的名称:

php
// 生成 URL...
$url = route('profile');

// 生成重定向...
return redirect()->route('profile');

如果命名路由定义了参数,您可以将参数作为 route 函数的第二个参数传递。给定的参数将自动插入到 URL 中的正确位置:

php
Route::get('user/{id}/profile', ['as' => 'profile', function ($id) {
    //
}]);

$url = route('profile', ['id' => 1]);

路由组

路由组允许您在大量路由之间共享路由属性,如中间件或命名空间,而无需在每个单独的路由上定义这些属性。共享属性以数组格式作为 Route::group 方法的第一个参数指定。

为了了解更多关于路由组的信息,我们将通过几个常见的用例来学习这一特性。

中间件

要为组内的所有路由分配中间件,您可以在组属性数组中使用 middleware 键。中间件将按照您定义的顺序执行:

php
Route::group(['middleware' => 'auth'], function () {
    Route::get('/', function ()    {
        // 使用 Auth 中间件
    });

    Route::get('user/profile', function () {
        // 使用 Auth 中间件
    });
});

命名空间

路由组的另一个常见用例是为一组控制器分配相同的 PHP 命名空间。您可以在组属性数组中使用 namespace 参数来为组内的所有控制器指定命名空间:

php
Route::group(['namespace' => 'Admin'], function()
{
    // 控制器在 "App\Http\Controllers\Admin" 命名空间内

    Route::group(['namespace' => 'User'], function() {
        // 控制器在 "App\Http\Controllers\Admin\User" 命名空间内
    });
});

请记住,默认情况下,RouteServiceProvider 在命名空间组中包含您的 routes.php 文件,允许您注册控制器路由而无需指定完整的 App\Http\Controllers 命名空间前缀。因此,我们只需指定命名空间中基本 App\Http\Controllers 之后的部分。

子域路由

路由组也可以用于路由通配符子域。子域可以像路由 URI 一样分配路由参数,允许您捕获子域的一部分以在路由或控制器中使用。子域可以使用组属性数组中的 domain 键指定:

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

路由前缀

prefix 组属性可用于为组内的每个路由添加给定的 URI 前缀。例如,您可能希望为组内的所有路由 URI 添加 admin 前缀:

php
Route::group(['prefix' => 'admin'], function () {
    Route::get('users', function ()    {
        // 匹配 "/admin/users" URL
    });
});

您还可以使用 prefix 参数为您的分组路由指定通用参数:

php
Route::group(['prefix' => 'accounts/{account_id}'], function () {
    Route::get('detail', function ($accountId)    {
        // 匹配 "/accounts/{account_id}/detail" URL
    });
});

CSRF 保护

介绍

Laravel 使得保护您的应用程序免受跨站请求伪造(CSRF)攻击变得简单。跨站请求伪造是一种恶意利用,未经授权的命令在经过身份验证的用户的名义下执行。

Laravel 为应用程序管理的每个活动用户会话自动生成一个 CSRF "令牌"。此令牌用于验证经过身份验证的用户确实是向应用程序发出请求的用户。

每当您在应用程序中定义 HTML 表单时,您都应该在表单中包含一个隐藏的 CSRF 令牌字段,以便 CSRF 保护中间件能够验证请求。要生成包含 CSRF 令牌的隐藏输入字段 _token,您可以使用 csrf_field 辅助函数:

php
// 原生 PHP
<?php echo csrf_field(); ?>

// Blade 模板语法
{{ csrf_field() }}

csrf_field 辅助函数生成以下 HTML:

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

您不需要在 POST、PUT 或 DELETE 请求上手动验证 CSRF 令牌。VerifyCsrfToken 中间件,包含在 web 中间件组中,将自动验证请求输入中的令牌是否与会话中存储的令牌匹配。

从 CSRF 保护中排除 URI

有时您可能希望从 CSRF 保护中排除一组 URI。例如,如果您使用 Stripe 处理支付并利用其 webhook 系统,您将需要从 Laravel 的 CSRF 保护中排除您的 webhook 处理程序路由。

您可以通过在默认的 routes.php 文件中将其路由定义在 web 中间件组之外,或通过将 URI 添加到 VerifyCsrfToken 中间件的 $except 属性中来排除 URI:

php
<?php

namespace App\Http\Middleware;

use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;

class VerifyCsrfToken extends BaseVerifier
{
    /**
     * 应从 CSRF 验证中排除的 URI。
     *
     * @var array
     */
    protected $except = [
        'stripe/*',
    ];
}

X-CSRF-TOKEN

除了检查作为 POST 参数的 CSRF 令牌外,Laravel 的 VerifyCsrfToken 中间件还将检查 X-CSRF-TOKEN 请求头。您可以,例如,将令牌存储在 "meta" 标签中:

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

一旦您创建了 meta 标签,您可以指示像 jQuery 这样的库将令牌添加到所有请求头中。这为您的 AJAX 应用程序提供了简单、方便的 CSRF 保护:

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

X-XSRF-TOKEN

Laravel 还将 CSRF 令牌存储在 XSRF-TOKEN cookie 中。您可以使用 cookie 值设置 X-XSRF-TOKEN 请求头。一些 JavaScript 框架,如 Angular,会自动为您执行此操作。您不太可能需要手动使用此值。

路由模型绑定

Laravel 路由模型绑定提供了一种方便的方法将模型实例注入到您的路由中。例如,您可以注入与给定 ID 匹配的整个 User 模型实例,而不是注入用户的 ID。

隐式绑定

Laravel 将自动解析在路由或控制器动作中定义的类型提示的 Eloquent 模型,其变量名称与路由段名称匹配。例如:

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

在此示例中,由于在路由上定义的类型提示的 $user 变量与路由 URI 中的 {user} 段匹配,Laravel 将自动注入与请求 URI 中相应值匹配的模型实例。

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

自定义键名称

如果您希望隐式模型绑定在检索模型时使用 id 以外的数据库列,您可以在 Eloquent 模型上重写 getRouteKeyName 方法:

php
/**
 * 获取模型的路由键。
 *
 * @return string
 */
public function getRouteKeyName()
{
    return 'slug';
}

显式绑定

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

将参数绑定到模型

php
public function boot(Router $router)
{
    parent::boot($router);

    $router->model('user', 'App\User');
}

接下来,定义一个包含 {user} 参数的路由:

php
$router->get('profile/{user}', function(App\User $user) {
    //
});

由于我们已将 {user} 参数绑定到 App\User 模型,因此将注入一个 User 实例到路由中。因此,例如,请求 profile/1 将注入 ID 为 1 的 User 实例。

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

自定义解析逻辑

如果您希望使用自己的解析逻辑,您应该使用 Route::bind 方法。传递给 bind 方法的 Closure 将接收 URI 段的值,并应返回要注入到路由中的类的实例:

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

自定义 "未找到" 行为

如果您希望指定自己的 "未找到" 行为,请将 Closure 作为第三个参数传递给 model 方法:

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

表单方法伪造

HTML 表单不支持 PUTPATCHDELETE 操作。因此,当定义从 HTML 表单调用的 PUTPATCHDELETE 路由时,您需要在表单中添加一个隐藏的 _method 字段。与 _method 字段一起发送的值将用作 HTTP 请求方法:

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

要生成隐藏的输入字段 _method,您还可以使用 method_field 辅助函数:

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

当然,使用 Blade 模板引擎

php
{{ method_field('PUT') }}

访问当前路由

Route::current() 方法将返回处理当前 HTTP 请求的路由,允许您检查完整的 Illuminate\Routing\Route 实例:

php
$route = Route::current();

$name = $route->getName();

$actionName = $route->getActionName();

您还可以使用 Route facade 上的 currentRouteNamecurrentRouteAction 辅助方法来访问当前路由的名称或动作:

php
$name = Route::currentRouteName();

$action = Route::currentRouteAction();

请参考 API 文档以查看 Route facade 的底层类Route 实例 的所有可访问方法。