简介
中间件用来过滤项目中的 HTTP 请求,实际上 Laravel 项目中大量使用了中间件。例如,Laravel 中有一个验证用户是否认证的中间件,如果没有认证,就跳转到登录页面;如果认证了就进一步操作。
当然,中间件的作用不只是在认证上。CORS 中间件负责给项目中的相应设定正确的头部(Headers);日志中的中间件负责人记录项目中处理的所有请求信息。
Laravel 框架中包含了几种中间件,包括负责处理认证和 CSRF 保护的。所有中间件位于 app/Http/Middleware
目录下。
定义中间件
使用 Artisan 命令 make:middleware
创建中间件:
php artisan make:middleware CheckAge
这个命令会在 app/Http/Middleware
目录下创建一个 CheckAge
类。在这个中间件里,我么设定年龄大于18岁的可以进一步操作,小于等于18岁的重定向到用户控制台地址/home
。
age <=18){
return redirect('/home');
}
return $next($request);
}
}
允许请求的进一步操作,只需将请求实例 $request
放入 $next
中即可。
可以把中间件设想成,在最终要达到执行的业务代码前的一层层 「过滤网」,请求必须通过所有的中间件才能达到最终要执行的业务逻辑,否则被中间任何一个中间件拒绝,都会导致请求失败的。
中间件处理时机
一个中间件在处理请求之前或者之后进行任务处理,取决于中间件的代码逻辑放置的位置。 例如,下面的中间件在处理请求 “之前” 进行了一些任务操作:
而下面的中间件是在处理请求 “之后” 才进行的任务操作:
注册中间件
全局中间件
如果一个中间件在每次的 HTTP 请求时都要用到,那么把他列入 app/Http/Kernel.php
的 $middleware
数组中即可。列入 $middleware
数组中的中间件又称为全局中间件。
为路由使用中间件
使用在路由身上的中间件是在 app/Http/Kernel.php
中的 $routeMiddleware
数组属性中定义的。如果你创建了一个新的给路由使用的中间件,就需要将他添加到 $routeMiddleware
这个数组里,并给中间件一个 key ——相当于中间件的名字。
// Within App/Http/Kenerl class····
protected $routeMiddleware = [
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth:class,
'bindings' => \Illuminate\Auth\Middleware\SubstituteBinding:class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
];
使用中间件时,就用到了这个 Key:
Route::get('/', function(){
//
})->middleware('auth');
也可以在一个路由上使用多个中间件:
Route::get('/', function(){
//
})->middleware('first', 'second');
也可以使用中间件的包含命名空间的完整类名(又称完全限定类名)使用中间件:
use App\Http\Middleware\CheckAge;
Route::get('admin/profile', function(){
//
})->middleware(CheckAge::class);
中间件组
中间件组中包含多个中间件,但它的使用和中间件是完全一样的。中间件组在 app/Http/Kenerl.php
中的 $middlewareGroups
属性中定义,每个中间件组还有一个对应的 key。
Laravel 项目中预设并使用了两个中间件组:web
和 api
,它们分别用在了 routes/web.php
和 routes/api.php
上,前者是定义 Web 接口的地方,后者是定义 Api 接口的地方。这两个类型的接口,都有一些通用的中间件,所以放到一个组里进行管理。
/**
* The application's route middleware groups.
*
* @var array
*/
protected $middlewareGroup = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'throttle:60,1',
'auth:api',
],
];
已经说过中间件组和中间件的使用是完全一样的,不过带来一个好处——一次定义,即可使用多个中间件。中间件组可以分配给路由和控制器 action使用:
Route::get('/' , function(){
//
})->middleware('web');
Route::group(['middleware' => ['web']], function(){
//
});
注意,routes/web.php
文件里的中间件已将默认使用 web
中间件组,这是在 RouteServiceProvider
中定义的,在 routes/web.php
中定义路由时,时无需为路由额外分配 web
中间件组的。
中间件参数
可以为中间件传递参数。如果系统有一个需求——需要在验证角色之后才允许认证用户的进一步操作。以下以 CheckRole
中间件为例,它接受一个角色名作为参数。
中间件参数在 handle
方法的 $next
参数之后定义。
user()->hasRole($role)){
..Redirect
}
return $next($request);
}
}
使用中间件参数的方式是在中间件后面加上一个 :
,然后在 :
后面跟上你要传递给中间件的参数。
Route::put('post/{id}',function(){
//
})->middleware('role:editor');
在 Http 响应之后......
有时,中间件需要在 HTTP 响应请求之后才做处理工作。例如,包含在 Laravel 中的会话中间件会在响应发送到浏览器之后,再次保存会话数据。针对这样的需求,需要在中间件定义一个 terminate
方法,它会在响应发送到浏览器之后自动被调用。
terminate
方法接受两个参数,请求实例和响应实例。定义好之后,将它放在 app/Http/Kernel.php
添加在中间件列表中即可。
在调用 terminate
方法时,Laravel 是从服务容器中找到并创建一个新的中间件实例使用的,如果你只要使用同一个中间件实例,那么就要使用容器的 singleton
方法注册中间件了。