Skip to content

HTTP 路由

基本路由

您将在 app/Http/routes.php 文件中定义应用程序的大多数路由,该文件由 App\Providers\RouteServiceProvider 类加载。最基本的 Laravel 路由只需接受一个 URI 和一个 Closure

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

Route::post('foo/bar', function () {
    return 'Hello World';
});

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

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

为多个动词注册路由

有时您可能需要注册一个响应多个 HTTP 动词的路由。您可以使用 Route facade 上的 match 方法来实现:

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

或者,您甚至可以使用 any 方法注册一个响应所有 HTTP 动词的路由:

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

生成路由的 URL

您可以使用 url 助手生成应用程序路由的 URL:

php
$url = url('foo');

路由参数

必需参数

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

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

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

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

路由参数始终用 "大括号" 括起来。参数将在路由执行时传递到路由的 Closure 中。

lightbulb

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

可选参数

有时您可能需要指定一个路由参数,但使该路由参数的存在可选。您可以通过在参数名称后加上 ? 来实现:

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 = route('profile');

$redirect = 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 ($account_id)    {
        // 匹配 accounts/{account_id}/detail URL
    });
});

CSRF 保护

介绍

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

Laravel 自动为应用程序管理的每个活动用户会话生成一个 CSRF "令牌"。此令牌用于验证经过身份验证的用户确实是向应用程序发出请求的用户。要生成包含 CSRF 令牌的隐藏输入字段 _token,您可以使用 csrf_field 助手函数:

php
<?php echo csrf_field(); ?>

csrf_field 助手函数生成以下 HTML:

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

当然,使用 Blade 模板引擎

php
{{ csrf_field() }}

您无需在 POST、PUT 或 DELETE 请求上手动验证 CSRF 令牌。VerifyCsrfToken HTTP 中间件 将验证请求输入中的令牌是否与会话中存储的令牌匹配。

从 CSRF 保护中排除 URI

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

您可以通过将 URI 添加到 VerifyCsrfToken 中间件的 $except 属性中来排除它们:

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。

首先,使用路由器的 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 实例。

lightbulb

如果在数据库中找不到匹配的模型实例,将自动抛出 404 异常。

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

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

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

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

表单方法伪造

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') }}

抛出 404 错误

有两种方法可以从路由手动触发 404 错误。首先,您可以使用 abort 助手。abort 助手只需抛出一个带有指定状态码的 Symfony\Component\HttpFoundation\Exception\HttpException

php
abort(404);

其次,您可以手动抛出 Symfony\Component\HttpKernel\Exception\NotFoundHttpException 的实例。

有关处理 404 异常和为这些错误使用自定义响应的更多信息,请参阅文档的 错误 部分。