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)!
相关文章推荐
- Laravel5.5源码详解 -- 一次查询的详细执行:从Auth-Login-web中间件到数据库查询结果的全过程
- Laravel5.5源码详解 -- Session的启动分析
- Laravel5.5源码详解 -- Config 配置文件的加载
- laravel中间件源码分析
- laravel中间件源码分析
- Laravel5.5源码详解 -- 数据库的启动与连接过程
- scrapy源码分析(十二)---------下载中间件RobotsTxtMiddleware
- Laravel5.5源码详解 -- Laravel-debugbar及使用elementUI-ajax的注意事项
- Laravel5.5源码详解 -- Request是如何生成的?
- 数据库中间件 MyCAT源码分析:【单库单表】插入
- 数据库中间件 MyCAT 源码分析 —— 【单库单表】查询
- 数据库分库分表中间件 Sharding-JDBC 源码分析 —— SQL 解析(四)之插入SQL
- 详解SpringMVC请求的时候是如何找到正确的Controller[附带源码分析]
- SSH:Hibernate框架(常用API详解及源码分析)
- hadoop源码分析系列(七)——org.apache.hadoop.hdfs包完结篇——dataNode详解及总结
- AFNetworking3.1.0源码分析(六)详解AFHTTPRequestSerializer 之序列化NSMutableURLRequest
- 【MVC - 参数原理】详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析]
- Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)
- netty源码分析之-Channel、ChannelPipeline、ChannelHandler以及 ChannelHandlerContext 详解(2)
- netty源码分析(二十二)Netty编解码器剖析与入站出站处理器详解