Laravel5.5源码详解 -- 中间件MiddleWare分析

Laravel5.5源码详解 – 中间件MiddleWare分析

启动流程

首先,我们从/public/index.php开始,程序正是从这里启动的。Middleware正是在这里启动,如下

$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);

注意到这个Kernel,本质上是/app/Http/Kernel.php,其handle函数在其父类Illuminate\Foundation\Http\Kernel中,

   public function handle($request)
    {
        try {
            $request->enableHttpMethodParameterOverride();

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

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

这里有必要解释最后这几句:

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

这三句等价于:

$this->container = $this->app;
$this->passable = $request;
$this->pipes =$pipes[middleWares array];

其中中间件名(路径)是以数组的形式传递给pipes[]的。

这里,最关键、也是最难理解是的,是看then函数。要理解这个函数,必须理解laravel的Pipeline。下面先列出来,然后再重点逐步讲解。

先看Pipeline(Illuminate\Routing\Pipeline)里面的carry 和 prepareDestination

use Illuminate\Pipeline\Pipeline as BasePipeline;

class Pipeline extends BasePipeline
{
    protected function prepareDestination(Closure $destination)
    {
        return function ($passable) use ($destination) {
            try {
                return $destination($passable);
            } catch (Exception $e) {
                return $this->handleException($passable, $e);
            } catch (Throwable $e) {
                return $this->handleException($passable, new FatalThrowableError($e));
            }
        };
    }

    protected function carry()
    {
        return function ($stack, $pipe) {
            return 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));
                }
            };
        };
}
…
}

上面这段代码中,其父类basePipeline(Illuminate\Pipeline\Pipeline)的源代码如下,其中就有大名鼎鼎的流程函数send,via, through,then

use Illuminate\Contracts\Pipeline\Pipeline as PipelineContract;
class Pipeline implements PipelineContract
{
    protected $container; 
    protected $passable;
    protected $pipes = [];
    protected $method = 'handle';

    public function __construct(Container $container = null)
    {
        $this->container = $container;
    }

    public function send($passable)
    {
        $this->passable = $passable;
        return $this;
    }

    public function through($pipes)
    {
        $this->pipes = is_array($pipes) ? $pipes : func_get_args();
        return $this;
    }

    public function via($method)
    {
        $this->method = $method;
        return $this;
    }

    public function then(Closure $destination)
    {
        // 所有中间件作为decorator函数,如同一系列按顺序排列的多段管道(多个中间件),
        // request必须经过这些管道,每段管道都有一个handle函数,对request进行处理。
        // 下面这一句,完成了管道的铺设工作 $pipeline
        $pipeline = array_reduce(
            array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)
        );

        // 事务处理,完工后返回response
        return $pipeline($this->passable);
    }

    protected function prepareDestination(Closure $destination)
    {
        return function ($passable) use ($destination) {
            return $destination($passable);
        };
    }

    protected function carry()
    {
        return function ($stack, $pipe) {
            return function ($passable) use ($stack, $pipe) {
                if (is_callable($pipe)) {                    
                    return $pipe($passable, $stack);
                } elseif (! is_object($pipe)) {

                    list($name, $parameters) = $this->parsePipeString($pipe);

                    $pipe = $this->getContainer()->make($name);

                    $parameters = array_merge([$passable, $stack], $parameters);
                } else {
                    $parameters = [$passable, $stack];
                }
                return method_exists($pipe, $this->method)
                                ? $pipe->{$this->method}(...$parameters)
                                : $pipe(...$parameters);
            };
        };
    }

    protected function parsePipeString($pipe)
    {
        list($name, $parameters) = array_pad(explode(':', $pipe, 2), 2, []);
        if (is_string($parameters)) {
            $parameters = explode(',', $parameters);
        }
        return [$name, $parameters];
    }

    protected function getContainer()
    {
        if (! $this->container) {
            throw new RuntimeException('A container instance has not been passed to the Pipeline.');
        }
        return $this->container;
    }
}

在carry()函数中,中间件被实例化,得到的这个$pipe,就是中间件对象,

$pipe = $this->getContainer()->make($name);

所以,在carry()函数最后的return中,

$pipe->{$this->method}(...$parameters)

实际上相当于调用了中间件的handle函数,来处理我们的目标函数(标的函数或对象)。标的函数(也就是要经过中间件的那些目标函数,比如Login会经过Auth中间件检查授权,那么Login就是标的函数)。

现在回来看,在then中传入进来的都是什么?

$destination是个闭包,如下

Closure {#23 ▼
  class: "Illuminate\Foundation\Http\Kernel"
  this: Kernel {#29 …}
  parameters: {$request: {}
  }
  file: "D:\wamp64\www\laravel\larablog\vendor\laravel\framework\src\Illuminate\Foundation\Http\Kernel.php"
  line: "173 to 177"
}

也就是下面这个闭包,他负责把request的实例绑定到app的intannces数组中去。

       return function ($request) {
            $this->app->instance('request', $request);
            return $this->router->dispatch($request);
        };

$this->pipes就是那些中间件,前面提到过的,当然后面还有更多,不再一一列举。

array:6 [▼
  0 => "Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode"
  1 => "Illuminate\Foundation\Http\Middleware\ValidatePostSize"
  2 => "App\Http\Middleware\TrimStrings"
  3 => "Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull"
  4 => "App\Http\Middleware\TrustProxies"
  5 => "Barryvdh\Debugbar\Middleware\InjectDebugbar"
]

函数then()之中最后return回去的那个Pipeline,是下面这个东东,

Closure {#166 ▼
  class: "Illuminate\Routing\Pipeline"
  this: Pipeline {#34 …}
  parameters: {$passable: {}
  }
  use: {▼
    $stack: Closure {#171 …}
    $pipe: "Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode"
  }
  file: "D:\wamp64\www\laravel\larablog\vendor\laravel\framework\src\Illuminate\Routing\Pipeline.php"
  line: "47 to 59"
}

这个闭包也就是下面这段代码

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

$this->passable($passable)又是什么呢?也是一个闭包,结合前面提到的sendRequestThroughRouter和下面的代码,

public function send($passable)
{
    $this->passable = $passable;
    return $this;
}

可以看到,这个$passable就是我们要发送的request。

另外,我们注意到,那个through什么也没干,和前面那个send一样,就是完成一次赋值的操作($pipes通常都是不为空的数组,在这种情况下,最后执行的结果,就相当于$this->pipes = $pipes),

public function through($pipes)
{
    $this->pipes = is_array($pipes) ? $pipes : func_get_args();
    return $this;
}

加入以下调试代码,可以看到输出结果,

public function then(Closure $destination)
{
    $pipeline = array_reduce(
        array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)
    );
    return $pipeline($this->passable);
}

整个过程传进来的参数如下
$destination;

Closure {#23 ▼
  class: "Illuminate\Foundation\Http\Kernel"
  this: Kernel {#29 …}
  parameters: {}
  file: "D:\wamp64\www\laravel\larablog\vendor\laravel\framework\src\Illuminate\Foundation\Http\Kernel.php"
  line: "173 to 177"
}

$this->pipes;

array:6 [▼

  0 => "Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode"
  1 => "Illuminate\Foundation\Http\Middleware\ValidatePostSize"
  2 => "App\Http\Middleware\TrimStrings"
  3 => "Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull"
  4 => "App\Http\Middleware\TrustProxies"
  5 => "Barryvdh\Debugbar\Middleware\InjectDebugbar"
]

最后得到的$pipeline;

Closure {#166 ▼
  class: "Illuminate\Routing\Pipeline"
  this: Pipeline {#34 …}
  parameters: {}
  use: {}
  file: "D:\wamp64\www\laravel\larablog\vendor\laravel\framework\src\Illuminate\Routing\Pipeline.php"
  line: "47 to 59"
}

$destination;

Closure {#289 ▼
  class: "Illuminate\Routing\Router"
  this: Router {#25 …}
  parameters: {}
  use: {}
  file: "D:\wamp64\www\laravel\larablog\vendor\laravel\framework\src\Illuminate\Routing\Router.php"
  line: "656 to 660"
}

$this->pipes;

array:7 [▼
  0 => "App\Http\Middleware\EncryptCookies"
  1 => "Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse"
  2 => "Illuminate\Session\Middleware\StartSession"
  3 => "Illuminate\View\Middleware\ShareErrorsFromSession"
  4 => "App\Http\Middleware\VerifyCsrfToken"
  5 => "Illuminate\Auth\Middleware\Authenticate:admin"
  6 => "Illuminate\Routing\Middleware\SubstituteBindings"
]

最后得到的$pipeline;

Closure {#319 ▼
  class: "Illuminate\Routing\Pipeline"
  this: Pipeline {#284 …}
  parameters: {}
  use: {}
  file: "D:\wamp64\www\laravel\larablog\vendor\laravel\framework\src\Illuminate\Routing\Pipeline.php"
  line: "47 to 59"
}

可以看到,这些middleWare分两波传进来。

array_reduce()

array_reduce在这里起到了最重要的作用,从原理上来说,
假设

$pipes = [0 => ‘middleWare1’, 1 => ‘middleWare2’, 2 => ‘middleWare3’];

array_reverse(pipes)之后,

$pipes = [0 => ‘middleWare3’, 1 => ‘middleWare2’, 2 => ‘middleWare1’];

那么,下面这个array_reduce函数中

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

prepareDestination(Closure $destination)相当于执行$destination($passable);并再次形成一个闭包。

还记得$destionation是什么吗?为方便,我再次贴出来,就是这段代码,

   return function ($request) {
        $this->app->instance('request', $request);
        return $this->router->dispatch($request);
    };

这里没有任何奇特的地方,就是必须不断构造闭包,否则array_reduce他老人家不认识(你不能把普通函数当参数传进去)。Laravel的这种写法确实简洁,但不好理解。另外说一句,在$destination已经是闭包的前提下,这里prepareDestination再来构造一次就有些多余;但这一段是必须的,万一你什么地方传进来的不是闭包呢?
当然,我们可以停下来看一眼这个carry是怎样进一步构造闭包的,我把子类函数和父类函数的代码分别贴上来。特别注意的是,我特意加上了两个dump来辅助调试。

protected function carry()
{
    return function ($stack, $pipe) {
        return function ($passable) use ($stack, $pipe) {
            try {
                dump($stack);
                dump($pipe);
                $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));
            }
        };
    };
}

protected function carry()
{
    return function ($stack, $pipe) {
        return function ($passable) use ($stack, $pipe) {
            if (is_callable($pipe)) {
                return $pipe($passable, $stack);
            } elseif (! is_object($pipe)) {
                list($name, $parameters) = $this->parsePipeString($pipe);
                $pipe = $this->getContainer()->make($name);
                $parameters = array_merge([$passable, $stack], $parameters);
            } else {                    
                $parameters = [$passable, $stack];
            }
            return method_exists($pipe, $this->method)
                            ? $pipe->{$this->method}(...$parameters)
                            : $pipe(...$parameters);
        };
    };
}

输出的代码如下面所示,很长,注意到两点,第一,最先出现的闭包use的是下面要出现的闭包。第二,pipes出现的顺序,和在then中参数传送进来的顺序是完全一样的,第一个就是

"Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode"

Closure {#170 ▼
  class:"Illuminate\Routing\Pipeline"
  this: Pipeline {#34 …}
  parameters: {$passable: {}
  }
  use: {▼
    $stack: Closure {#169 …}
    $pipe:"Illuminate\Foundation\Http\Middleware\ValidatePostSize"
  }  file:"D:\wamp64\www\laravel\larablog\vendor\laravel\framework\src\Illuminate\Routing\Pipeline.php"
  line: "47 to 61"
}
"Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode"


Closure {#169 ▼
  class:"Illuminate\Routing\Pipeline"
  this: Pipeline {#34 …}
  parameters: {}
  use: {▼
    $stack: Closure {#168 …}
    $pipe:"App\Http\Middleware\TrimStrings"
  }
  file: "D:\wamp64\www\laravel\larablog\vendor\laravel\framework\src\Illuminate\Routing\Pipeline.php"
  line: "47 to 61"
}
"Illuminate\Foundation\Http\Middleware\ValidatePostSize"

下面都是一样的,原理上就是这个样子,后面省略不少,不然打出来太长了

Closure {#168 ▼
    use: {▼
    $stack: Closure {#167}


Closure {#167 ▼
  use: {▼
    $stack: Closure {#166}


Closure {#166 ▼
  use: {▼
$stack: Closure {#119} 

。。。。。。

array_reverse($this->pipes)

关于array_reverse($this->pipes)的原因,我写了个可以直接运行的例程,参考下面的代码。在此之前,如果你想知道些decorator的模型,可以参考这篇文章:Decorator函数

<?php

interface IMiddleware
{
    public function handle();
}

//definition of middle ware classes
class CheckForMaintenanceMode implements IMiddleware
{
    private $middleware;

    public function __construct(IMiddleware $middleware)
    {
        $this->middleware = $middleware;
    }

    public function handle()
    {
        //$this->middleware->handle();
        echo 'CheckForMaintenanceMode' . "<br>";
        echo "<hr>";
        $this->middleware->handle();
    }
}

class ValidatePostSize implements IMiddleware
{
    private $middleware;

    public function __construct(IMiddleware $middleware)
    {
        $this->middleware = $middleware;
    }

    public function handle()
    {
        //$this->middleware->handle();
        echo 'ValidatePostSize' . "<br>";
        echo "<hr>";
        $this->middleware->handle();
    }
}

class TrimStrings implements IMiddleware
{
    private $middleware;

    public function __construct(IMiddleware $middleware)
    {
        $this->middleware = $middleware;
    }

    public function handle()
    {
        //$this->middleware->handle();
        echo 'TrimStrings' . "<br>";
        echo "<hr>";
        $this->middleware->handle();
    }
}

//define a passable request
interface IPassable extends IMiddleware
{
    public function getRequest();
}

class Request implements IPassable
{
    public function handle()
    {
        echo 'Handle request from the client, and it has passed through all the 3 middlewares.'. "<br>";
        echo "<hr>";
    }

    public function getRequest()
    {
        return $this;
    }
}

class Client
{
    protected $request;
    protected $response;

    public function __construct()
    {
        $this->request  = new Request();
        $this->response = $this->wrapDecorator($this->request);
    }

    public function wrapDecorator(IMiddleware $decorator)
    {
        $decorator = new TrimStrings($decorator);  //2 = func(1)
        $decorator = new ValidatePostSize($decorator);  //3= func(2)
        $response  = new CheckForMaintenanceMode($decorator);  //4= func(3)

        return $response;
    }

    /**
     * @return \MyRightCapital\Development\DecoratorPattern\IMiddleware
     */
    public function getResponse()
    {
        return $this->response->handle();
    }
}

$client = new Client;
$client->getResponse();

运行上面这段代码(或者,在命令窗口输入 php index.php > index.html,然后用浏览器打开index.html),你会得到下面的结果,

CheckForMaintenanceMode
ValidatePostSize
TrimStrings
Handle requestfrom the client, and it has passed through all the 3 middlewares.

如果你把那些echo语句调到$this->middleware->handle()这句的前面,你会得到一个相反的输出顺序,如下,

Handle requestfrom the client, and it has passed through all the 3 middlewares.
TrimStrings
ValidatePostSize
CheckForMaintenanceMode

这些反序一般并不是我们所希望的,因为在laravel中,大多数情况下是希望middleWare 能在response被handle之前,做一些具体的把关工作(相当于这里的echo),所以,这里echo语句都应该在handle语句之前。因此,也不难理解,为什么在laravel的then逻辑中,array_reduce的第一个参数是array_reverse($this->pipes)!

猜你喜欢

转载自blog.csdn.net/tanmx219/article/details/78903521