Home > PHP Framework > Laravel > body text

Analysis of middleware source code based on laravel5.2

不言
Release: 2018-07-31 14:37:18
Original
2196 people have browsed it

在laravel5.2中,Http的主要作用就是过滤Http请求(php aritsan是没有中间件机制的),同时也让系统的层次(Http过滤层)更明确,使用起来也很优雅。但实现中间件的代码却很复杂,下面就来具分析下有关中间件的源码的内容。

中间件源码

中间件本身分为两种,一种是所有http的,另一种则是针对route的。一个有中间件的请求周期是:Request得先经过Http中间件,才能进行Router,再经过Requset所对应Route的Route中间件, 最后才会进入相应的Controller代码。laravel把请求分为了两种:http和console。不同的请求方式用它自己的Kernel来驱动Application。Http请求则是通过
\Illuminate\Foundation\Http\Kernel类来驱动,它定义了所有的中间件,其父类\Illuminate\Foundation\Http\Kernel::handle就是对请求进行处理的入口了

Http中间件

跟踪入口handle()方法,很容易发现该函数(\Illuminate\Foundation\Http\Kernel::sendRequestThroughRouter):

protected function sendRequestThroughRouter($request)
{
    $this->app->instance('request', $request);

    Facade::clearResolvedInstance('request');

    $this->bootstrap();

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

该函数会把Requset分发到Router(通过方法名就知道了), 主要的逻辑则是通过\Illuminate\Routing\Pipeline完成的, 作用就是让Requset通过Http中间件的检测,然后再到达Router。这里的代码看起来很优雅,但不是很好理解。所以,了解Pipeline的运行机制就会明白中间件的使用。

Pipeline的运行实现

Pipleline基类是\Illuminate\Pipeline\Pipeline,它的执行在then方法:

public function then(Closure $destination)
{
    $firstSlice = $this->getInitialSlice($destination);

    $pipes = array_reverse($this->pipes);

    return call_user_func(
        array_reduce($pipes, $this->getSlice(), $firstSlice), $this->passable
    );
}
Copy after login

了解这段代码执行的意图,必须要知道array_reduce()做了什么。 为了清楚array_reduce怎么运行的,先把array_reduce重写一次:

//将数组中的元素,依次执行$func函数,且上一次的$func的返回值作为下一次调用$func的第一个参数输入
function array_reduce_back($arr, callable $func, $firstResult = null)
{
    $result = $firstResult;
    
    foreach ($arr as $v) {
        $result = $func($result, $v);
    }
    
    return $result;
}
Copy after login

所以,源代码中的$funcgetSlice(),它返回的是一个回调函数:function($passable) use ($stack, $pipe){...}$stack$pipe被输入的具体值代替),也就是说作为上一次返回结果输入到下一次$func的第一个参数是上述的回调函数,如此循环,当数组遍历完成,array_reduce就返回的是一个回调函数,现在关键就是了解这个回调函数是什么样子,又如何执行?为方便讨论,可分析下面的代码:

call_user_func(
        array_reduce([1, 2, 3], $this->getSlice(), $firstSlice), $this->passable
    );
Copy after login

Analysis of middleware source code based on laravel5.2

执行说明:
1.$result_0是初始化的值 ,为$firstSlice ,即是\Illuminate\Pipeline\Pipeline::getInitialSlice的返回回调
2.每遍历一个元素,都会执行\Illuminate\Pipeline\Pipeline::getSlice的回调,同时也会返回一个回调
3.$result中的具体执行代码都在getSlice()
4.最后的array_reduce返回结果是$result_3,是一个有多层闭包的回调函数
5.执行的是call_user_func($result_3, $this->passable),即执行function($this->passable) use ($result_2, 3){...}

至此已经清楚了then()是如何运行的了,要继续下去,则需再搞定回调函数到底怎么执行的.现在再跟着sendRequestThroughRouter中的Pipeline走,看它是如何执行的。

// 把具体的参数带进来
return (new Pipeline($this->app))
                ->send($request)
                ->through(['\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode'])
                ->then($this->dispatchToRouter());
Copy after login

用上面的所分析的Pipeline执行过程,很快就会分析出最后执行的是

function($requset) use (\Illuminate\Foundation\Http\Kernel::dispatchToRouter(), '\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode') {

            if ($pipe instanceof Closure) {
                return call_user_func($pipe, $passable, $stack);
            }
            
            // $name和$parameters很容易得到
            // $name = '\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode';
            // $parameters = [];
            list($name, $parameters) = $this->parsePipeString($pipe);


            // 执行的就是\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::handle($request, \Illuminate\Foundation\Http\Kernel::dispatchToRouter())
            return call_user_func_array([$this->container->make($name), $this->method],
                        array_merge([$passable, $stack], $parameters));
}
Copy after login

逻辑处理已经到了\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::handle,其代码是:

public function handle($request, Closure $next)
{
    if ($this->app->isDownForMaintenance()) {
        throw new HttpException(503);
    }

    return $next($request);
}
Copy after login

这里,它处理了这个中间件所需要过滤的条件,同时执行了$next($request),即\Illuminate\Foundation\Http\Kernel::dispatchToRouter(), 这样,就把Request转到了Router中,也就完成了Http中间件的所有处理工作,而$next($request)是每个中间件都不可少的操作,因为在回调中嵌套了回调,就是靠中间件把Request传递到下一个回调中,也就会解析到下一个中间件,直到最后一个。紧跟上面的已分析的Pipeline执行过程,讲其补充完整:

6.执行$result_3中的回调,getSlice实例化中间件,执行其handle,在中间件处理中执行回调

7.回调中还嵌套回调的,每个中间件中都需有执行回调的代码$next($request) ,才能保证回调中的回调会执行,执行的顺序就是3::handel,2::handel,1::handel,$first

8.最里面一层,一定是传递给then()的参数,then执行的就是最后一步

9.执行的顺序是由数组中的最后一个,向前,到then()的参数,为了使其执行顺序是数组中的第一个到最后一个,再到then()中的参数,then()方法中就做了一个反转array_reverse

Pipeline小结

现在,Pipeline的所有执行流程就都分析完了。实现代码真的很绕,但理解之后编写自定义的中间件应该就很容易了。现在再把Pipeline的使用翻译成汉语,应该是这样的

// 使用管道,发送$request,使之通过middleware ,再到$func
(new Pipeline($this->app))->send($request)->through($this->middleware)->then($func);
Copy after login

这样的代码不管是从语义上,还是使用上都很优雅,高!确实是高!再回到源码,Requset的流程就通过dispatchToRouter进入到了Router

Route中间件

在Router中,\Illuminate\Routing\Router::dispatch就承接了来自Http中间件的Requset, Router把Request分发到了具体的Route,再进行处理,主要代码如下:

public function dispatchToRoute(Request $request)
{
    // 找到具体的路由对象,过程略
    $route = $this->findRoute($request);

    $request->setRouteResolver(function () use ($route) {
        return $route;
    });

    // 执行Request匹配到Route的事件,具体的代码在这里:\Illuminate\Foundation\Providers\FoundationServiceProvider::configureFormRequests
    $this->events->fire(new Events\RouteMatched($route, $request));
    
    // 这里就运行路由中间件了
    $response = $this->runRouteWithinStack($route, $request);

    return $this->prepareResponse($request, $response);
}


protected function runRouteWithinStack(Route $route, Request $request)
{
    // 获取该路由上的中间件
    // 简单就点可这样写:
    // $middleware = App::shouldSkipMiddleware() ? [] : $this->gatherRouteMiddlewares($route);
    $shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
                         $this->container->make('middleware.disable') === true;

    $middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddlewares($route);
    
    // 了解Pipeline后,这里就好理解了,应该是通过管道,发送$request,经过$middleware,再到then中的回调
    return (new Pipeline($this->container))
                    ->send($request)
                    ->through($middleware)
                    ->then(function ($request) use ($route) {
                        return $this->prepareResponse(
                            $request,
                            $route->run($request)
                        );
                    });
}
Copy after login

如何获取Route中间件的,就可以跟gatherRouteMiddlewares,这个代码并不难,很好跟。接下来,Request就到到达至于Controller, Request是如何到达Controller的代码就不难了,这里就不说了

Controller后执行中间件

成功获取Response后,在public/index.php58行执行了$kernel->terminate($request, $response);, 也就是在主要逻辑处理完成之后,再执行此代码,它实际上调用是的\Illuminate\Foundation\Http\Kernel::terminate, 跟进去就很容易发现,它处理了这此请求所涉及到的中间件,并执行了各自的terminate方法,到这里,中间件的另一个功能就展现出来了,就是主要逻辑完成之后的收尾工作.到这里为止,中间件就完成了它的使命(一个请求也就完成了)

如何使用中间件

在官方文档上讲解的很清楚注册中间

中间件小结

至此,中间件的实现逻辑与使用就清晰了.从执行的顺序来分,一个在Controller之前,一个在Controller之后,所以它一个很重要的作用就是可以让Controller专注于自己的主要逻辑的职责更明确. 奇怪的是,但前后两种中间件的执行方式却不一样, \Illuminate\Foundation\Http\Kernel::terminate,中间件的结束却没有使用Pipeline, 而是直接foreach.相同的工作却用两种代码来实现.现在看来,中间件本身并不复杂,但它带给了我两个启发,1.层次明确 2,Pipeline所带来的优雅.

相关推荐:

laravel5.4中自定义包开发的实例

Laravel 5.1框架中如何创建自定义Artisan控制台命令

laravel框架的启动过程分析

The above is the detailed content of Analysis of middleware source code based on laravel5.2. For more information, please follow other related articles on the PHP Chinese website!

Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template