HTTP 路由
基本路由
您将在 app/Http/routes.php
文件中定义应用程序的大多数路由,该文件由 App\Providers\RouteServiceProvider
类加载。最基本的 Laravel 路由只需接受一个 URI 和一个 Closure
:
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
方法来实现:
Route::match(['get', 'post'], '/', function () {
return 'Hello World';
});
或者,您甚至可以使用 any
方法注册一个响应所有 HTTP 动词的路由:
Route::any('foo', function () {
return 'Hello World';
});
生成路由的 URL
您可以使用 url
助手生成应用程序路由的 URL:
$url = url('foo');
路由参数
必需参数
当然,有时您需要在路由中捕获 URI 的片段。例如,您可能需要从 URL 中捕获用户的 ID。您可以通过定义路由参数来实现:
Route::get('user/{id}', function ($id) {
return 'User '.$id;
});
您可以根据路由的需要定义任意数量的路由参数:
Route::get('posts/{post}/comments/{comment}', function ($postId, $commentId) {
//
});
路由参数始终用 "大括号" 括起来。参数将在路由执行时传递到路由的 Closure
中。
路由参数不能包含 -
字符。请使用下划线 (_
) 代替。
可选参数
有时您可能需要指定一个路由参数,但使该路由参数的存在可选。您可以通过在参数名称后加上 ?
来实现:
Route::get('user/{name?}', function ($name = null) {
return $name;
});
Route::get('user/{name?}', function ($name = 'John') {
return $name;
});
正则表达式约束
您可以使用路由实例上的 where
方法约束路由参数的格式。where
方法接受参数的名称和一个定义参数应如何约束的正则表达式:
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
方法。您应该在 RouteServiceProvider
的 boot
方法中定义这些模式:
/**
* 定义路由模型绑定、模式过滤器等。
*
* @param \Illuminate\Routing\Router $router
* @return void
*/
public function boot(Router $router)
{
$router->pattern('id', '[0-9]+');
parent::boot($router);
}
一旦定义了模式,它将自动应用于所有使用该参数名称的路由:
Route::get('user/{id}', function ($id) {
// 仅在 {id} 为数字时调用。
});
命名路由
命名路由允许您方便地为特定路由生成 URL 或重定向。您可以在定义路由时使用 as
数组键指定路由的名称:
Route::get('user/profile', ['as' => 'profile', function () {
//
}]);
您还可以为控制器动作指定路由名称:
Route::get('user/profile', [
'as' => 'profile', 'uses' => 'UserController@showProfile'
]);
您可以在路由定义的末尾链接 name
方法,而不是在路由数组定义中指定路由名称:
Route::get('user/profile', 'UserController@showProfile')->name('profile');
路由组和命名路由
如果您正在使用 路由组,您可以在路由组属性数组中指定一个 as
关键字,允许您为组内的所有路由设置一个通用的路由名称前缀:
Route::group(['as' => 'admin::'], function () {
Route::get('dashboard', ['as' => 'dashboard', function () {
// 路由命名为 "admin::dashboard"
}]);
});
生成命名路由的 URL
一旦为给定路由分配了名称,您可以在通过 route
函数生成 URL 或重定向时使用路由的名称:
$url = route('profile');
$redirect = redirect()->route('profile');
如果路由定义了参数,您可以将参数作为 route
方法的第二个参数传递。给定的参数将自动插入到 URL 中:
Route::get('user/{id}/profile', ['as' => 'profile', function ($id) {
//
}]);
$url = route('profile', ['id' => 1]);
路由组
路由组允许您在大量路由之间共享路由属性,如中间件或命名空间,而无需在每个单独的路由上定义这些属性。共享属性以数组格式作为 Route::group
方法的第一个参数指定。
为了了解更多关于路由组的信息,我们将通过几个常见的用例来学习该功能。
中间件
要为组内的所有路由分配中间件,您可以在组属性数组中使用 middleware
键。中间件将按照您定义的顺序执行:
Route::group(['middleware' => 'auth'], function () {
Route::get('/', function () {
// 使用 Auth 中间件
});
Route::get('user/profile', function () {
// 使用 Auth 中间件
});
});
命名空间
路由组的另一个常见用例是为一组控制器分配相同的 PHP 命名空间。您可以在组属性数组中使用 namespace
参数为组内的所有控制器指定命名空间:
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
键指定:
Route::group(['domain' => '{account}.myapp.com'], function () {
Route::get('user/{id}', function ($account, $id) {
//
});
});
路由前缀
prefix
组数组属性可用于为组内的每个路由添加给定的 URI 前缀。例如,您可能希望为组内的所有路由 URI 添加 admin
前缀:
Route::group(['prefix' => 'admin'], function () {
Route::get('users', function () {
// 匹配 "/admin/users" URL
});
});
您还可以使用 prefix
参数为分组路由指定通用参数:
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 echo csrf_field(); ?>
csrf_field
助手函数生成以下 HTML:
<input type="hidden" name="_token" value="<?php echo csrf_token(); ?>">
当然,使用 Blade 模板引擎:
{{ csrf_field() }}
您无需在 POST、PUT 或 DELETE 请求上手动验证 CSRF 令牌。VerifyCsrfToken
HTTP 中间件 将验证请求输入中的令牌是否与会话中存储的令牌匹配。
从 CSRF 保护中排除 URI
有时您可能希望从 CSRF 保护中排除一组 URI。例如,如果您使用 Stripe 处理支付并利用其 webhook 系统,您将需要从 Laravel 的 CSRF 保护中排除您的 webhook 处理程序路由。
您可以通过将 URI 添加到 VerifyCsrfToken
中间件的 $except
属性中来排除它们:
<?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" 标签中:
<meta name="csrf-token" content="{{ csrf_token() }}">
一旦创建了 meta
标签,您可以指示像 jQuery 这样的库将令牌添加到所有请求头中。这为您的基于 AJAX 的应用程序提供了简单、方便的 CSRF 保护:
$.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
方法中定义您的模型绑定:
将参数绑定到模型
public function boot(Router $router)
{
parent::boot($router);
$router->model('user', 'App\User');
}
接下来,定义一个包含 {user}
参数的路由:
$router->get('profile/{user}', function(App\User $user) {
//
});
由于我们已将 {user}
参数绑定到 App\User
模型,因此将 User
实例注入到路由中。因此,例如,对 profile/1
的请求将注入 ID 为 1 的 User
实例。
如果在数据库中找不到匹配的模型实例,将自动抛出 404 异常。
如果您希望指定自己的 "未找到" 行为,请将 Closure 作为第三个参数传递给 model
方法:
$router->model('user', 'App\User', function() {
throw new NotFoundHttpException;
});
如果您希望使用自己的解析逻辑,您应该使用 Route::bind
方法。您传递给 bind
方法的 Closure 将接收 URI 段的值,并应返回要注入到路由中的类的实例:
$router->bind('user', function($value) {
return App\User::where('name', $value)->first();
});
表单方法伪造
HTML 表单不支持 PUT
、PATCH
或 DELETE
操作。因此,当定义从 HTML 表单调用的 PUT
、PATCH
或 DELETE
路由时,您需要在表单中添加一个隐藏的 _method
字段。与 _method
字段一起发送的值将用作 HTTP 请求方法:
<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 echo method_field('PUT'); ?>
当然,使用 Blade 模板引擎:
{{ method_field('PUT') }}
抛出 404 错误
有两种方法可以从路由手动触发 404 错误。首先,您可以使用 abort
助手。abort
助手只需抛出一个带有指定状态码的 Symfony\Component\HttpFoundation\Exception\HttpException
:
abort(404);
其次,您可以手动抛出 Symfony\Component\HttpKernel\Exception\NotFoundHttpException
的实例。
有关处理 404 异常和为这些错误使用自定义响应的更多信息,请参阅文档的 错误 部分。