美文网首页
laravel 源码解析之 Pipeline

laravel 源码解析之 Pipeline

作者: 杰森跟班 | 来源:发表于2019-01-16 12:52 被阅读0次

    解析 Pipeline 之前,我觉得有必要好好看一下 这个 $request 是怎么生成的,底层用了 Symfony 的 request 类。

    $response = $kernel->handle(
        $request = Illuminate\Http\Request::capture()
    );
    

    首先进入 capture 方法

    public static function capture()
    {
        # 开启 http 方法重写,之前一篇文章已经讲到过了,你也可以看看它的注释,你会懂的
        static::enableHttpMethodParameterOverride();
    
        return static::createFromBase(SymfonyRequest::createFromGlobals());
    }
    

    tips: application/x-www-form-urlencoded: 数据被编码成以 '&' 分隔的键-值对, 同时以 '=' 分隔键和值. 非字母或数字的字符会被 percent-encoding: 这也就是为什么这种类型不支持二进制数据的原因 (应使用 multipart/form-data 代替).

    # parse_str 的使用
    <?php
    $str = "first=value&arr[]=foo+bar&arr[]=baz";
    
    // Recommended
    parse_str($str, $output);
    echo $output['first'];  // value
    echo $output['arr'][0]; // foo bar
    echo $output['arr'][1]; // baz
    
    public static function createFromGlobals()
    {
        # 拿到全局变量中的 $_GET, $_POST, $_COOKIE,$_FILES,$_SERVER
        $request = self::createRequestFromFactory($_GET, $_POST, array(), $_COOKIE, $_FILES, $_SERVER);
    
        # 前半句判断请求是否是form表单的形式发送的,后半句判断请求的方法是否是 put patch 或者 delete,因为php本身不支持 put 或者 delete 方法
        if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded')
            && \in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), array('PUT', 'DELETE', 'PATCH'))
           ) {
            # 如果是 application/x-www-form-urlencoded 形式的,那么需要解析参数
            parse_str($request->getContent(), $data);
            # 把初始化的 request 覆盖掉
            $request->request = new ParameterBag($data);
        }
    
        return $request;
    }
    
    private static function createRequestFromFactory(
        array $query = array(), 
        array $request = array(), 
        array $attributes = array(), 
        array $cookies = array(), 
        array $files = array(), 
        array $server = array(), 
        $content = null
    ) {
        # 如果已经有实例? $requestFactory ,这一步情景暂时看不出,但可以肯定的是一个新的请求,这个判断返回 false
        if (self::$requestFactory) {
            $request = \call_user_func(self::$requestFactory, $query, $request, $attributes, $cookies, $files, $server, $content);
    
            if (!$request instanceof self) {
                throw new \LogicException('The Request factory must return an instance of Symfony\Component\HttpFoundation\Request.');
            }
    
            return $request;
        }
    
        # 生成一个 request 实例
        return new static($query, $request, $attributes, $cookies, $files, $server, $content);
    }
    
    # Request 构造函数
    public function __construct(
        array $query = array(), 
        array $request = array(), 
        array $attributes = array(), 
        array $cookies = array(), 
        array $files = array(), 
        array $server = array(), 
        $content = null
    ) {
        $this->initialize($query, $request, $attributes, $cookies, $files, $server, $content);
    }
    

    初始化了相关类,这里把不同的属性不同的参数给到不同的类做了解耦,good!

    public function initialize(
        array $query = array(), 
        array $request = array(), 
        array $attributes = array(), 
        array $cookies = array(), 
        array $files = array(), 
        array $server = array(), 
        $content = null
    ) {
        $this->request = new ParameterBag($request);
        $this->query = new ParameterBag($query);
        $this->attributes = new ParameterBag($attributes);
        $this->cookies = new ParameterBag($cookies);
        $this->files = new FileBag($files);
        $this->server = new ServerBag($server);
        $this->headers = new HeaderBag($this->server->getHeaders());
    
        $this->content = $content;
        $this->languages = null;
        $this->charsets = null;
        $this->encodings = null;
        $this->acceptableContentTypes = null;
        $this->pathInfo = null;
        $this->requestUri = null;
        $this->baseUrl = null;
        $this->basePath = null;
        $this->method = null;
        $this->format = null;
    }
    
    # 注意这里传入的 $request 实例!这个实例是 SymfonyRequest 的实例,而第一句判断中的 static 是 Illuminate\Http\Request,所以是 false,laravel 框架还要对其进一步包装
    public static function createFromBase(SymfonyRequest $request)
    {
        if ($request instanceof static) {
            return $request;
        }
    
        $content = $request->content;
    
        # laravel clone 了一份 request
        $request = (new static)->duplicate(
            $request->query->all(), $request->request->all(), $request->attributes->all(),
            $request->cookies->all(), $request->files->all(), $request->server->all()
        );
    
        $request->content = $content;
        # 如果请求给的是json数据格式,那么将其转化为 ParameterBag 实例,因为 SymfonyRequest 对 json 请求格式没有做处理
        $request->request = $request->getInputSource();
    
        return $request;
    }
    
    Symfony 的 Request laravel 本身的 Request

    了解了 request 的实例过程之后,我们来看 Pipeline

    return (new Pipeline($this->app)) # 传入 app 实例(单例)
        ->send($request) # 传入 request,赋值给 Pipeline 的 passable 属性
        ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) # 传入中间件,这个判断是为了测试时候禁用中间件而写的,把 对应的中间件类传入,赋值给 Pipeline 的 pipes
        ->then($this->dispatchToRouter());
    
    public function then(Closure $destination)
    {
        # array_reduce 如果有三个参数,先看第三个,它初始化传入的值
        $pipeline = array_reduce(
            array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)
        );
    
        return $pipeline($this->passable);
    }
    
    
    # 第三个参数传入了dispatchToRouter这个闭包
    protected function dispatchToRouter()
    {
        return function ($request) {
            $this->app->instance('request', $request);
    
            return $this->router->dispatch($request);
        };
    }
    
    protected function carry()
    {
        # $stack -> 闭包,$pipe -> 在这里是中间件的类名
        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;
            };
        };
    }
    
    $pipeline = array_reduce(
        array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)
    );
    
    
    # 合并。最终这个 reduce 将形成一个大大的闭包函数,类似递归,一层嵌套一层
    array_reduce(
        array_reverse($this->pipes), 
        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;
            };
        }
        , function ($passable) use ($destination) {
            return function ($passable) {
                $this->app->instance('request', $passable);
    
                return $this->router->dispatch($passable);
            };
        };
    );
    

    源码不愧是源码,很难理解,那么我这里先来个简单的

    <?php
    # laravel pipeline 的简单实现,pipeline 主要用来对数据进行管道化处理,类似于拦截器,层层过滤数据
    # 请求 -> 管道1 —> 管道2 -> 管道3 -> 业务逻辑 -> 后置管道 -> 返回结果
    
     function runPipeline()
    {
        return $result = array_reduce(
            array_reverse([new Handler1, new Handler2, new Handler3, new Handler4]),
            function ($start, $handler) {
                return function () use ($start, $handler) {
                    $result =  $handler->hanlde($start);
    
                    return $result;
                };
        }, function () {
            echo "处理核心业务逻辑\n";
        });
    }
    
    $result = runPipeline();
    $result();
    
    /**
     * 前置管道1
     */
    class Handler1
    {
        function hanlde($callback)
        {
            echo '处理器1' . "\n";
    
            return $callback();
        }
    }
    
    /**
     * 前置管道2
     */
    class Handler2
    {
        function hanlde($callback)
        {
            echo '处理器2' . "\n";
    
            return $callback();
        }
    }
    
    /**
     * 后置管道3
     */
    class Handler3
    {
        function hanlde($callback)
        {
            $result = $callback();
    
            echo '处理器3' . "\n";
    
            return $result;
        }
    }
    
    /**
     * 前置管道4
     */
    class Handler4
    {
        function hanlde($callback)
        {
            echo '处理器4' . "\n";
    
            return $callback();
        }
    }
    
    

    为什么要 array_reverse

    # 管道最终的包装之后是这样的,正如 laravel 源码中的 $this->prepareDestination($destination) 是核心逻辑,然后它外面包的是一个有一个的管道,也就是中间件,最先进入的 最后执行,所以取反之后,最先进入的最先执行
    function duc() {
        $callback1 = function () {
            echo '处理器1' . "\n";
    
            $callback2 = function () {
                echo '处理器2' . "\n";
    
                $callback3 = function () {
                    echo "处理核心业务逻辑\n";
                };
    
                $callback3();
            };
    
            $callback2();
        };
    
        $callback1();
    }
    
    duc();
    

    前置和后置?

    前置和后置其实就是执行顺序问题,只要把上面的echo放到 callback 之后,就能实现后置

    function duc() {
        $callback1 = function () {
            echo '处理器1' . "\n";
    
            $callback2 = function () {
    
                $callback3 = function () {
                    echo "处理核心业务逻辑\n";
                };
    
                $callback3();
                
                echo '处理器2' . "\n";
            };
    
            $callback2();
        };
    
        $callback1();
    }
    
    duc();
    

    这个pipeline 在laravel 中中间件处理的时候得到了应用,去看文档你会发现中间件的前置和后置和本文的用法一样

    到这里你就应该知道中间件的用法了吧!我们继续看下去,由上文可知,$this->prepareDestination($destination) 才是核心逻辑实现的地方,下面贴代码

    # passable 是 request 实例,在 pipeline send 的时候传入的
    return function ($passable) {
        $this->app->instance('request', $passable);
    
        return $this->router->dispatch($passable);
    };
    
    public function dispatch(Request $request)
    {
        # 为 router  类添加了类属性
        $this->currentRequest = $request;
    
        # 分发路由
        return $this->dispatchToRoute($request);
    }
    
    public function dispatchToRoute(Request $request)
    {
        return $this->runRoute($request, $this->findRoute($request));
    }
    
    # 这一步是去从你的路由中寻找相应的控制器和方法
    # 你可以打印router类,至于框架什么时候解析了你的路由文件,我告诉你,它是在  bootstrap 阶段,运行 BootProviders 的bootstrap方法,调用所有 providers 的 boot 方法时加载的
    protected function findRoute($request)
    {
        $this->current = $route = $this->routes->match($request);
    
        # 框架还把其赋值给了 Route,也就是说,你在控制器中用 app(\Illuminate\Routing\Route::class) 拿到是是下图路由数组中的某一个路由对象,比如 / 你拿到的就是下图中 / 对应的键值,注意这里 app(\Illuminate\Routing\Route::class) !== app('route')
        $this->container->instance(Route::class, $route);
    
        return $route;
    }
    
    Router
    protected function runRoute(Request $request, Route $route)
    {
        $request->setRouteResolver(function () use ($route) {
            return $route;
        });
    
        # 触发事件,调用对应的 listener
        $this->events->dispatch(new Events\RouteMatched($route, $request));
    
        return $this->prepareResponse($request,
            $this->runRouteWithinStack($route, $request)
        );
    }
    
    protected function runRouteWithinStack(Route $route, Request $request)
    {
        $shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
            $this->container->make('middleware.disable') === true;
    
        $middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);
    
        # ???? wtf 这里怎么还有管道
        # 大家都懂的,核心业务代码就是 then 里面的部分
        return (new Pipeline($this->container))
            ->send($request)
            ->through($middleware)
            ->then(function ($request) use ($route) {
                return $this->prepareResponse(
                    $request, $route->run()
                );
            });
    }
    
    # run 执行控制器里面的方法
    public function run()
    {
        $this->container = $this->container ?: new Container;
    
        try {
            if ($this->isControllerAction()) {
                return $this->runController();
            }
    
            return $this->runCallable();
        } catch (HttpResponseException $e) {
            return $e->getResponse();
        }
    }
    
    # toResponse 转换为相应的返回格式
    public static function toResponse($request, $response)
    {
        if ($response instanceof Responsable) {
            $response = $response->toResponse($request);
        }
    
        if ($response instanceof PsrResponseInterface) {
            $response = (new HttpFoundationFactory)->createResponse($response);
        } elseif ($response instanceof Model && $response->wasRecentlyCreated) {
            $response = new JsonResponse($response, 201);
        } elseif (! $response instanceof SymfonyResponse &&
                  ($response instanceof Arrayable ||
                   $response instanceof Jsonable ||
                   $response instanceof ArrayObject ||
                   $response instanceof JsonSerializable ||
                   is_array($response))) {
            $response = new JsonResponse($response);
        } elseif (! $response instanceof SymfonyResponse) {
            $response = new Response($response);
        }
    
        if ($response->getStatusCode() === Response::HTTP_NOT_MODIFIED) {
            $response->setNotModified();
        }
    
        return $response->prepare($request);
    }
    

    再回过头来看 handle 方法

    public function handle($request)
    {
        try {
            $request->enableHttpMethodParameterOverride();
    
            # 这里的种种操作最后返回的是 Symfony\Component\HttpFoundation\Response 实例
            $response = $this->sendRequestThroughRouter($request);
        } catch (Exception $e) {
            $this->reportException($e);
    
            $response = $this->renderException($request, $e);
        } catch (Throwable $e) {
            $this->reportException($e = new FatalThrowableError($e));
    
            $response = $this->renderException($request, $e);
        }
    
        # 请求结束还触发了事件
        $this->app['events']->dispatch(
            new Events\RequestHandled($request, $response)
        );
    
        return $response;
    }
    
    $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
    
    $response = $kernel->handle(
        $request = Illuminate\Http\Request::capture()
    );
    # 最后把相应返回给客户端
    $response->send();
    
    # 处理中间件中的 terminate 方法
    $kernel->terminate($request, $response);
    
    /**
    * 
    * Sends HTTP headers and content.
    *
    * @return $this
    */
    public function send()
    {
        $this->sendHeaders();
        $this->sendContent();
    
        if (\function_exists('fastcgi_finish_request')) {
            fastcgi_finish_request();
        } elseif (!\in_array(\PHP_SAPI, array('cli', 'phpdbg'), true)) {
            static::closeOutputBuffers(0, true);
        }
    
        return $this;
    }
    

    再多讲一句

    # 这个阶段你再dd或者dump是不会有任何输出的,因为服务端已经返回了
    $kernel->terminate($request, $response);
    
    # 比如 session 的保存和关闭处理
    public function terminate($request, $response)
    {
        if ($this->sessionHandled && $this->sessionConfigured() && ! $this->usingCookieSessions()) {
            $this->manager->driver()->save();
        }
    }
    

    相关文章

      网友评论

          本文标题:laravel 源码解析之 Pipeline

          本文链接:https://www.haomeiwen.com/subject/mrfmdqtx.html