您的位置:首页 > 编程语言 > PHP开发

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

2017-12-26 16:15 811 查看

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
1577a
\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)!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐