【Laravel-海贼王系列】第七章,Pipeline 类解析
Pipeline
Laravel 的中间件是通过管道类来实现的。
通过内核处理请求的过程中管道的作用来解析管道类!
protected function sendRequestThroughRouter($request){$this->app->instance('request', $request);Facade::clearResolvedInstance('request');$this->bootstrap();return (new Pipeline($this->app)) //这是个 Illuminate\Routing\Pipeline 对象,继承了 Illuminate\Pipeline\Pipeline 对象。->send($request) // 调用 Illuminate\Pipeline\Pipeline 的 send() 方法传入 $request 对象->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) // 传入需要经过的中间件数组->then($this->dispatchToRouter());// 传入最后执行的闭包并且运行管道} 复制代码接下来我们看看这段代码是如何让请求通过所有的中间件之后返回的。
代码调用追踪
约定 (new Pipeline($this->app)) 下面统称 $pipe
$pipe->send($request) // 将 $request 对象赋值给 $pipe->passable
$pipe->pipes 的赋值
4.$pipe->then($this->dispatchToRouter()); 这里是执行父类的 then() 方法
public function then(Closure $destination){$pipeline = array_reduce(array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination));return $pipeline($this->passable);} 复制代码• array_reverse($this->pipes),就是将刚才存入的中间件顺序反转。
• $this->carry() 这里的 $this 指向的对象是 Illuminate\Routing\Pipeline 对象因此调用 carry() 方法是自身的。
• $this->prepareDestination($destination) 返回一个闭包
return function ($passable) use ($destination) {return $destination($passable);}; 复制代码接着开始看
$pipeline = array_reduce(array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)); 复制代码这段代码可以改造成容易读的方式
$cb = $this->carry();$stack = $this->prepareDestination($destination);foreach (array_reverse($this->pipes) as $pipe) {$stack = $cb($stack,$pipe);}$pipeline = $stack; 复制代码先获取一个闭包,然后获取第一个闭包参数 $stack ,之后遍历 pipes 数组来进行迭代,每次迭代会更新下次迭代的 $stack 变量,等迭代完成之后将 $stack 赋值给 $pipeline.
所以我们只要关心最后 $pipeline 拿到的是一个什么东西 那么这里就要解析 $this->carry() 每次执行之后返回的是什么,下面是执行调用的方法。
protected function carry(){return function ($stack, $pipe) {return function ($passable) use ($stack, $pipe) {try {$slice = parent::carry();$callable = $slice($stack, $pipe);return $callable($passable);} catch (Exception $e) {return $this->handleException($passable, $e);} catch (Throwable $e) {return $this->handleException($passable, new FatalThrowableError($e));}};};} 复制代码这里其实每次执行返回的就是个新闭包,同时 $stack,$pipe 的值也随着调用存入闭包。为了方便我声明下变量
$cb = function ($passable) use ($stack, $pipe) {try {$slice = parent::carry();$callable = $slice($stack, $pipe);return $callable($passable);} catch (Exception $e) {return $this->handleException($passable, $e);} catch (Throwable $e) {return $this->handleException($passable, new FatalThrowableError($e));}}; 复制代码所以上面 $cb 的值就是 $this->carry() 执行后返回的闭包就像洋葱一样,我们来看封装过程。
第一次封装 $stack1 = $cb($passable) use ($this->prepareDestination($destination),'App\Http\Middleware\TrustProxies') 第二次封装 $stack2 = $cb($passable) use ($stack1,'Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull') 第三次封装 $stack3 = $cb($passable) use ($stack2,'App\Http\Middleware\TrimStrings') 第四次封装 $stack4 = $cb($passable) use ($stack3,'Illuminate\Foundation\Http\Middleware\ValidatePostSize') 第五次封装 $stack5 = $cb($passable) use ($stack4,'App\Http\Middleware\CheckForMaintenanceMode') 复制代码最后 $pipeline 对象实际就是 $stack5。
看到这里我们获取了一个层层封装的闭包,同时我们也看出为什么中间件的顺序先反转了,因为执行的时候是从 $stack5 开始的!那么下一步就是看看如何执行了。
return $pipeline($this->passable); 复制代码在递归完成之后我们获得了一个 $pipeline 对象, 此时我们触发这个闭包,后面就是连锁反应!这里我用 $stack5 来代替 $pipeline 方便理解。 首先执行
$stack5($this->passable,'App\Http\Middleware\CheckForMaintenanceMode') 复制代码这段代码是一个起点,也就是点燃整个连锁反应的开始,我们来追踪下去会回到 $cb 这个闭包的逻辑,
$cb = function ($passable) use ($stack, $pipe) {try {$slice = parent::carry();$callable = $slice($stack, $pipe);return $callable($passable);} catch (Exception $e) {return $this->handleException($passable, $e);} catch (Throwable $e) {return $this->handleException($passable, new FatalThrowableError($e));}}; 复制代码这里最终还是调用了 parent::carry(), 执行到了最里层的函数。
protected function carry(){return function ($stack, $pipe) {return function ($passable) use ($stack, $pipe) {if (is_callable($pipe)) {return $pipe($passable, $stack);} elseif (!is_object($pipe)) {[$name, $parameters] = $this->parsePipeString($pipe);$pipe = $this->getContainer()->make($name);$parameters = array_merge([$passable, $stack], $parameters);} else {$parameters = [$passable, $stack];}$response = method_exists($pipe, $this->method)? $pipe->{$this->method}(...$parameters): $pipe(...$parameters);return $response instanceof Responsable? $response->toResponse($this->container->make(Request::class)): $response;};};} 复制代码到这里我们已经进入最后的堡垒,由于传入的 $pipe 是中间件的名称,不是闭包所以进入 elseif 中开始执行。 第一次执行:
$stack5 = $cb($passable) use ($stack4,'App\Http\Middleware\CheckForMaintenanceMode') 复制代码function ($passable) use ($stack, $pipe) {if (is_callable($pipe)) {return $pipe($passable, $stack);} elseif (!is_object($pipe)) {// 进入这里开始执行[$name, $parameters] = $this->parsePipeString($pipe);$pipe = $this->getContainer()->make($name); // 从通过Application对象从容器中生产对应的类,这里不拓展了,就是应用了容器的特性来生产类。$parameters = array_merge([$passable, $stack], $parameters); // 这里非常重要,将 $passable (就是开始的 $request 对象) 和 $stack (就是最近一次调用的$stack4) 合并成数组} else {$parameters = [$passable, $stack];}$response = method_exists($pipe, $this->method)? $pipe->{$this->method}(...$parameters): $pipe(...$parameters); // 调用中间件中$pipe->handle($request, $stack4)return $response instanceof Responsable? $response->toResponse($this->container->make(Request::class)): $response;}; 复制代码分析完上面并没有完成,最后代码运行到
$this->method = 'handle'; 默认配置,可以通过 $this->via($method) 来修改。$response = method_exists($pipe, $this->method)? $pipe->{$this->method}(...$parameters): $pipe(...$parameters); // ...$parameters 解构数组参数实际调用 $pipe->handle($request, $stack4) 复制代码此时只是调用一次闭包,那么之前封装了那么多层都怎么办呢?
接下来我们分析 CheckForMaintenanceMode 中间件的 handle($request, Closure $next) 方法。
public function handle($request, Closure $next){if ($this->app->isDownForMaintenance()) {$data = json_decode(file_get_contents($this->app->storagePath().'/framework/down'), true);if (isset($data['allowed']) && IpUtils::checkIp($request->ip(), (array) $data['allowed'])) {return $next($request);}if ($this->inExceptArray($request)) {return $next($request);}throw new MaintenanceModeException($data['time'], $data['retry'], $data['message']);}return $next($request);} 复制代码return $next($request); 这句话点亮了一切
实际调用了 $stack4($request) , 我们来看看当时 $stack4 这个闭包里面是啥
$stack4 = $cb($passable) use ($stack3,'Illuminate\Foundation\Http\Middleware\ValidatePostSize') 复制代码是不是和 $stack5 有点像, 直到这里形成了递归, 同时解答了为什么中间件的格式要按照文档上面说用。
回到最初的封装
第一次封装 $stack1 = $cb($passable) use ($this->prepareDestination($destination),'App\Http\Middleware\TrustProxies') 第二次封装 $stack2 = $cb($passable) use ($stack1,'Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull') 第三次封装 $stack3 = $cb($passable) use ($stack2,'App\Http\Middleware\TrimStrings') 第四次封装 $stack4 = $cb($passable) use ($stack3,'Illuminate\Foundation\Http\Middleware\ValidatePostSize') 第五次封装 $stack5 = $cb($passable) use ($stack4,'App\Http\Middleware\CheckForMaintenanceMode') 复制代码我们的调用链就变成了
$stack5 = $cb($passable) use ($stack4,'App\Http\Middleware\CheckForMaintenanceMode') $stack4 = $cb($passable) use ($stack3,'Illuminate\Foundation\Http\Middleware\ValidatePostSize') $stack3 = $cb($passable) use ($stack2,'App\Http\Middleware\TrimStrings') $stack2 = $cb($passable) use ($stack1,'Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull') $stack1 = $cb($passable) use ($this->prepareDestination($destination),'App\Http\Middleware\TrustProxies') 复制代码最后执行
$this->prepareDestination($destination)$destination = $this->dispatchToRouter();return function ($passable) use ($destination) {return $destination($passable); }; // 返回一个 $response 对象 ...复制代码到这里管道的核心代码就结束了,当然是通过在内核启动周期中 关于请求发送到路由获取响应这个实例来解析。
laravel 中路由对系统的管道做了细微的拓展,整体还是没啥变化,就是闭包套闭包,不停地调用,就像剥洋葱。
总结
以上是生活随笔为你收集整理的【Laravel-海贼王系列】第七章,Pipeline 类解析的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: 你知道前端单页面路由是怎么实现的吗?
- 下一篇: https的那些事儿