路由

路由

基本路由

构建基本的路由只需要一个 URI 与一个 闭包 ,这里提供了一个非常简单优雅定义路由的方法:

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

默认路由文件

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

大多数的应用构建,都是以在 routes/web.php文件定义路由开始的。可以通过在浏览器中输入定义的路由 URL 来访问 routes/web.php 中定义的路由。例如,你可以在浏览器中输入http://your-app.dev/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 路由文件中定义的 POSTPUTDELETE 路由的任何 HTML 表单都应该包含一个 CSRF 令牌字段,否则,这个请求将会被拒绝。可以在 CSRF 文档 中阅读有关 CSRF 更多的信息:

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

重定向路由

如果要定义重定向到另一个 URI 的路由,可以使用 Route::redirect 方法。这个方法可以快速的实现重定向,而不再需要去定义完整的路由或者控制器:

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

Route::redirect 默认会返回状态码 302 。 你可以通过第三个参数自定义返回码:

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

你也可以使用 Route::permanentRedirect 方法来返回 301 状态码:

Route::permanentRedirect('/here', '/there');

视图路由

如果你的路由只需要返回一个视图,可以使用 Route::view 方法。它和 redirect 一样方便,不需要定义完整的路由或控制器。view 方法有二个参数,其中第一个是必填参数,是包含视图名称的URI 。第二个参数选填,可以传入一个数组,数组中的数据会被传递给视图:

Route::view('/welcome');

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

路由参数

必填参数

当然,有时需要在路由中捕获一些 URL 片段。例如,从 URL 中捕获用户的 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 方法在 RouteServiceProvider 的 boot 方法中定义这些模式:

/**
 * 定义你的路由模型绑定, pattern 过滤器等。
 *
 * @return void
 */
public function boot()
{
    Route::pattern('id', '[0-9]+');

    parent::boot();
}

一旦定义好之后,便会自动应用这些规则到所有使用该参数名称的路由上:

Route::get('user/{id}', function ($id) {
    // 只有在 id 为数字时才执行。
});

编码正斜杠字符

Laravel 路由组件允许除 / 之外的所有字符。你必须使用 where 条件正则表达式显式地允许 / 成为占位符的一部分:

Route::get('search/{search}', function ($search) {
    return $search;
})->where('search', '.*');

注意:编码正斜杠字符仅在最后一个路由段中是支持的。

路由命名

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

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

你还可以指定控制器行为的路由名称:

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

生成指定路由的 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 方法的第一个参数中。

中间件

要给路由组中所有的路由分配中间件,可以在 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 一样被分配路由参数,允许你获取一部分子域名作为参数给路由或控制器使用。可以在 group 之前调用 domain 方法来指定子域名:

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

路由前缀

可以用 prefix 方法为路由组中给定的 URL 增加前缀。例如,你可以为组中所有路由的 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 异常。

自定义键名

如果你想要模型绑定在检索给定的模型类时使用除 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 异常。

自定义逻辑解析

如果你想要使用自定义的解析逻辑,就使用 Route::bind 方法。传递到 bind 方法的 闭包 会接受 URI 中大括号对应的值,并且返回你想要在该路由中注入的类的实例:

/**
 * 启动应用服务。
 *
 * @return void
 */
public function boot()
{
        parent::boot();

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

或者,您可以重写 Eloquent 模型上的 resolveRouteBinding 方法。 此方法会接受 URI 中大括号对应的值,并且返回你想要在该路由中注入的类的实例:

/**
 * 检索绑定值的模型。
 *
 * @param  mixed  $value
 * @return \Illuminate\Database\Eloquent\Model|null
 */
public function resolveRouteBinding($value)
{
    return $this->where('name', $value)->first() ?? abort(404);
}

回退路由

使用 Route::fallback 方法, 你可以定义在没有其他路由匹配传入请求时执行的路由。通常,未处理的请求会通过应用程序的异常处理程序自动呈现 “404” 页面。 但是,因为你可以在 routes/web.php 文件中定义 fallback 路由,web 中间件的所有中间件都将应用到路由中。 当然,你也可以根据需要向这条路由中添加额外的中间件:

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

{note} 回退路由应始终是你应用程序注册的最后一个路由。

访问控制

Laravel 包含了一个 middleware 用于控制应用程序对路由的访问。 如果想要使用, 请将 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 输入标签。使用 _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 输入:

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

访问当前路由

你可以使用 Route facade 上的 currentcurrentRouteNamecurrentRouteAction 方法来访问处理传入请求的路由的信息:

$route = Route::current();

$name = Route::currentRouteName();

$action = Route::currentRouteAction();

如果你想知道所有可访问的方法,可以查看 API 文档,了解 Route facadeRoute 实例 的基础类。

本文章首发在 LearnKu.com 网站上。
上一篇 下一篇
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
贡献者:9
讨论数量: 10
发起讨论 只看当前版本


xiaocao
路由命名可以加前缀吗?
2 个点赞 | 7 个回复 | 问答 | 课程版本 5.5
关于路由里中间件使用的问题?
1 个点赞 | 3 个回复 | 问答 | 课程版本 5.5
ldwang
显式绑定 这块是不是有问题啊?
1 个点赞 | 2 个回复 | 问答 | 课程版本 5.5
hustnzj
路由名称前缀:/admin.users 无法访问?
0 个点赞 | 3 个回复 | 问答 | 课程版本 5.6
SHYBOY
检查当前路由
0 个点赞 | 2 个回复 | 问答 | 课程版本 5.5
hustnzj
隐式绑定和显式绑定有何不同?
0 个点赞 | 2 个回复 | 问答 | 课程版本 5.7
Adolph-Wu
Laravel 5.6 路由 domain 设置 可以设置成域名加端口号吗?
0 个点赞 | 1 个回复 | 问答 | 课程版本 5.6
JohnLan
未声明模型,会报错
0 个点赞 | 0 个回复 | 分享 | 课程版本 5.8