生命周期 8--request 对象是如何通过路由&控制器中间件的

前言

上一节已经分析了《如何根据route对象的信息获取路由和控制器中间件》,本文分析一下「request对象是如何通过路由中间件的」

代码位置

在route对象的runRouteWithinStack方法中,\Illuminate\Routing\Router::runRouteWithinStack

    /**
     * Run the given route within a Stack "onion" instance.
     *
     * @param  \Illuminate\Routing\Route  $route
     * @param  \Illuminate\Http\Request  $request
     * @return mixed
     */
    protected function runRouteWithinStack(Route $route, Request $request)
    {
        $shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
                                $this->container->make('middleware.disable') === true;

        //前面已经获取好的路由、控制器中间件
        $middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);

        return (new Pipeline($this->container))
                        ->send($request)
                        ->through($middleware)
                        ->then(function ($request) use ($route) {
                            return $this->prepareResponse(
                                $request, $route->run()
                            );
                        });
    }

要分析的代码

(new Pipeline($this->container))
            ->send($request)
            ->through($middleware)
            ->then(function ($request) use ($route) {
                return $this->prepareResponse(
                    $request, $route->run()
                );
            });

这种骚操作已经不是头一次见了,早在《管道流分析》中,就已经见识了。
看看是不是几乎一样的?

return (new Pipeline($this->app))
            ->send($request)
            ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
            ->then($this->dispatchToRouter());

不同的是:前面的中间件是全局中间件,而当前的中间件是路由、控制器的中间件。然后前面的最终操作是发送request到路由,目前的最终操作是执行路由的操作并返回最终的response对象。
由于篇幅限制,本文将集中在request对象如何穿过路由&控制器中间件。
再看看将要穿过哪些中间件:

  array (
    0 => 'App\\Http\\Middleware\\EncryptCookies',
    1 => 'Illuminate\\Routing\\Middleware\\SubstituteBindings',
    2 => 'Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse',
    3 => 'Illuminate\\Session\\Middleware\\StartSession',
    4 => 'Illuminate\\View\\Middleware\\ShareErrorsFromSession',
    5 => 'App\\Http\\Middleware\\VerifyCsrfToken',
    6 => 'App\\Http\\Middleware\\RecordLastActivedTime',
    7 => 'App\\Http\\Middleware\\RedirectIfAuthenticated',
  )

具体分析

迭代创建超级大闭包

迭代开始前,指定目标,此为$destination

function ($request) use ($route) {
    return $this->prepareResponse($request, $route->run())

$this->prepareDestination($destination)就返回闭包0(这是为了讲述方便自己设定的)

    /**
     * Get the final piece of the Closure onion.
     *
     * @param  \Closure  $destination
     * @return \Closure
     */
    protected function prepareDestination(Closure $destination)
    {
        return function ($passable) use ($destination) {
            return $destination($passable);
        };
    }

因此,闭包0是一个双层闭包,可以看成是将要执行$this->prepareResponse($request, $route->run())

第1次迭代:将返回一个闭包对象1(简称闭包1,下同)。如下:

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为闭包0。
$stack = function ($passable) use ($destination) {
            return $destination($passable);
        };
$pipe = "App\Http\Middleware\RedirectIfAuthenticated";

第2次迭代:将返回一个闭包2。如下:

function ($passable) use ($stack, $pipe) {
    //omit for clarity.
};
//其中$stack为闭包1,闭包1的$stack是闭包0.

第3次迭代:将返回一个闭包3。如下:

function ($passable) use ($stack, $pipe) {
    //omit for clarity.
};
//其中$stack为闭包2,闭包2中的$stack又是闭包1,闭包1的$stack是闭包0.

第4次迭代:将返回一个闭包4。如下:

function ($passable) use ($stack, $pipe) {
    //omit for clarity.
};
//其中$stack为闭包3,闭包3中的$stack又是闭包2,闭包2中的$stack又是闭包1,闭包1的$stack是闭包0.

依次类推。。。

第n次迭代:将返回一个闭包n。如下:

function ($passable) use ($stack, $pipe) {
    //omit for clarity.
};
//其中$stack为闭包n,闭包n中的$stack又是闭包n-1......闭包1的$stack是闭包0.

最后,$pipeline就是一个闭包,它的$stack就是闭包n.

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));
    }
};

穿过中间件

简要过程

执行$pipeline($this->passable)操作,会先执行$slice = parent::carry();,此操作返回一个方法签名为$stack, $pipe闭包A,赋给变量$slice

然后执行$callable = $slice($stack, $pipe);,在这里,将上面的闭包n传入$slice,执行它,而它仍然返回一个闭包B,方法签名为$passable,且use了$stack, $pipe。返回后赋值给
变量$callable

$callable($passable),就是去执行闭包B。在这个过程中,$stack闭包n。而这个$stack会传入第一个$pipehandle方法$next参数中,因此在执行完handle方法中的处理$request的代码后,$next(...)就是调用闭包n。

而闭包n的代码结构跟$pipeline闭包的代码结构是完全一致的,唯一的不同就是$stack, $pipe的不同: 闭包n中的$stack是闭包n-1,这个前面已经提到过了。

因此,在下一次handle方法中,$next就是闭包n-1, $next(...)就是调用闭包n-1。以此类推,这样就不断的调用嵌套的闭包,直到最里面的闭包0。而闭包0是一个双层闭包,可以看成是将要执行$this->prepareResponse($request, $route->run())

详细过程

首先穿过的是App\Http\Middleware\EncryptCookies中间件,其handle方法是:

    public function handle($request, Closure $next)
    {
        //先将request中的cookie解密,然后进入下一个中间件
        //然后后面返回的结果再加密,返回给response
        return $this->encrypt($next($this->decrypt($request)));
    }

注意:这里只解密,然后就进入下一个中间件了,还没有返回,因此暂时不加密

然后是Illuminate\Session\Middleware\StartSession中间件:

    public function handle($request, Closure $next)
    {
        $this->sessionHandled = true;

        // If a session driver has been configured, we will need to start the session here
        // so that the data is ready for an application. Note that the Laravel sessions
        // do not make use of PHP "native" sessions in any way since they are crappy.
        if ($this->sessionConfigured()) {
            $request->setLaravelSession(
                $session = $this->startSession($request)
            );

            $this->collectGarbage($session);
        }

        $response = $next($request);

        // Again, if the session has been configured we will need to close out the session
        // so that the attributes may be persisted to some storage medium. We will also
        // add the session identifier cookie to the application response headers now.
        if ($this->sessionConfigured()) {
            $this->storeCurrentUrl($request, $session);

            $this->addCookieToResponse($response, $session);
        }

        return $response;
    }

可以看到这里,这里也是next后还有其他操作,这种叫做后置中间件,官网有提到。

然后穿过剩下的中间件,到达目的地,准备执行router对象的prepareResponse方法:

return $this->prepareResponse(
    $request, $route->run()
);

router对象的prepareResponse方法如下:

    /**
     * Create a response instance from the given value.
     *
     * @param  \Symfony\Component\HttpFoundation\Request  $request
     * @param  mixed  $response
     * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse
     */
    public function prepareResponse($request, $response)
    {
        return static::toResponse($request, $response);
    }

传入当前的request对象和执行当前route对象的run方法后的结果,最后返回一个response对象。

堆栈视角

从IDE的堆栈中看得更清楚,穿过顺序从下往上,可以看到到底经过哪些中间件,找handle()就可以了。

Route.php:165, Illuminate\Routing\Route->run()
Router.php:679, Illuminate\Routing\Router->Illuminate\Routing\{closure}()
Pipeline.php:30, Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}()
RedirectIfAuthenticated.php:24, App\Http\Middleware\RedirectIfAuthenticated->handle()
Pipeline.php:151, Illuminate\Routing\Pipeline->Illuminate\Pipeline\{closure}()
Pipeline.php:54, Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}()
RecordLastActivedTime.php:24, App\Http\Middleware\RecordLastActivedTime->handle()
Pipeline.php:151, Illuminate\Routing\Pipeline->Illuminate\Pipeline\{closure}()
Pipeline.php:54, Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}()
VerifyCsrfToken.php:75, App\Http\Middleware\VerifyCsrfToken->handle()
Pipeline.php:151, Illuminate\Routing\Pipeline->Illuminate\Pipeline\{closure}()
Pipeline.php:54, Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}()
AddQueuedCookiesToResponse.php:37, Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse->handle()
Pipeline.php:151, Illuminate\Routing\Pipeline->Illuminate\Pipeline\{closure}()
Pipeline.php:54, Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}()
SubstituteBindings.php:41, Illuminate\Routing\Middleware\SubstituteBindings->handle()
Pipeline.php:151, Illuminate\Routing\Pipeline->Illuminate\Pipeline\{closure}()
Pipeline.php:54, Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}()
ShareErrorsFromSession.php:49, Illuminate\View\Middleware\ShareErrorsFromSession->handle()
Pipeline.php:151, Illuminate\Routing\Pipeline->Illuminate\Pipeline\{closure}()
Pipeline.php:54, Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}()
StartSession.php:63, Illuminate\Session\Middleware\StartSession->handle()
Pipeline.php:151, Illuminate\Routing\Pipeline->Illuminate\Pipeline\{closure}()
Pipeline.php:54, Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}()
EncryptCookies.php:66, App\Http\Middleware\EncryptCookies->handle()
Pipeline.php:151, Illuminate\Routing\Pipeline->Illuminate\Pipeline\{closure}()
Pipeline.php:54, Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}()
Pipeline.php:104, Illuminate\Routing\Pipeline->then()
Router.php:681, Illuminate\Routing\Router->runRouteWithinStack()
Router.php:656, Illuminate\Routing\Router->runRoute()
Router.php:622, Illuminate\Routing\Router->dispatchToRoute()
Router.php:611, Illuminate\Routing\Router->dispatch()
Kernel.php:176, App\Http\Kernel->Illuminate\Foundation\Http\{closure}()
Pipeline.php:30, Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}()
InjectDebugbar.php:58, Barryvdh\Debugbar\Middleware\InjectDebugbar->handle()
Pipeline.php:151, Illuminate\Routing\Pipeline->Illuminate\Pipeline\{closure}()
Pipeline.php:54, Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}()
TrustProxies.php:57, App\Http\Middleware\TrustProxies->handle()
Pipeline.php:151, Illuminate\Routing\Pipeline->Illuminate\Pipeline\{closure}()
Pipeline.php:54, Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}()
TransformsRequest.php:31, Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull->handle()
Pipeline.php:151, Illuminate\Routing\Pipeline->Illuminate\Pipeline\{closure}()
Pipeline.php:54, Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}()
TransformsRequest.php:31, App\Http\Middleware\TrimStrings->handle()
Pipeline.php:151, Illuminate\Routing\Pipeline->Illuminate\Pipeline\{closure}()
Pipeline.php:54, Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}()
ValidatePostSize.php:27, Illuminate\Foundation\Http\Middleware\ValidatePostSize->handle()
Pipeline.php:151, Illuminate\Routing\Pipeline->Illuminate\Pipeline\{closure}()
Pipeline.php:54, Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}()
CheckForMaintenanceMode.php:62, App\Http\Middleware\CheckForMaintenanceMode->handle()
Pipeline.php:151, Illuminate\Routing\Pipeline->Illuminate\Pipeline\{closure}()
Pipeline.php:54, Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}()
Request.php:111, Dingo\Api\Http\Middleware\Request->handle()
Pipeline.php:151, Illuminate\Routing\Pipeline->Illuminate\Pipeline\{closure}()
Pipeline.php:54, Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}()
Pipeline.php:104, Illuminate\Routing\Pipeline->then()
Kernel.php:151, App\Http\Kernel->sendRequestThroughRouter()
Kernel.php:116, App\Http\Kernel->handle()
index.php:55, {main}()

计划

下一节将描述通过执行当前route对象的run方法,如何执行路由对应的方法并返回结果。这也是我们刚开始使用框架时大部分代码书写的位置。

小结

  • 中间件分为前置中间件和后置中间件,仅仅在于$next(...)前后是否有相应的处理逻辑代码。
本作品采用《CC 协议》,转载必须注明作者和本文链接
日拱一卒
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
未填写
文章
92
粉丝
87
喜欢
152
收藏
121
排名:72
访问:11.3 万
私信
所有博文
社区赞助商