5 基本功能
5.1 路由
5.1.1 基本路由
laravel所有的路由都可以定义在App/Http/routes.php
文件,它会被App/Providers/RouteServiceProvider
类载入。
下方是一个最基本的get
与post
。get
接收URL,post
为一个闭包。
1 2 3 4 5 6 7
| Route::get('foo', function () { return 'Hello World'; });
Route::post('foo', function () { });
|
默认情况下,routes.php
文件包含单个路由和一个套用web
中间件的路由组。
这个中间件路由组提供了session
状态以及CSRF
保护。
一般情况下,会将全部路由放置在这个路由组。
5.1.1.1 有效的路由方法
注册路由来响应任何HTTP请求:
下方是HTTP请求方法:
1 2 3 4 5 6
| 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请求:
1 2 3 4 5 6 7
| Route::match(['get', 'post'], '/', function () { });
Route::any('foo', function () { });
|
当路由过多时,使用控制器
是个好方法。
5.1.2 路由参数
5.1.2.1 基础路由参数
有时可能需要在路由中捕获URL片段。比如,要从URL中捕获用户ID,需要通过下方代码定义路由参数:
1 2 3
| Route::get('user/{id}', function ($id) { return 'User '.$id; });
|
可以按照需要在路由中定义多个路由参数:
1 2 3
| Route::get('posts/{post}/comments/{comment}', function ($postId, $commentId) { });
|
路由参数总是通过{ }
进行包裹,这些参数在路由被执行时会被传递到路由的闭包。
注意:路由参数不能包含-
,要使用_
替换。
5.1.2.2 选择性路由参数
有时候可能需要指定可选的路由参数,这可以通过在参数后加?
标记来实现,这种情况下需要指定变量的默认值:
下方代码先设定name可以为空
,再设定默认name为John
。
1 2 3 4 5 6 7
| Route::get('user/{name?}', function ($name = null) { return $name; });
Route::get('user/{name?}', function ($name = 'John') { return $name; });
|
5.1.2.3 正则约束
路由设置中可以使用where
方法来约束路由参数。where
方法接收参数名和一个正则表达式来定义参数如何被约束:
1 2 3 4 5 6 7 8 9 10 11
| 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]+']);
|
5.1.2.4 全局约束
如果想要全局约束,可以使用pattern
方法。在RouteServiceProvider
类的boot
方式中定义约束:
1 2 3 4 5 6 7 8 9 10 11
|
public function boot(Router $router){ $router->pattern('id', '[0-9]+'); parent::boot($router); }
|
一旦模式被定义,将会自动应用到所有包含参数名的路由中:
1 2 3
| Route::get('user/{id}', function ($id) { });
|
上方例子中限定id
只能是数字。
5.1.3 命令路由
命令路由为生成URL或重新定向提供方便。在定义路由是使用as
指定路由名称:
1 2 3
| Route::get('user/profile', ['as' => 'profile', function () { }]);
|
此外,还可以为控制器动作指定路由名称:
1 2 3
| Route::get('user/profile', [ 'as' => 'profile', 'uses' => 'UserController@showProfile' ]);
|
还有另一种,路由命名的方法,比如使用name
方式来实现:
1 2 3
| Route::get('user/profile',function () { })->name('profile');
|
5.1.3.1 路由群组 & 命名路由
如果你在使用路由群组,可以通过在路由群组的属性数组中指定as
关键字来为群组的路由设置一个共用的路由名前缀:
1 2 3 4 5
| Route::group(['as' => 'admin::'], function () { Route::get('dashboard', ['as' => 'dashboard', function () { }]); });
|
5.1.3.2 为命名路由生成URL
如果为给定路由进行了命名,就可以通过全局route
函数为该命名路由生成对应URL,或者重新定向:
1 2
| $url = route('profile'); $redirect = redirect()->route('profile');
|
如果命名路由定义了参数,可以将该参数作为第二个参数穿传递给route
函数。路由参数会自动插入URL中。
1 2 3 4
| Route::get('user/{id}/profile', ['as' => 'profile', function ($id) { }]); $url = route('profile', ['id' => 1]);
|
5.1.4 路由群组
路由组允许共享路由属性,比如中间件与命名空间等。这样利用路由群组套用这些属性到多个路由,而不需要在每个路由都设定一次。
共享属性被要求为数组格式。共享属性作为第一个参数被传递给Route::group
方法。
5.1.4.1 中间件
要给路由组中所有路由分配中间件,可以在群组属性中使用middleware
。中间件将会按照数组中定义的顺序依次执行:
1 2 3 4 5 6 7 8 9
| Route::group(['middleware' => 'auth'], function () { Route::get('/', function () { });
Route::get('user/profile', function () { }); });
|
5.1.4.2 命名空间
另一个例子,指定相同的PHP命名空间下多个控制器,可以在分组属性数组中使用namespace
来指定群组中所有控制器的公共命名空间:
1 2 3 4 5 6 7
| Route::group(['namespace' => 'Admin'], function(){
Route::group(['namespace' => 'User'], function(){ }); });
|
默认情况下,RouteServiceProvider
会在命名空间群组内导入routes.php
文件,让你不用指定完整App/Http/Controllers
命令空间,就能注册控制器。所有,只需要指定在App/Http/Controllers
之后的命名。
5.1.4.3 子域名路由
子域名可以像URL一样被分配给路由参数,从而允许捕获子域名的部分用于路由或者控制器,子域名可以通过群组属性中的domani
来指定:
1 2 3 4 5
| Route::group(['domain' => '{account}.myapp.com'], function () { Route::get('user/{id}', function ($account, $id) { }); });
|
注意:其中的{account}可以随意修改的。{id}也是可以随意更改的。
5.1.4.4 路由前缀
prefix
群组属性,用来为群组中每个路由添加一个给定URL前缀。
例如,你可以为所有路由URL添加admin
前缀:
1 2 3 4 5
| Route::group(['prefix' => 'admin'], function () { Route::get('users', function () { }); });
|
还可以使用prefix
参数为路由组指定公共路由参数:
1 2 3 4 5
| Route::group(['prefix' => 'accounts/{account_id}'], function () { Route::get('detail', function ($account_id) { }); });
|
5.1.5 CSRF保护
5.1.5.1 简介
CSRF指跨网站请求伪造。跨网站请求伪伪造是一种恶意攻击,透过经身份验证的使用者身份执行未经授权的命令。
laravel会自动产生一个CSRF令牌给每个被应用管理的有效的用户,该令牌用于验证授权用户和发起请求者是否为同一个人。
想要生成包含CSRF令牌的隐藏输入字段,可以使用csrf_field
函数来实现:
1
| <?php echo csrf_field(); ?>
|
csrf_field
函数会生成如下HTML:
1
| <input type="hidden" name="_token" value="<?php echo csrf_token(); ?>">
|
当然推荐使用Blade
引擎模板提供的方式:
不需要自己编写代码去验证POST、PUT或者DELETE请求的CSRF令牌,因为Laravel自带的HTTP中间件VerfyCsrfToken
会完成这些工作。
只要将请求中的输入token
和Session中存储的token
作对比来进行验证。
5.1.5.2 不受 CSRF 保护的 URIs
有时需要从CSRF保护中排除一些URL。比如,如果使用Stripe
来处理支付并用到他们的webhook系统,这时就需要从laravel的CSRF保护中排除webhook处理路由。
要实现这一目的,需要在VerifyCsrfToken
中间件中将要排除的URL添加到$except
属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;
class VerifyCsrfToken extends BaseVerifier {
protected $except = [ 'stripe/*', ]; }
|
5.1.5.3 X-CSRF-Token
除了检查当前POST参数的CDSRF令牌外,在laravel的VerifyCsrfToken
中介层也会确认请求头部中的X-CSRF-Token
。
例如,可以将其存储在meta标签中:
1
| <meta name="csrf-token" content="{{ csrf_token() }}">
|
一旦建立了meta
标签,就可以使用jQuery之类的函数库,将令牌
加入到所有请求头部。
这为基于AJAX的应用提供了简单、方便的方式来避免CSRF攻击。
1 2 3 4 5
| $.ajaxSetup({ headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') } });
|
5.1.5.4 X-XSRF-Token
laravel还会将CSRF令牌保存到名为XSRF-TOKEN
的Cookie中,可以使用该Cookie值来设置X-XSRF-TOKEN
请求头。
一些javascript框架,比如Angular,会为你自动进行设置,基本上不太需要手动设置这个值。
5.1.6 路由模型绑定
laravel路由模型绑定提供了一个方便的方式来注入类至路由中。
例如,可以将匹配到ID
的整个User类注入到路由中,而不是直接注入用户ID。
5.1.6.1 隐式绑定
laravel会自动解析定义在路由或控制器动作(变量名匹配路由片段)中的Eloquent模型类型声明,例如:
1 2 3
| Route::get('api/users/{user}', function (App\User $user) { return $user->email; });
|
这个例子中,路由URL的user
符合$user
实例的ELoquent模型,所以laravel会自动注入与请求URL的ID对应的模型实例。
如果找不到对应的模型实例,会自动生成HTTP404响应。
5.1.6.2 显式绑定
要注册显式绑定,需要使用路由的model
方法来为给定参数指定绑定类。
必须在RouteServiceProvider::boot
方法中定义模型绑定。
绑定参数到模型
1 2 3 4 5
| public function boot(Router $router) { parent::boot($router); $router->model('user', 'App\User'); }
|
接下来,定义一个包含user
参数的路由:
1 2 3
| $router->get('profile/{user}', function(App\User $user) { });
|
由于已经绑定{user}
参数到App\User
模型,User实例会被注入到该路由。因此,如果请求URL是profile/1
,就会注入一个用户ID为1的User实例。
如果匹配的模型实例在数据库不存在,会自动生成并返回HTTP404响应。
自定义解析逻辑
如果想要使用自定义的解析逻辑需要使用Route::bind
方法,传递到bind
方法的闭关会获取到URL请求的参数中的值,并返回你想要在该路由中注入的类实例:
1 2 3
| $router->bind('user', function($value) { return App\User::where('name', $value)->first(); });
|
自定义”Not Found”
如果想要指定自己的Not Found
行为,将封装该行为的闭包作为第三个参数传递给model
方法
1 2 3
| $router->model('user', 'App\User', function() { throw new NotFoundHttpException; });
|
5.1.7 表单方法伪造
HTML表单不支持PUT、PATCH或者DELETE请求方法。当使用这些路由时,需要添加一个隐藏的_method
字段到表单中,其值被用作该表单的HTTP请求方法:
1 2 3 4
| <form action="/foo/bar" method="POST"> <input type="hidden" name="_method" value="PUT"> <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form>
|
还可以使用辅助函数method_field
来实现这一目的:
1
| <?php echo method_field('PUT'); ?>
|
当然,也支持Blade模板引擎:
{method——field('PUT')}