【Laravel-海贼王系列】第七章,Pipeline 类解析

Pipeline (管道)

Laravel 的中间件是经过管道类来实现的。laravel

经过内核处理请求的过程当中管道的做用来解析管道类!json

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());// "传入最后执行的闭包而且运行管道"
}
复制代码

接下来咱们看看这段代码是如何让请求经过全部的中间件以后返回的。bootstrap

代码调用追踪

  1. 约定 (new Pipeline($this->app)) 下面统称 $pipe数组

  2. $pipe->send($request) // 将 $request 对象赋值给 $pipe->passablebash

  3. $pipe->pipes 的赋值闭包

array:5 [▼
    0 => "App\Http\Middleware\CheckForMaintenanceMode"
    1 => "Illuminate\Foundation\Http\Middleware\ValidatePostSize"
    2 => "App\Http\Middleware\TrimStrings"
    3 => "Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull"
    4 => "App\Http\Middleware\TrustProxies"
]
$pipe->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
复制代码

4.$pipe->then($this->dispatchToRouter()); 这里是执行父类的 then() 方法app

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() 方法是自身的。ui

$this->prepareDestination($destination) 返回一个闭包this

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);
        
        // "从经过Application对象从容器中生产对应的类, 这里不拓展了,就是应用了容器的特性来生产类。"
        $pipe = $this->getContainer()->make($name); 
        
        // "这里很是重要,将 $passable (就是开始的 $request 对象) 和 $stack (就是最近一次调用的$stack4) 合并成数组"
        $parameters = array_merge([$passable, $stack], $parameters);
    } else {
        $parameters = [$passable, $stack];
    }
    
    // "调用中间件中$pipe->handle($request, $stack4)"
    $response = method_exists($pipe, $this->method)
        ? $pipe->{$this->method}(...$parameters)
        : $pipe(...$parameters);

    return $response instanceof Responsable
        ? $response->toResponse($this->container->make(Request::class))
        : $response;
};
复制代码

分析完上面并无完成,最后代码运行到

// "默认配置,能够经过 $this->via($method) 来修改。"
$this->method = 'handle';

 // "...$parameters 解构数组参数实际调用 $pipe->handle($request, $stack4)"
$response = method_exists($pipe, $this->method)
            ? $pipe->{$this->method}(...$parameters)
            : $pipe(...$parameters);
复制代码

此时只是调用一次闭包,那么以前封装了那么多层都怎么办呢?

接下来咱们分析 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();

// "返回一个 $response 对象 ..."
return function ($passable) use ($destination) {
    return $destination($passable);
}; 
    
复制代码

总结

到这里管道的核心代码就结束了,

固然是经过在内核启动周期中

关于请求发送到路由获取响应这个实例来解析。

laravel 中路由对系统的管道作了细微的拓展,

总体仍是没啥变化,就是闭包套闭包,不停地调用,就像剥洋葱。

相关文章
相关标签/搜索