路由

Laravel 中文文档 / 基础功能

路由

基本路由

最基本的 Laravel 路由接收一个 URI 和一个闭包,提供了非常简单且直观的方式来定义路由:

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

默认路由文件

所有的 Laravel 路由都在 routes 目录中定义,这些文件由框架自动加载。routes/web.php 文件用于定义 Web 界面的路由。这些路由被分配给 web 中间件组,它提供了会话状态和 CSRF 保护等功能。routes/api.php 中的路由都是无状态的,并被分配给 api 中间件组。

对于大多数应用,您都会先在 routes/web.php 文件中定义路由。routes/web.php 中定义的路由可以通过在浏览器中输入路由中定义的 URL 来访问。例如,您可以通过在浏览器中导航到 http://your-app.test/user 来访问以下路由:

Route::get('/user', 'UserController@index');

routes/api.php 文件中定义的路由通过 RouteServiceProvider 被嵌套到一个路由组里。该路由组中的路由会自动添加 URL 前缀 /api,这样您就不用手动为每个路由添加前缀了。您可以在 RouteServiceProvider 类中修改此前缀和其它路由组选项。

可用的路由器方法

路由器允许您注册响应任何 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 () {
    //
});

CSRF 保护

任何指向 web 路由文件中定义的 POSTPUT 或者 DELETE 路由的 HTML 表单,都应该包含一个 CSRF 令牌字段。否则,该请求将被拒绝。您可以在 CSRF 文档 中阅读有关 CSRF 保护的更多内容:

<form method="POST" action="/profile">
    @csrf
    ...
</form>

重定向路由

如果要定义一个重定向到另一个 URI 的路由,可以使用 Route::redirect 方法。该方法可以方便地实现重定向,而无需定义完整的路由或者控制器:

Route::redirect('/here', '/there', 301);

视图路由

如果路由只要返回视图,可以使用 Route::view 方法。与 redirect 方法一样,该方法提供了一个简便的方式,因此无需定义完整的路由或控制器。view 方法有三个参数,其中前两个是必填参数,分别是 URI 和视图名称。第三个参数选填,可以传入一个数组,数组中的数据会被传递给视图:

Route::view('/welcome', 'welcome');

Route::view('/welcome', 'welcome', ['name' => 'Taylor']);

路由参数

必填参数

当然,有时需要获取路由中的 URI 字段。例如,从 URI 中获取用户的 ID。可以通过定义路由参数来这样做:

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

也可以根据需要在路由中定义多个参数:

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

路由参数放在 {} 中,并且应由字母组成,同时不能包含 -。如需使用 -,可以使用下划线(_)代替。路由参数会按顺序依次注入到路由回调/控制器中——而不受回调/控制器参数名的影响。

可选参数

有时可能需要指定一个路由参数,但希望该参数是可选的。那么可以在参数后面加上 ?。确保给对应变量一个默认值:

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 方法。应该在 RouteServiceProviderboot 方法中定义这些约束:

/**
 * 定义路由绑定,约束条件等等
 *
 * @return void
 */
public function boot()
{
    Route::pattern('id', '[0-9]+');

    parent::boot();
}

定义好约束之后,会自动应用到使用该参数名的所有路由:

Route::get('user/{id}', function ($id) {
    // 当 {id} 是数字时才会执行
});

命名路由

对路由命名后,可以方便地生成 URL 或者重定向。通过在路由上链式调用 name 方法来指定路由名称:

Route::get('user/profile', function () {
    //
})->name('profile');

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

Route::get('user/profile', 'UserProfileController@show')->name('profile');

为命名路由生成 URL

为给定路由指定名称后,就可以使用路由名称来生成 URL,或者通过全局辅助函数 route 生成重定向:

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

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

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

Route::get('user/{id}/profile', function ($id) {
    //
})->name('profile');

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

检查当前路由

如果要判断当前请求是否路由到了给定的命名路由,可以在路由实例上调用 named 方法。例如,可以在路由中间件中检查当前路由名称:

/**
 * 处理传入的请求
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \Closure  $next
 * @return mixed
 */
public function handle($request, Closure $next)
{
    if ($request->route()->named('profile')) {
        //
    }

    return $next($request);
}

路由组

路由组允许您在大量路由之间共享路由属性(例如中间件或命名空间),而不需要为每个路由单独定义这些属性。共享属性应以数组的形式传到 Route::group 方法的第一个参数。

中间件

要将中间件分配给路由组中所有的路由,可以在定义路由组之前调用 middleware 方法。中间件会按它们在数组中的顺序依次执行:

Route::middleware(['first', 'second'])->group(function () {
    Route::get('/', function () {
        // 使用 first 和 second 中间件
    });

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

命名空间

另一个路由组的常见用例是使用 namespace 方法将相同 PHP 命名空间分配给路由组的中所有的控制器:

Route::namespace('Admin')->group(function () {
    // 控制器都在「App\Http\Controllers\Admin」命名空间下
});

要记住的是,默认情况下,RouteServiceProvider 会在命名空间组中引入路由文件,使您不用指定完整的 App\Http\Controllers 命名空间前缀就能注册控制器路由。因此,只需要指定命名空间 App\Http\Controllers 之后的部分即可。

子域名路由

路由组也可以用于处理子域名路由。子域名可以像路由 URI 一样分配路由参数,允许您获取子域名的参数部分给路由或控制器使用。可以在定义路由组之前调用 domain 方法来指定子域名:

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

路由前缀

prefix 方法可用于为路由组中所有路由的 URI 添加给定的前缀。例如,您可以为路由组中所有路由的 URI 加上 admin 前缀:

Route::prefix('admin')->group(function () {
    Route::get('users', function () {
        // 匹配「/admin/users」URL
    });
});

路由名称前缀

name 方法可用于为路由组中所有路由名称添加给定的前缀。例如,您可以为路由组中所有路由名称加上 admin 前缀。由于给定字符串添加前缀后和路由名称完全一致,因此我们要确保在前缀最后添加一个 . 字符:

Route::name('admin.')->group(function () {
    Route::get('users', function () {
        // 路由名是「admin.users」
    })->name('users');
});

路由模型绑定

将模型 ID 注入路由或控制器操作时,通常会查询该 ID 对应的模型。Laravel 路由模型绑定提供了直接将模型实例自动注入到路由的便捷方法。例如,您可以注入和给定用户 ID 匹配的 User 模型,而不是用户 ID。

隐式绑定

如果路由或控制器操作中类型提示的变量名与路由的参数名相匹配,那么 Laravel 会自动解析对应的 Eloquent 模型。例如:

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

由于 $user 变量被类型提示为 Eloquent 模型 App\User,变量名又与 URI 中的 {user} 相匹配,因此 Laravel 会自动注入与所请求 URI 中传入的 ID 匹配的用户模型实例。如果在数据库中找不到对应的模型实例,则会自动生成一个 404 HTTP 响应。

自定义键名

如果您希望模型绑定时使用除 id 之外的字段来查询给定的模型类,可以重写 Eloquent 模型的 getRouteKeyName 方法:

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

显式绑定

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

public function boot()
{
    parent::boot();

    Route::model('user', App\User::class);
}

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

Route::get('profile/{user}', function (App\User $user) {
    //
});

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

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

自定义解析逻辑

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

public function boot()
{
    parent::boot();

    Route::bind('user', function ($value) {
        return App\User::where('name', $value)->first() ?? abort(404);
    });
}

回退路由

使用 Route::fallback 方法,可以定义请求在没有匹配到任何路由时执行的路由。通常来说,未处理的请求会由应用的异常处理器渲染为「404」页面。然而,由于您可以在 routes/web.php 文件中定义 fallback 路由,因此所有 web 中间件组中的中间件都会应用到该路由。当然,您可以根据需要在路由上添加其它中间件:

Route::fallback(function () {
    //
});

速率限制

Laravel 包含一个 中间件 用于在应用中限制路由访问速率。首先,将 throttle 中间件分配给一个路由或路由组。throttle 中间件接收两个参数,决定了在给定的分钟数内最多可以请求多少次。例如,我们指定一个认证用户每分钟最多可以访问以下路由组 60 次:

Route::middleware('auth:api', 'throttle:60,1')->group(function () {
    Route::get('/user', function () {
        //
    });
});

动态速率限制

您可以根据认证用户 User 模型的属性来动态请求最大值。例如,如果 User 模型包含 rate_limit 属性,则可以将该属性的名称传给 throttle 中间件,以便用来计算最大请求数:

Route::middleware('auth:api', 'throttle:rate_limit,1')->group(function () {
    Route::get('/user', function () {
        //
    });
});

表单方法伪造

HTML 表单不支持 PUTPATCHDELETE 方法。所以当在 HTML 表单中使用 PUTPATCHDELETE 定义的路由时,您需要在表单中增加一个隐藏的 _method 字段。并将 HTTP 请求类型作为 _method 字段的值一起发送:

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

还可以使用 Blade 指令 @method 来生成 _method 表单隐藏域:

<form action="/foo/bar" method="POST">
    @method('PUT')
    @csrf
</form>

获取当前路由

您可以使用 Route Facade 的 currentcurrentRouteNamecurrentRouteAction 方法来获取当前请求的路由信息:

$route = Route::current();

$name = Route::currentRouteName();

$action = Route::currentRouteAction();

参阅 路由 Facade 的底层类路由实例 的 API 文档了解更多可用方法。

  上一篇 下一篇  

作者:ilaoniu

牛哥,俗称哞哞。单纯的九零后理工小青年。喜欢折腾,爱玩,爱安卓,爱音乐,爱游戏,爱电影,爱旅游...