本篇主要讲解在Laravel框架中路由的基本知识,主要参考中文文档中的路由章节,并融合本身的理解。以通俗易懂的方式来解析Laravel框架中最重要的路由知识。php
学习Laravel路由以前咱们好好了解如下几个知识
第一个就是 —— HTTP 协议基础。不要求彻底了解 HTTP 协议,但必需要知道一小部分概念。若是对其不了解,会在阅读 laravel 文档时出现不少疑惑。对于HTTP协议的理解能够参考这篇文章深刻理解HTTP协议。html
第二个是 —— RESTful,这是一种设计思想,这对学习 laravel 的路由有很大的帮助,尤为是在学习 laravel 控制器的资源控制器部分,不少人的疑惑都是在不了解 RESTful 的状况下产生的。阮一峰的日志中有两篇详细讲解了 RESTful,其中一篇是理解RESTful架构。laravel
全部的 Laravel 路由都在 routes 目录中的路由文件中定义,这些文件都由框架自动加载。routes/web.php 文件用于定义 web 界面的路由。这里面的路由都会被分配给 web 中间件组,它提供了会话状态和 CSRF 保护等功能。定义在 routes/api.php 中的路由都是无状态的,而且被分配了 api 中间件组。web
大多数的应用构建,都是以在 routes/web.php 文件定义路由开始的。能够经过在浏览器中输入定义的路由 URL 来访问 routes/web.php 中定义的路由。例如,你能够在浏览器中输入 http://your-app.dev/user 来访问如下路由正则表达式
Route::get('/user', 'UserController@index');
构建基本的路由只须要一个 URI 与一个 闭包函数(什么是闭包函数,想了解更多闭包函数,请看这篇PHP的闭包) ,这里提供了一个很是简单优雅定义路由的方法:数据库
Route::get('foo', function () { return 'Hello World'; });
路由器容许你注册能响应任何 HTTP 请求的路由,大体有如下七种api
Route::get('books', 'BooksController@index'); //获取 Route::post('books', 'BookController@store'); //保存 Route::get('books/create', 'BookController@create'); //建立 Route::get('books/:id', 'BookController@show'); //显示 Route::put('books/:id', 'BookController@update'); //更新 Route::delete('books/:id', 'BookController@destroy'); //删除 Route::get('books/:id/edit', 'BookController@edit'); //编辑
以上的七种方法能够用Route:source的方法来进行总结使用。后面会对Route:source方法进行详解。数组
Route::resource('books', 'BooksController');
有的时候你可能须要注册一个可响应多个 HTTP 请求的路由,这时你可使用 match 方法,也可使用 any 方法注册一个实现响应全部 HTTP 请求的路由,可是根据面向对象的单一职责原则,一个类或者一个模块应该有且只有一个职责,因此不推荐使用。(什么是单一职责原则?请看这篇文章单一职责原则)浏览器
Route::match(['get', 'post'], '/', function () { // }); Route::any('foo', function () { // });
若是要定义重定向到另外一个 URI 的路由,可使用 Route::redirect 方法。这个方法能够快速的实现重定向,而再也不须要去定义完整的路由或者控制器:restful
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'); //参数1:URL 参数2:视图名称 Route::view('/welcome', 'welcome', ['name' => 'Taylor']); //参数1:URL 参数2:视图名称 参数3:可选参数(能够传数组)
固然,有时须要在路由中捕获一些 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 方法在 appProvidersRouteServiceProvider 的 boot 方法中定义这些模式:
/** * 定义你的路由模型绑定, pattern 过滤器等。 * * @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
为路由指定了名称后,就可使用全局辅助函数 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 会在命名空间组中引入你的路由文件,让你不用指定完整的 AppHttpControllers 命名空间前缀就能注册控制器路由。所以,你只须要指定命名空间 AppHttpControllers 以后的部分。
路由组也能够用来处理子域名。子域名能够像路由 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 模型 AppUser,变量名称又与 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} 参数绑定至 AppUser 模型,因此 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 包含了一个 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 表单不支持 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 输入:
<form action="/foo/bar" method="POST"> @method('PUT') @csrf </form>
CSRF 保护
指向 web 路由文件中定义的 POST、PUT 或 DELETE 路由的任何 HTML 表单都应该包含一个 CSRF 令牌字段,不然,这个请求将会被拒绝。能够在 CSRF 文档 中阅读有关 CSRF 更多的信息.
<form method="POST" action="/profile"> @csrf ... </form>
你可使用 Route facade 上的 current 、 currentRouteName 和 currentRouteAction 方法来访问处理传入请求的路由的信息。
$route = Route::current(); $name = Route::currentRouteName(); $action = Route::currentRouteAction();