路由

未匹配的标注

路由

基本路由

构建基本路由只需要一个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.test/user 来访问以下路由:

use App\Http\Controllers\UserController;

Route::get('/user', [UserController::class, 'index']);

定义在 routes/api.php 文件中的路由是被 RouteServiceProvider 嵌套在一个路由组内。在这个路由组中,将自动应用 /api URI 前缀,所以你无需手动将其应用于文件中的每个路由。你可以通过修改 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('/', function () {
    //
});

CSRF 保护

指向 web 路由文件中定义的 POSTPUTPATCH, 或 DELETE 路由的任何 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', 'welcome');

Route::view('/welcome', '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 方法在 RouteServiceProviderboot 方法中定义这些:

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

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

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::class, 'show'])->name('profile');

注意:路由命名必须是唯一的

生成指定路由的 URL

一旦为路由指定了名称,就可以使用全局辅助函数 route 来生成链接或者重定向到该路由:

// 生成链接...
$url = route('profile');

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

如果有定义参数的命名路由,可以把参数作为 route 函数的第二个参数传入,指定的参数将会自动插入到 URL 中对应的位置:

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

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

如果在数组中传递额外的参数,这些键或值将自动添加到生成的 URL 的查询字符串中:

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

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

// /user/1/profile?photos=yes

技巧:有时候,你可能希望为某些 URL 参数的请求范围指定默认值,例如在本地环境,你可以使用 URL::defaults 方法

检查当前路由

如果你想判断当前请求是否指向了某个命名过的路由,你可以调用路由实例上的 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 方法的第一个参数中。

嵌套的组尝试智能地「合并」其属性及其父组。中间件和 where 条件语句在附加名称、命名空间和前缀时被合并。在适当的情况下,命名空间的分隔符和斜线会被自动添加到 URI 前缀中。

中间件

要给路由组中所有的路由分配中间件,可以在 group 之前调用 middleware 方法,中间件会依照它们在数组中列出的顺序来运行:

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

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

子域名路由

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

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

注意:为了确保您的子域名路由是可访问的,您应该在注册根域名路由之前注册子域名路由。这将防止根域名路由覆盖具有相同 URI 路径的子域名路由。

路由前缀

prefix 方法将会为路由组中的每一个 URI 添加前缀。例如,您可以给该组中所有的 URI 添加 admin 的前缀:

Route::prefix('admin')->group(function () {
    Route::get('users', function () {
        // Matches The "/admin/users" URL
    });
});

路由名称前缀

name 方法可以为路由组中每一个路由名称添加一个指定的字符串作为前缀。例如,您可以给已经分组的路径添加 admin 的前缀。给定的字符串与指定的路由名称前缀完全相同,因此我们将确保在前缀中提供尾部的 . 字符:

Route::name('admin.')->group(function () {
    Route::get('users', function () {
        // Route assigned name "admin.users"...
    })->name('users');
});

路由与模型绑定

当向路由或控制器方法注入模型 ID 时,您可能总是会检索该 ID 与模型是否一致。Laravel 路由模型绑定提供了一个方便的方案,它可以自动注入模型实例到路由中。例如,您可以注入与给定 ID 匹配的整个 User 模型实例,而不是注入用户的 ID。

隐式绑定

Laravel 会自动处理在路由或控制器方法中,与类型提示的变量名相匹配的路由段名称的的 Eloquent 模型。例如:

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

在这个例子中,由于 $user 变量被类型提示为 Eloquent 模型 App\Models\User,变量名称又与 URI 中的 {user} 匹配,因此,Laravel 会自动注入与请求 URI 中传入的 ID 匹配的用户模型实例。如果在数据库中未找到对应的模型实例,将会自动生成 404 异常。

自定义键名

有时,您可能希望使用 id 以外的列来解析 Eloquent 模型。为此,您可以在路由参数定义中指定列:

Route::get('api/posts/{post:slug}', function (App\Models\Post $post) {
    return $post;
});

自定义键和作用域

有时,当一个路由定义中隐式绑定多个 Eloquent 模型时, 您可能需要限定第二个 Eloquent 模型的作用域,使它是第一个 Eloquent 模型的子模型。例如, 考虑这样一种情况,通过 slug 字段为特定用户检索博客文章:

use App\Models\Post;
use App\Models\User;

Route::get('api/users/{user}/posts/{post:slug}', function (User $user, Post $post) {
    return $post;
});

当使用自定义键值隐式绑定作为嵌套路由参数时,Laravel 将自动限定查询的范围,通过其父节点使用猜测父节点上关系名称的约定来检索嵌套模型。 在本例中,假定 User 模型有一个名为 posts 的关联(路由参数名的复数形式),可用于检索 Post 模型。

自定义键名

如果您希望模型绑定在检索给定的模型类时,默认使用的是 id 以外的数据库字段,则可以重写 Eloquent 模型上的 getRouteKeyName 方法:

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

显式绑定

要注册显式绑定,请使用路由器的 model 方法为给定的参数指明参数对应的类。您应该在 RouteServiceProvider 类的 boot 方法的开头定义显式模型绑定:

/**
 * 定义你的路由模型绑定, pattern 过滤器等
 *
 * @return void
 */
public function boot()
{
    Route::model('user', App\Models\User::class);

    // ...
}

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

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

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

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

自定义解析逻辑

如果您希望使用自己的解析逻辑,应该使用 Route::bind 方法。传递给 bind 方法的 闭包 将接收 URI 中大括号对应的值,并返回您想要在该路由中注入的类的实例:

/**
 * 定义你的路由模型绑定, pattern 过滤器等
 *
 * @return void
 */
public function boot()
{
    Route::bind('user', function ($value) {
        return App\Models\User::where('name', $value)->firstOrFail();
    });

    // ...
}

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

/**
 * 检查绑定值的模型
 *
 * @param  mixed  $value
 * @param  string|null  $field
 * @return \Illuminate\Database\Eloquent\Model|null
 */
public function resolveRouteBinding($value, $field = null)
{
    return $this->where('name', $value)->firstOrFail();
}

回退路由

使用 Route::fallback 方法, 您可以定义一个在没有其他路由可匹配传入的请求时才执行的路由。一般来说,未处理的请求会通过应用程序的异常处理程序自动呈现 「404」 页面。但是由于您可以在 routes/web.php 文件中定义 fallback 路由,因此 web 中间件组中的所有中间件都将应用于该路由。当然,您也可以根据需要随意向此路由添加其他中间件:

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

注意:回退路由应始终是您应用程序注册的最后一个路由。

限流

定义限流器

Laravel 包含功能强大且可自定义的限流服务,您可以利用这些服务来限制给定的路由或一组路由的流量。首先,您应该定义满足应用需求的限流器配置。通常,这可以写在应用程序的 RouteServiceProvider 中。

限流器可以使用 RateLimiter 门面的 for 方法定义。 for 方法接受一个速率限制器名称和一个闭包会返回限制配置,该配置可以应用于分配了该限流器的路由:

use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter;

RateLimiter::for('global', function (Request $request) {
    return Limit::perMinute(1000);
});

如果传入的请求速率超过了设置的请求频率,Laravel 会自动的响应 HTTP 429 的状态码。如果你想自定义响应内容,使用 response 方法:

RateLimiter::for('global', function (Request $request) {
    return Limit::perMinute(1000)->response(function () {
        return response('Custom response...', 429);
    });
});

Laravel 提供了使用 $request 实例调取参数,如 user() 来进行身份验证从而自定义请求频率的大小或是否限制。

RateLimiter::for('uploads', function (Request $request) {
    return $request->user()->vipCustomer()
                ? Limit::none()
                : Limit::perMinute(100);
});

范围频率限制

根据业务有时会按某个范围去限制频率。比如 VIP 用户无限制,不是 VIP 的用户每个 IP 每分钟只能请求100次:

RateLimiter::for('uploads', function (Request $request) {
    return $request->user()->vipCustomer()
                ? Limit::none()
                : Limit::perMinute(100)->by($request->ip());
});

多个频率限制

你可以使用数组的形式制定多个限制,路由会根据数组的先后顺序依次去执行:

RateLimiter::for('login', function (Request $request) {
    return [
        Limit::perMinute(500),
        Limit::perMinute(3)->by($request->input('email')),
    ];
});

直接给路由配置限制

将配置好的频率限制器直接配置到路由上,使用 throttle 关键字加上频率限制器的名字放到中间件 middleware 数组中即可:

Route::middleware(['throttle:uploads'])->group(function () {
    Route::post('/audio', function () {
        //
    });

    Route::post('/video', function () {
        //
    });
});

表单方法伪造

HTML 表单不支持 PUTPATCHDELETE 请求。所以当 HTML 表单调用请求方式为 PUTPATCHDELET 的路由时,您需要在表单中添加一个 _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 Blade 指定生成 _method 隐藏域:

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

访问当前路由

您可以使用 Route 门面的 currentcurrentRouteNamecurrentRouteAction 方法处理传入的请求路由访问信息:

$route = Route::current();

$name = Route::currentRouteName();

$action = Route::currentRouteAction();

有关 Route 门面Route 实例 的基础类的信息,请参阅 API 文档,以查看所有可访问的方法。

跨域资源共享 (CORS)

Laravel 可以使用您配置的值自动响应 CORS OPTIONS 请求。所有的 CORS 设置都可以在您的 cors 配置文件中设置, 默认情况下的全局中间件堆中的 HandleCors 中间件会自动处理OPTIONS 请求。

技巧:要获取 CORS 和 CORS 头的更多信息,请参阅 MDN web 文档上的 CORS 节

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
上一篇 下一篇
Summer
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
贡献者:16
讨论数量: 0
发起讨论 只看当前版本


暂无话题~