HTTP 路由
基本路由
所有的 Laravel 路由都定义在 app/Http/routes.php
文件中,该文件由框架自动加载。最基本的 Laravel 路由只需接受一个 URI 和一个 Closure
,提供了一种非常简单且富有表现力的方法来定义路由:
Route::get('foo', function () {
return 'Hello World';
});
默认路由文件
默认的 routes.php
文件由 RouteServiceProvider
加载,并自动包含在 web
中间件组中,该组提供了会话状态和 CSRF 保护。大多数应用程序的路由将在此文件中定义。
可用的路由器方法
路由器允许您注册响应任何 HTTP 动词的路由:
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 动词的路由:
Route::match(['get', 'post'], '/', function () {
//
});
Route::any('foo', function () {
//
});
路由参数
必需参数
当然,有时您需要在路由中捕获 URI 的片段。例如,您可能需要从 URL 中捕获用户的 ID。您可以通过定义路由参数来实现:
Route::get('user/{id}', function ($id) {
return 'User '.$id;
});
您可以根据路由的需要定义任意数量的路由参数:
Route::get('posts/{post}/comments/{comment}', function ($postId, $commentId) {
//
});
路由参数总是用“大括号”括起来的。当路由执行时,参数将传递给路由的 Closure
。
NOTE
路由参数不能包含 -
字符。请使用下划线 (_
) 代替。
可选参数
有时您可能需要指定一个路由参数,但使该路由参数的存在是可选的。您可以通过在参数名称后加上 ?
来实现。确保为路由的对应变量提供一个默认值:
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...
$url = route('profile');
// 生成重定向...
return 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 ($accountId) {
// 匹配 "/accounts/{account_id}/detail" URL
});
});
CSRF 保护
介绍
Laravel 使得保护您的应用程序免受跨站请求伪造(CSRF)攻击变得简单。跨站请求伪造是一种恶意利用,未经授权的命令在经过身份验证的用户的名义下执行。
Laravel 为应用程序管理的每个活动用户会话自动生成一个 CSRF "令牌"。此令牌用于验证经过身份验证的用户确实是向应用程序发出请求的用户。
每当您在应用程序中定义 HTML 表单时,您都应该在表单中包含一个隐藏的 CSRF 令牌字段,以便 CSRF 保护中间件能够验证请求。要生成包含 CSRF 令牌的隐藏输入字段 _token
,您可以使用 csrf_field
辅助函数:
// 原生 PHP
<?php echo csrf_field(); ?>
// Blade 模板语法
{{ csrf_field() }}
csrf_field
辅助函数生成以下 HTML:
<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
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。
隐式绑定
Laravel 将自动解析在路由或控制器动作中定义的类型提示的 Eloquent 模型,其变量名称与路由段名称匹配。例如:
Route::get('api/users/{user}', function (App\User $user) {
return $user->email;
});
在此示例中,由于在路由上定义的类型提示的 $user
变量与路由 URI 中的 {user}
段匹配,Laravel 将自动注入与请求 URI 中相应值匹配的模型实例。
如果在数据库中找不到匹配的模型实例,将自动生成 404 HTTP 响应。
自定义键名称
如果您希望隐式模型绑定在检索模型时使用 id
以外的数据库列,您可以在 Eloquent 模型上重写 getRouteKeyName
方法:
/**
* 获取模型的路由键。
*
* @return string
*/
public function getRouteKeyName()
{
return 'slug';
}
显式绑定
要注册显式绑定,请使用路由器的 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 HTTP 响应。
自定义解析逻辑
如果您希望使用自己的解析逻辑,您应该使用 Route::bind
方法。传递给 bind
方法的 Closure
将接收 URI 段的值,并应返回要注入到路由中的类的实例:
$router->bind('user', function ($value) {
return App\User::where('name', $value)->first();
});
自定义 "未找到" 行为
如果您希望指定自己的 "未找到" 行为,请将 Closure
作为第三个参数传递给 model
方法:
$router->model('user', 'App\User', function () {
throw new NotFoundHttpException;
});
表单方法伪造
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') }}
访问当前路由
Route::current()
方法将返回处理当前 HTTP 请求的路由,允许您检查完整的 Illuminate\Routing\Route
实例:
$route = Route::current();
$name = $route->getName();
$actionName = $route->getActionName();
您还可以使用 Route
facade 上的 currentRouteName
和 currentRouteAction
辅助方法来访问当前路由的名称或动作:
$name = Route::currentRouteName();
$action = Route::currentRouteAction();
请参考 API 文档以查看 Route facade 的底层类 和 Route 实例 的所有可访问方法。