Laravel中间件原理理解-Pipeline管道模式

Photo by KOBU Agency on Unsplash

Laravel中间件原理理解-Pipeline管道模式

打开入口文件index.php,我们发现,laravel在通过Application对象解析(make)出

Http Kernel对象后,会调用kernel对象的handle方法,我们的中间件Middleware就是在这里去调用的。

<?php

//index.php 入口文件

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);

//Http Kernel.class 类文件
//handle方法
public function handle($request)
    {
        try {
            $request->enableHttpMethodParameterOverride();
            //将$request对象穿透路由
            $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 RequestHandled($request, $response)
        );

        return $response;
    }

//sendRequestThroughRouter方法
protected function sendRequestThroughRouter($request)
    {
        //将request对象放进Application对象的$instances属性中
        $this->app->instance('request', $request);

        Facade::clearResolvedInstance('request');

        //先启动服务:,调用这些服务的bootstrap方法
        /**
         *  \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class, 加载环境变量
            \Illuminate\Foundation\Bootstrap\LoadConfiguration::class, 加载配置信息
            \Illuminate\Foundation\Bootstrap\HandleExceptions::class, 处理异常
            \Illuminate\Foundation\Bootstrap\RegisterFacades::class, 注册门面
            \Illuminate\Foundation\Bootstrap\RegisterProviders::class, 调用服务提供者的register方法,一般是向服务容器注册服务
            \Illuminate\Foundation\Bootstrap\BootProviders::class, 调用服务提供者的boot方法
         */
        $this->bootstrap();

        //这里就是中间件的调用流转,会将$request对象通过管道的方式一层层(像剥洋葱一样)传递下去执行
        return (new Pipeline($this->app))
                    ->send($request)
                    ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                    ->then($this->dispatchToRouter());
    }

Pipeline对象就是我们今天研究的对象,看上面我们调用了Pipeline对象的三个方法

方法一:send(),这里我们会将$request传进去给Pipeline对象的$passable属性;

方法二:through(),这里我们将http kernel对象中的$middleware属性传进去给Pipeline对象的$pipes属性;

方法三:then(),我们的重点研究方法,是将从路由中解析出的控制器方法闭包形式传进去给$destination形参;

我们打开then方法,看到如下的代码

protected function carry()
    {
        return function ($stack, $pipe) {
            return function ($passable) use ($stack, $pipe) {
                try {
                    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];
                    }
                    $carry = method_exists($pipe, $this->method)
                                    ? $pipe->{$this->method}(...$parameters)
                                    : $pipe(...$parameters);

                    return $this->handleCarry($carry);
                } catch (Exception $e) {
                    return $this->handleException($passable, $e);
                } catch (Throwable $e) {
                    return $this->handleException($passable, new FatalThrowableError($e));
                }
            };
        };
    }
public function then(Closure $destination)
{
    $pipeline = array_reduce(
        array_reverse($this->pipes()), $this->carry(), $this->prepareDestination($destination)
    );

    return $pipeline($this->passable);
}

这段代码的大致意思,先将$pipes数组(也就是传进来的$middleware)中的元素反转倒序array_reverse一下

这里有个比较陌生的array_reduce方法,这个方法有三个形参,作用是循环参数1中的$pipes元素,将第一个元素作为参数2{调用carry()}返回的回调方法的$pipe参数传递进去,而$destination就是传递给回调方法的$stack参数,这就完成了第一个元素的调用,生成一个闭包callback1,以此类推,第二个元素作为参数2{调用carry()}返回的回调方法的$pipe参数传递进去,而上一次处理第一个元素生成的callback1作为传递给回调方法的$stack参数。最终会生成一个层层递进像洋葱一样的回调函数,最后调用这个洋葱回调函数执行每个中间件的handle方法,来让$request对象在中间件中流转处理。

可能上面说了一大段会被绕晕,我这里从网上摘抄了一个简单点的例子,加以自己的理解放出来给大家去研究。

<?php
//laravel中间件的实现原理简化版:
interface Middleware
{
  public static function handle($request,\Closure $next);
}

class CSRFToken implements Middleware
{
  public static function handle($request,\Closure $next)
  {
      echo $request.'处理token';
      echo "\n";
      $next($request);//这里执行包含调用下一个中间件的handle方法的回调,将对象$request传进去
  }
}

class SetSession implements Middleware
{
  public static function handle($request,\Closure $next)
  {
      echo $request.'处理session';
      echo "\n";
      $next($request);//这里执行带有调用下一个中间件的handle方法的回调,将对象$request传进去
  }
}

class CommonOrder implements Middleware
{
  public static function handle($request,\Closure $next)
  {
      echo $request.'处理order下单';
      echo "\n";
      $next($request);//这里已经到了最后一个中间件,这里表示调用$destination回调函数,将对象$request传进去
  }
}

$pipes = [
  'CSRFToken',
  'SetSession',
  'CommonOrder',
];


$destination = function($passable){

  echo '最终执行控制器方法';//类似:CommonOrderController::update($passable)

};

function prepareDestination(Closure $destination)
{
  //这里是最后一个中间件执行的回调函数,里面包含执行控制器方法并返回响应
  return function ($passable) use ($destination) {
    return $destination($passable);          
  };
}


function carry()
{
  return function ($stack, $pipe) {

      //第一次执行的$stack是$destination 回调函数,$pipe是数组$pipes的第一个元素'CommonOrder'

      //每执行一次返回的这个callback就作为下一次执行carry()返回的回调函数的$stack参数,数组的下一个元素'SetSession'作为$pipe参数,以此类推
      return function ($passable) use ($stack, $pipe) {
          return $pipe::handle($passable,$stack);
      };
  };
}


$pipeline = array_reduce(array_reverse($pipes), carry(),prepareDestination($destination));

print_r($pipeline);//这里的pipeline就是跟洋葱一样带有层层递进回调函数的回调函数

$passable = 'request对象';

$pipeline($passable);//执行这个跟洋葱一样的回调函数,把$request对象传进去

贴出上面跟洋葱一样的回调函数伪代码和最终执行结果

//伪代码:第一次形成的闭包函数
function ($passable) {
  return CommonOrder::handle($passable,function ($passable) use ($destination) {
    return $destination($passable);           
  });
}

//伪代码:第二次形成的闭包函数
function ($passable) {
  return SetSession::handle($passable,function ($passable) {
    return CommonOrder::handle($passable,function ($passable) use ($destination) {
      return $destination($passable);           
    });
  });
}

//伪代码:第三次形成的闭包函数
function ($passable) {
  return CSRFToken::handle($passable,function ($passable) {
    return SetSession::handle($passable,function ($passable) {
      return CommonOrder::handle($passable,function ($passable) use ($destination) {
        return $destination($passable);            
      });
    });
  });
}
//request对象处理token
//request对象处理session
//request对象处理order下单
//最终执行控制器方法%

这就是Laravel实现中间件的大致原理啦,记录到这里学习用。