简介
中间件提供了一中方便的机制来过滤进入应用的 HTTP 请求。例如,Laravel 包含一个可以验证当前用户使用以及认证的路由。如果未认证,中间件会重定向用户到登录页。然而,如果用户以及认证,中间件则会允许请求进入应用的下一步操作。
当然,除了用户认证外,中间件还可用来处理其他多种任务。例如,CORS 中间件可以用于为离开站点的响应添加合适的头。日志中间件可用户记录所有进入应用的请求。
Laravel 框架包含了一些中间件,包括用户认证及 CSRF 保护的中间件。所有的这些中间件都位于 app/Http/Middleware
目录下。
定义中间件
要创建一个新的中间件,可以使用 make:middleware
Artisan 命令:
php artisan make:middleware CheckAge
这个命令会在 app/Http/Middleware
目录下生成一个 CheckAge
类。在这个中间件中,我们只允许给定的 age
是大于 200 的访问路由。否则,我们会重定向用户到 home
URI:
<?php namespace App\Http\Middleware; use Closure; class CheckAge { /** * 运行请求过滤 * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { if ($request->age <= 200) { return redirect('home'); } return $next($request); } }
你可以看到,如果 age
小于等于 200,中间件会返回一个重定向 HTTP 到客户端,否则,请求进入应用的下一步操作。要传递请求给应用的下一层(允许中间件“通过”),可以调用 $next
毁掉函数并附带 $request
作为参数。
你可以把中间件当作 HTTP 请求进入应用前必须通过的一系列“层”。每一层都可以检查层,甚至可以完全拒绝它。
中间件处理前后
中间件在请求之前还是之后允许取决于中间件本身。例如,下面的中间件会在请求被应用处理器执行一些任务:
<?php namespace App\Http\Middleware; use Closure; class BeforeMiddleware { public function handle($request, Closure $next) { // 做一些操作 return $next($request); } }
然而,下面的中间件会在请求被应用处理后执行一些任务:
<?php namespace App\Http\Middleware; use Closure; class AfterMiddleware { public function handle($request, Closure $next) { $response = $next($request); // 做一些操作 return $response; } }
注册中间件
全局注册
如果你希望进入应用的每一个请求都运行中间件,只需把该中间件类添加到 app/Http/Kernel.php
类的 $middleware
属性中。
为路由分配中间件
如果你想为指定的路由分配中间件,你首先需要在 app/Http/Kernel.php
文件中为路由分配一个键。默认情况下,这个类的 $routeMiddleware
属性包含了 Laravel 的入口中间件。要新增自己的,只需把它添加到这个列表中并分配给你选定的键。例如:
// 在 App\Http\Kernel 类中 protected $routeMiddleware = [ 'auth' => \Illuminate\Auth\Middleware\Authenticate::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, 'can' => \Illuminate\Auth\Middleware\Authorize::class, 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, ];
当中间件在 HTTP 内核中定义后,你可以使用 middleware
方法为路由分配中间件:
Route::get('admin/profile', function () { // })->middleware('auth');
你还可以为路由分配多个中间件:
Route::get('/', function () { // })->middleware('first', 'second');
当分配中间件时,你还可以传递完整的类名:
use App\Http\Middleware\CheckAge; Route::get('admin/profile', function () { // })->middleware(CheckAge::class);
中间件组
有时候你可以想把多个中间件放在一个组中并指定一个键名,以便更简单的分配给路由,你可以在 HTTP 内核的 $middlewareGroups
属性中实现。
默认情况下,Laravel 提供了 web
和 api
两个中间件组,包含了你可以希望分配给 UI 及 API 路由的通用中间件:
/** * 应用的路由中间件组 * * @var array */ protected $middlewareGroups = [ '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', ], ];
中间件组可以作为独立的中间件,使用相同的语法分配给路由及控制器方法。再次强调,中间件组只是为了方便一次给路由分配多个中间件:
Route::get('/', function () { // })->middleware('web'); Route::group(['middleware' => ['web']], function () { // });
中间件参数
中间件也可以接收额外的参数。例如,如果你的应用需要在执行给定操作前检查认证的用户是否具有给定的“角色”,你可以创建一个 CheckRole
中间件并接收角色名作为额外的参数。
额外的中间件参数会位于 $next
参数后传递给中间件:
<?php namespace App\Http\Middleware; use Closure; class CheckRole { /** * 处理进入的请求 * * @param \Illuminate\Http\Request $request * @param \Closure $next * @param string $role * @return mixed */ public function handle($request, Closure $next, $role) { if (! $request->user()->hasRole($role)) { // Redirect... } return $next($request); } }
在定义路由时,可以使用 :
来分隔中间件的路由名与参数名,并指派给路由。多个参数需要由逗号来分隔:
Route::put('post/{id}', function ($id) { // })->middleware('role:editor');
有期限的中间件
有时候,中间件可能需要在 HTTP 响应发送给浏览器后做一些工作。例如,Laravel 提供的 session 中间件需要在响应发送给浏览器后存储 session 数据。你可以在你的中间件中定义一个 terminate
方法,它会在响应发送给浏览器后自动调用:
<?php namespace Illuminate\Session\Middleware; use Closure; class StartSession { public function handle($request, Closure $next) { return $next($request); } public function terminate($request, $response) { // 存储 session 数据 } }
terminate
方法需要接收请求与响应。一旦你定义了 terminate
方法,就需要你把它添加到 HTTP 内核的全局中间件列表中。
当调用中间件的 terminate
方法时,Laravel 会从服务容器中获取一个新的该中间件的实例。如果你想在 handle
以及 terminate
方法中使用相同的中间件实例,使用服务容器的 singleton
方法在容器中注册它。
该篇属于专题:《Laravel 5.3 中文文档》