laravel 源码解析之 Pipeline

解析 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 的使用
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;
}
laravel 源码解析之 Pipeline_第1张图片
Symfony 的 Request
laravel 源码解析之 Pipeline_第2张图片
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);
        };
    };
);

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

 管道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;
}
laravel 源码解析之 Pipeline_第3张图片
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)