>PHP 프레임워크 >Laravel >laravel5.2 기반 미들웨어 소스코드 분석

laravel5.2 기반 미들웨어 소스코드 분석

不言
不言원래의
2018-07-31 14:37:182220검색

laravel5.2에서 Http의 주요 기능은 Http 요청을 필터링하는 것입니다(php aritsan에는 미들웨어 메커니즘이 없습니다). 또한 시스템 수준(Http 필터링 계층)을 더 많이 만듭니다. 명확하고 우아하게 사용할 수 있습니다. 그러나 미들웨어를 구현하는 코드는 매우 복잡합니다. 미들웨어의 소스 코드 내용을 분석해 보겠습니다. php aritsan是没有中间件机制的),同时也让系统的层次(Http过滤层)更明确,使用起来也很优雅。但实现中间件的代码却很复杂,下面就来具分析下有关中间件的源码的内容。

中间件源码

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

Http中间件

跟踪入口handle()方法,很容易发现该函数(IlluminateFoundationHttpKernel::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());
}

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

Pipeline的运行实现

Pipleline基类是IlluminatePipelinePipeline,它的执行在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
    );
}

了解这段代码执行的意图,必须要知道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;
}

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

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

laravel5.2 기반 미들웨어 소스코드 분석

执行说明:
1.$result_0是初始化的值 ,为$firstSlice ,即是IlluminatePipelinePipeline::getInitialSlice的返回回调
2.每遍历一个元素,都会执行IlluminatePipelinePipeline::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());

用上面的所分析的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));
}

逻辑处理已经到了IlluminateFoundationHttpMiddlewareCheckForMaintenanceMode::handle,其代码是:

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

    return $next($request);
}

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

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

7.回调中还嵌套回调的,每个中间件中都需有执行回调的代码$next($request)

미들웨어 소스 코드

미들웨어 자체는 두 가지 유형으로 나뉘는데 하나는 모든 http용이고 다른 하나는 경로용입니다. . 미들웨어가 있는 요청 주기는 다음과 같습니다. 요청은 라우터로 가기 전에 먼저 🎜Http 미들웨어를 거쳐야 하며, 그런 다음 해당 Route의 🎜Route 미들웨어를 거쳐야 합니다. 요청>을 클릭하면 마지막으로 해당 🎜컨트롤러 코드가 입력됩니다. Laravel은 요청을 http와 콘솔의 두 가지 유형으로 나눕니다. 다양한 요청 방법은 자체 커널을 사용하여 애플리케이션을 구동합니다. Http 요청은 모든 미들웨어를 정의하는
IlluminateFoundationHttpKernel 클래스에 의해 구동되며 해당 상위 클래스인 IlluminateFoundationHttpKernel::handle은 요청 처리를 위한 진입점입니다. h3>Http 미들웨어🎜handle() 항목 메서드를 추적하면 이 함수를 쉽게 찾을 수 있습니다( IlluminateFoundationHttpKernel::sendRequestThroughRouter): 🎜
// 使用管道,发送$request,使之通过middleware ,再到$func
(new Pipeline($this->app))->send($request)->through($this->middleware)->then($func);
🎜이 함수는 Requset을 Router에 배포하며(메소드 이름으로 알 수 있음) 주요 로직은 IlluminateRoutingPipeline을 통해 완성됩니다. 기능은 Requset이 Http 미들웨어 감지를 통과한 다음 라우터에 도달하도록 하는 것입니다. 여기의 코드는 우아해 보이지만 이해하기는 쉽지 않습니다. 따라서 Pipeline의 작동 메커니즘을 이해하면 미들웨어의 사용법도 이해하게 됩니다. 🎜

파이프라인의 실행 중인 구현

🎜Pipleline 기본 클래스는 IlluminatePipelinePipeline입니다. 실행은 then 메서드에서 이루어집니다. 🎜
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)
                        );
                    });
}
🎜 이 코드 실행의 의도를 이해하려면 array_reduce()가 수행하는 작업을 알아야 합니다. array_reduce가 어떻게 작동하는지 이해하려면 먼저 array_reduce를 다시 작성하세요. 🎜rrreee🎜그래서 소스 코드의 $func getSlice( ), 콜백 함수를 반환합니다: function($passable) use ($stack, $pipe){...}($stack$pipe는 입력된 특정 값으로 대체됨) 즉, 마지막 반환 결과로 다음 $func에 입력되는 첫 번째 매개변수가 위에서 언급한 콜백입니다. function 등입니다. 배열 순회가 완료되면 array_reduce가 콜백 함수를 반환합니다. 이제 핵심은 이 콜백 함수의 모양과 실행 방법을 이해하는 것입니다. 논의를 촉진하기 위해 다음 코드를 분석할 수 있습니다. 🎜rrreee🎜laravel5.2 기반 미들웨어 소스코드 분석🎜🎜Execution 지침 :
1.$result_0은 초기화된 값으로 $firstSlice이며 IlluminatePipelinePipeline::getInitialSlice의 반환 콜백입니다. >
/>2. 요소가 탐색될 때마다 IlluminatePipelinePipeline::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()이 어떻게 작동하는지 명확해졌습니다. 계속해서 콜백 함수가 어떻게 실행되는지 파악해야 합니다. 이제 sendRequestThroughRouterPipeline을 따라 실행되는 방식을 확인하세요. 🎜rrreee🎜위에서 분석한 Pipeline 실행 프로세스를 사용하면 곧 마지막 실행이 🎜rrreee🎜인 것으로 분석됩니다. 논리적 처리가 IlluminateFoundationHttpMiddlewareCheckForMaintenanceMode::handle에 도착했습니다. which 코드는 🎜rrreee🎜여기서 이 미들웨어에 필요한 필터링 조건을 처리하고 $next($request), 즉 IlluminateFoundationHttpKernel::dispatchToRouter()를 실행합니다. >, 이렇게 Request가 Router로 전달되면서 Http 미들웨어의 모든 처리 작업이 완료되고 $next($request)는 모든 미들웨어에 없어서는 안될 작업이기 때문입니다. 콜백은 콜백에 중첩됩니다. 즉, 미들웨어가 요청을 다음 콜백에 전달하고 마지막 콜백까지 다음 미들웨어로 구문 분석됩니다. 위의 분석된 파이프라인 실행 프로세스를 따르고 완전히 추가합니다. 🎜🎜6. $result_3에서 콜백을 실행하고, getSlice가 미들웨어를 인스턴스화하고 해당핸들, 미들웨어 처리에서 콜백 실행 🎜🎜7. 콜백도 콜백에 중첩되어 있으며, 각 미들웨어에는 콜백 <code>$next($request) code>을 실행하는 코드가 있어야 합니다. 콜백이 실행됩니다. 실행 순서는 3::handel, 2::handel, 1::handel, $first🎜입니다.<p>8.最里面一层,一定是传递给then()的参数,then执行的就是最后一步</p> <p>9.执行的顺序是由数组中的最后一个,向前,到then()的参数,为了使其执行顺序是数组中的第一个到最后一个,再到then()中的参数,then()方法中就做了一个反转<code>array_reverse

Pipeline小结

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

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

这样的代码不管是从语义上,还是使用上都很优雅,高!确实是高!再回到源码,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(&#39;middleware.disable&#39;) &&
                         $this->container->make(&#39;middleware.disable&#39;) === 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)
                        );
                    });
}

如何获取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框架的启动过程分析

위 내용은 laravel5.2 기반 미들웨어 소스코드 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.