Laravel 中间件实现原理是什么? ## Laravel 中间件介绍 中间件提供了一种方便的机制过滤进入应用程序的 HTTP 请求。例如,Laravel 包含一个中间件,验证您的应用程序的用户身份验证。如果用户未被认证,中间件会将用户重定向到登录界面。然而,如果用户通过身份验证,中间件将进一步允许请求到应用程序中。 当然,除了身份认证以外,还可以编写另外的中间件来执行各种任务。例如:CORS 中间件可以负责为所有离开应用的响应添加合适的头部信息;日志中间件可以记录所有传入应用的请求。 Laravel 自带了一些中间件,包括身份验证、CSRF 保护等。所有这些中间件都位于 app/Http/Middleware 目录。 ## Laravel 中间件示例 创建三个中间件 PipelineOne, PipelineTwo, PipelineThree ```shell php artisan make:middleware PipelineOne php artisan make:middleware PipelineTwo php artisan make:middleware PipelineThree ``` 内容分别是 ```php <?php namespace App\Http\Middleware; use Closure; class PipelineOne { // 过滤60岁以下人员 public function handle($request, Closure $next) { $age = $request->input('age'); if ($age < 60) { return response('60岁以下不宜'); } return $next($request); } } ``` ```php <?php namespace App\Http\Middleware; use Closure; class PipelineTwo { // 过滤30岁以下人员 public function handle($request, Closure $next) { $age = $request->input('age'); if ($age < 30) { return response('30岁以下不宜'); } return $next($request); } } ``` ```php <?php namespace App\Http\Middleware; use Closure; class PipelineThree { // 过滤18岁以下人员 public function handle($request, Closure $next) { $age = $request->input('age'); if ($age < 18) { return response('18岁以下不宜'); } return $next($request); } } ``` 中间件使用 ```php <?php namespace App\Http\Controllers; use App\Http\Middleware\PipelineOne; use App\Http\Middleware\PipelineTwo; use App\Http\Middleware\PipelineThree; use Illuminate\Http\Request; class PipelineController extends Controller { public function __construct() { $this->middleware([ PipelineOne::class, PipelineTwo::class, PipelineThree::class, ]); } public function index(Request $request) { $age = $request->input('age'); return "你当前{$age}岁, 很高兴为你服务"; } } ``` 访问地址: http://localhost:8000/pipeline/index?age=26 输出结果: 60岁以下不宜 如果把 PipelineOne, PipelineTwo 移除掉 访问地址: http://localhost:8000/pipeline/index?age=26 输出结果: 你当前26岁, 很高兴为你服务 手动狗头, 可以看到 Laravel 中间件就是用来过滤 Request 请求的, 过滤规则可以在 `handle()` 内自定义 如果 $request 有幸在中间件的 `handle()` 中没有被 return , 将继续被下一个 Closure 对象当做参数继续执行, 直到 $request 被传送到 Route 的 `run()` 方法中执行直到结束 ## 中间件的原理 好, 我们看看 Laravel 源码 $request 被传送 Route 的 `run()` 方法之前都经历了什么? 我们看到 `$kernel->handle()` 方法内有关键方法 `$response = $this->sendRequestThroughRouter($request)` $request 对象通过 new Pipeline() 管道对象传送到指定的路由中, 经过了 `$this->middleware` 应用中设置的中间件列表 ```php protected function sendRequestThroughRouter($request) { $this->app->instance('request', $request); Facade::clearResolvedInstance('request'); $this->bootstrap(); return (new Pipeline($this->app)) ->send($request) ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) ->then($this->dispatchToRouter()); } ``` 可见实现中间件的关键应该是 Pipeline 了, Pipeline 是什么? 答: 顾名思义是管道, 把管道想象成漏斗, 这个漏斗有筛选规则, 通过规则验证的东西才能从管道里面穿过去 再看看 Pipeline 提供的这几个方法 - send() 把什么东西放进管道 - through() 要经过哪些筛选规则 - then() 开始经历筛选, 最后到达哪个 Closure 闭包函数中执行, 并返回结果 通过查看 `Illuminate\Pipeline\Pipeline` 源码, 了解到 `send()` 和 `through()` 只设置了 `$passable` 和 `$pipes` 属性 重点在于 `then()` 方法, 我们进一步探索 ```php public function then(Closure $destination) { $pipeline = array_reduce( array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination) ); return $pipeline($this->passable); } protected function carry() { return function ($stack, $pipe) { return function ($passable) use ($stack, $pipe) { if (is_callable($pipe)) { // If the pipe is an instance of a Closure, we will just call it directly but // otherwise we'll resolve the pipes out of the container and call it with // the appropriate method and arguments, returning the results back out. return $pipe($passable, $stack); } elseif (! is_object($pipe)) { [$name, $parameters] = $this->parsePipeString($pipe); // If the pipe is a string we will parse the string and resolve the class out // of the dependency injection container. We can then build a callable and // execute the pipe function giving in the parameters that are required. $pipe = $this->getContainer()->make($name); $parameters = array_merge([$passable, $stack], $parameters); } else { // If the pipe is already an object we'll just make a callable and pass it to // the pipe as-is. There is no need to do any extra parsing and formatting // since the object we're given was already a fully instantiated object. $parameters = [$passable, $stack]; } $response = method_exists($pipe, $this->method) ? $pipe->{$this->method}(...$parameters) : $pipe(...$parameters); return $response instanceof Responsable ? $response->toResponse($this->getContainer()->make(Request::class)) : $response; }; }; } // 准备目的地 (闭包外面加个闭包) protected function prepareDestination(Closure $destination) { return function ($passable) use ($destination) { return $destination($passable); }; } ``` 技术点 - array_reduce() 函数用回调函数迭代地将数组简化为单一的值 - array_reverse() 反转数组, 因为下一步要嵌套闭包, 利用堆栈的先进后出机制需要反转规则数组, 让后验证的规则先进去 - $this->carry() 巧用 array_reduce() 的迭代功能, 将多个筛选规则用闭包函数嵌套起来 - $this->prepareDestination() 将目的 Closure 再用一个闭包函数包裹起来 一顿操作猛如虎之后, 大概是这个样子 ```php protected function sendRequestThroughRouter($request) { $this->app->instance('request', $request); Facade::clearResolvedInstance('request'); $this->bootstrap(); return function() use ($request) { // PipelineOne 筛选规则 return function() use ($request) { // PipelineTwo 筛选规则 return function() use ($request) { // PipelineThree 筛选规则 // 传送到的最终执行的路由 return $this->dispatchToRouter(); }; }; }; } ``` 到此我们应该了解了 Laravel 中间件是怎么运作的, 我们也大概认识到了 Pipeline 管道的使用, 他帮助 Laravel 实现了中间件功能 Pipeline 在很多地方还会用到, 例如 消息队列, Redis发布订阅 等等 后期追加更多 Pipeline 的使用场景