# 路由 - [基本路由](#basic-routing) - [路由重定向](#redirect-routes) - [视图路由](#view-routes) - [路由参数](#route-parameters) - [必选参数](#required-parameters) - [可选参数](#parameters-optional-parameters) - [正则表达式参数](#parameters-regular-expression-constraints) - [命名路由](#named-routes) - [路由组](#route-groups) - [中间件](#route-group-middleware) - [命名空间](#route-group-namespaces) - [子域名路由](#route-group-sub-domain-routing) - [路由前缀](#route-group-prefixes) - [路由命名前缀](#route-group-name-prefixes) - [路由模型绑定](#route-model-binding) - [隐式绑定](#implicit-binding) - [显式绑定](#explicit-binding) - [回退路由](#fallback-routes) - [限流](#rate-limiting) - [表单方法伪造](#form-method-spoofing) - [访问当前路由](#accessing-the-current-route) ## 基本路由 构建基本的路由只需要一个 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('/', function () { // }); #### CSRF 保护 指向 `web` 路由文件中定义的 `POST`、`PUT` 或 `DELETE` 路由的任何 HTML 表单都应该包含一个 CSRF 令牌字段,否则,这个请求将会被拒绝。可以在 [CSRF 文档](/docs/{{version}}/csrf) 中阅读有关 CSRF 更多信息:
### 重定向路由 如果要定义重定向到另一个 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` 方法在 `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` 方法的第一个参数中。 嵌套的组尝试智能地「合并」其属性及其父组。中间件和 `where` 条件语句在附加名称、命名空间和前缀时被合并。在适当的情况下,命名空间的分隔符和斜线会被自动添加到 URI 前缀中。 ``` Route::post('auth/login', [ 'uses' => 'Auth\AuthController@login', 'middleware'=>['first'] ]); Route::group(['prefix' => 'auth', 'middleware' => ['auth',]], function() { //baidu.com/auth/show Route::post('show', 'Auth\AuthController@show'); }); ``` ### 中间件 要给路由组中所有的路由分配中间件,可以在 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) { // }); }); > {注意} 为了确保您的子域名路由是可访问的,您应该在注册根域名路由之前注册子域名路由。这将防止根域名路由覆盖具有相同 URI 路径的子域名路由。 ### 路由前缀 可以用 `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 () { // }); > {注意} 回退路由应始终是你应用程序注册的最后一个路由。 ## 限流 Laravel 包含了一个 [中间件](https://learnku.com/docs/laravel/{{version}}/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 () { // }); }); #### 独立访客和认证用户的限流 您可以为访客和经过身份验证的用户指定不同的访问控制。例如,可以为访客指定每分钟最多 10 次请求,为认证用户设置每分钟最多 60 次请求: Route::middleware('throttle:10|60,1')->group(function () { // }); 您还可以将此功能与动态限流相结合。例如,如果 `User` 模型包含一个 `rate_limit` 属性,您可以将该属性的名称传递给 `throttle` 中间件,以便用于计算认证用户的最大请求数: Route::middleware('auth:api', 'throttle:10|rate_limit,1')->group(function () { Route::get('/user', function () { // }); }); ## 表单方法伪造 HTML 表单不支持 `PUT` 、`PATCH` 或 `DELETE` 行为。所以当你要从 HTML 表单中调用定义了 `PUT`、 `PATCH` 或 `DELETE` 行为的路由时,你将需要在表单中增加一个隐藏的 `_method` 输入标签。使用 `_method` 字段的值作为 HTTP 的请求方法: 你也可以使用 `@method` 模板指令生成 `_method` 输入: ## 访问当前路由 你可以使用 `Route` facade 上的 `current` 、 `currentRouteName` 和 `currentRouteAction` 方法来访问处理传入请求的路由的信息: $route = Route::current(); $name = Route::currentRouteName(); $action = Route::currentRouteAction(); 如果你想知道所有可访问的方法,可以查看 API 文档,了解 [路由 facade 基础类](https://laravel.com/api/{{version}}/Illuminate/Routing/Router.html) 和 [Route 实例](https://laravel.com/api/{{version}}/Illuminate/Routing/Route.html) 。