说明
ThinkPHP 6.0 RC5 开始使用了管道模式来实现中间件,比起之前版本的实现更加简洁、有序。这篇文章对其实现细节进行分析。
首先我们从入口文件 public/index.php 开始,$http = (new App())->http;
获得一个 http 类的实例后调用它的 run 方法:$response = $http->run();,然后它的 run 方法又调用了 runWithRequest 方法:
protected function runWithRequest(Request $request) { . . . return $this->app->middleware->pipeline() ->send($request) ->then(function ($request) { return $this->dispatchToRoute($request); }); }
中间件的执行都在最后的 return 语句中。
pipeline、through、send 方法
$this->app->middleware->pipeline() 的 pipeline 方法: public function pipeline(string $type = 'global') { return (new Pipeline()) // array_map将所有中间件转换成闭包,闭包的特点: // 1. 传入参数:$request,请求实例; $next,一个闭包 // 2. 返回一个Response实例 ->through(array_map(function ($middleware) { return function ($request, $next) use ($middleware) { list($call, $param) = $middleware; if (is_array($call) && is_string($call[0])) { $call = [$this->app->make($call[0]), $call[1]]; } // 该语句执行中间件类实例的handle方法,传入的参数是外部传进来的$request和$next // 还有一个$param是中间件接收的参数 $response = call_user_func($call, $request, $next, $param); if (!$response instanceof Response) { throw new LogicException('The middleware must return Response instance'); } return $response; }; // 将中间件排序 }, $this->sortMiddleware($this->queue[$type] ?? []))) ->whenException([$this, 'handleException']); }
through 方法代码:
public function through($pipes) { $this->pipes = is_array($pipes) ? $pipes : func_get_args(); return $this; }
前面调用 through 是传入的 array_map(...) 把中间件封装为一个个闭包,through 则是把这些闭包保存在 Pipeline 类的 $pipes 属性中。
PHP 的 array_map 方法签名:
array_map ( callable $callback , array $array1 [, array $... ] ) : array
$callback 迭代作用于每一个 $array 的元素,返回新的值。所以,最后得到 $pipes 中每个闭包的形式特征是这样的(伪代码):
function ($request, $next) { $response = handle($request, $next, $param); return $response; }
该闭包接收两个参数,一个是请求实例,一个是回调用函数,handle 方法处理后得到相应并返回。
through 返回一个 Pipeline 类的实例,接着调用 send 方法:
public function send($passable) { $this->passable = $passable; return $this; }
该方法很简单,只是将传入的请求实例保存在 $passable 成员变量,最后同样返回 Pipeline 类的实例,这样就可以链式调用 Pipeline 类的其他方法。
then,carry 方法
send 方法之后,接着调用 then 方法:
return $this->app->middleware->pipeline() ->send($request) ->then(function ($request) { return $this->dispatchToRoute($request); });
这里的 then 接收一个闭包作为参数,这个闭包实际上包含了控制器操作的执行代码。
then 方法代码:
public function then(Closure $destination) { $pipeline = array_reduce( //用于迭代的数组(中间件闭包),这里将其倒序 array_reverse($this->pipes), // array_reduce需要的回调函数 $this->carry(), //这里是迭代的初始值 function ($passable) use ($destination) { try { return $destination($passable); } catch (Throwable | Exception $e) { return $this->handleException($passable, $e); } }); return $pipeline($this->passable); }
carry 代码:
protected function carry() { // 1. $stack 上次迭代得到的值,如果是第一次迭代,其值是后面的「初始值 // 2. $pipe 本次迭代的值 return function ($stack, $pipe) { return function ($passable) use ($stack, $pipe) { try { return $pipe($passable, $stack); } catch (Throwable | Exception $e) { return $this->handleException($passable, $e); } }; }; }
为了更方便分析原理,我们把 carry 方法内联到 then 中去,并去掉错误捕获的代码,得到:
public function then(Closure $destination) { $pipeline = array_reduce( array_reverse($this->pipes), function ($stack, $pipe) { return function ($passable) use ($stack, $pipe) { return $pipe($passable, $stack); }; }, function ($passable) use ($destination) { return $destination($passable); }); return $pipeline($this->passable); }
这里关键是理解 array_reduce 以及 $pipeline($this->passable) 的执行过程,这两个过程可以类比于「包洋葱」和「剥洋葱」的过程。
array_reduce 第一次迭代,$stack 初始值为:
(A)
function ($passable) use ($destination) { return $destination($passable); });
回调函数的返回值为:
(B)
function ($passable) use ($stack, $pipe) { return $pipe($passable, $stack); };
将 A 代入 B 可以得到第一次迭代之后的 $stack 的值:
(C)
function ($passable) use ($stack, $pipe) { return $pipe($passable, function ($passable) use ($destination) { return $destination($passable); }) ); };
第二次迭代,同理,将 C 代入 B 可得:
(D)
// 伪代码 // 每一层的$pipe都代表一个中间件闭包 function ($passable) use ($stack, $pipe) { return $pipe($passable, //倒数第二层中间件 function ($passable) use ($stack, $pipe) { return $pipe($passable, //倒数第一层中间件 function ($passable) use ($destination) { return $destination($passable); //包含控制器操作的闭包 }) ); }; ); };
以此类推,有多少个中间件,就代入多少次,最后一次得到 $stack 就返回给 $pipeline。由于前面对中间件闭包进行了倒序,排在前面的闭包被包裹在更里层,所以倒序后的闭包越是后面的在外面,从正序来看,则变成越前面的中间件在最外层。
层层包裹好闭包后,我们得到了一个类似洋葱结构的「超级」闭包 D,该闭包的结构如上面的代码注释所示。最后把 $request 对象传给这个闭包,执行它:$pipeline($this->passable);,由此开启一个类似剥洋葱的过程,接下来我们看看这洋葱是怎么剥开的。
剥洋葱过程分析
array_map(...) 把每一个中间件类加工成一个类似这种结构的闭包:
function ($request, $next) { $response = handle($request, $next, $param); return $response; }
其中 handle 是中间件中的入口,其结构特点是这样的:
public function handle($request, $next, $param) { // do sth ------ M1-1 / M2-1 $response = $next($request); // do sth ------ M1-2 / M2-2 return $response; }
我们上面的「洋葱」一共只有两层,也就是有两层中间件的闭包,假设 M1-1,M1-2 分别是第一个中间件 handle 方法的前置和后值操作点位,第二个中间件同理,是 M2-1,M2-2。现在,让程序执行 $pipeline($this->passable),展开来看,也就是执行:
// 伪代码 function ($passable) use ($stack, $pipe) { return $pipe($passable, function ($passable) use ($stack, $pipe) { return $pipe($passable, function ($passable) use ($destination) { return $destination($passable); }) ); }; ); }($this->passable)
此时,程序要求从:
return $pipe($passable, function ($passable) use ($stack, $pipe) { return $pipe($passable, function ($passable) use ($destination) { return $destination($passable); }) ); }; );
返回值,也就是要执行第一个中间件闭包,$passable 对应 handle 方法的 $request 参数,而下一层闭包
function ($passable) use ($stack, $pipe) { return $pipe($passable, function ($passable) use ($destination) { return $destination($passable); }) ); }
则对应 handle 方法的 $next 参数。
要执行第一个闭包,即要执行第一个闭包的 handle 方法,其过程是:首先执行 M1-1 点位的代码,即前置操作,然后执行 $response = $next($request);,这时程序进入执行下一个闭包,$next($request) 展开来,也就是:
function ($passable) use ($stack, $pipe) { return $pipe($passable, function ($passable) use ($destination) { return $destination($passable); }) ); }($request)
依次类推,执行该闭包,即执行第二个中间件的 handle 方法,此时,先执行 M2-1 点位,然后执行 $response = $next($request),此时的 $next 闭包是:
function ($passable) use ($destination) { return $destination($passable); })
属于洋葱之芯 —— 最里面的一层,也就是包含控制器操作的闭包,展开来看:
function ($passable) use ($destination) { return $destination($passable); })($request)
最终,我们从 return $destination($passable) 中返回一个 Response 类的实例,也就是,第二层的 $response = $next($request) 语句成功得到了结果,接着执行下面的语句,也就是 M2-2 点位,最后第二层闭包返回结果,也就是第一层闭包的 $response = $next($request) 语句成功得到了结果,然后执行这一层闭包该语句后面的语句,即 M1-2 点位,该点位之后,第一层闭包也成功返回结果,于是,then 方法最终得到了返回结果。
整个过程过来,程序经过的点位顺序是这样的:M1-1→M2-1→控制器操作→M2-2→M1-2→返回结果。
总结
整个过程看起来虽然复杂,但不管中间件有多少层,只要理解了前后两层中间件的这种递推关系,洋葱是怎么一层层剥开又一层层返回的,来多少层都不在话下。
以上是ThinkPHP6.0管道模式与中间件的实现分析的详细内容。更多信息请关注PHP中文网其他相关文章!

thinkphp是国产框架。ThinkPHP是一个快速、兼容而且简单的轻量级国产PHP开发框架,是为了简化企业级应用开发和敏捷WEB应用开发而诞生的。ThinkPHP从诞生以来一直秉承简洁实用的设计原则,在保持出色的性能和至简的代码的同时,也注重易用性。

本篇文章给大家带来了关于thinkphp的相关知识,其中主要介绍了关于使用think-queue来实现普通队列和延迟队列的相关内容,think-queue是thinkphp官方提供的一个消息队列服务,下面一起来看一下,希望对大家有帮助。

thinkphp基于的mvc分别是指:1、m是model的缩写,表示模型,用于数据处理;2、v是view的缩写,表示视图,由View类和模板文件组成;3、c是controller的缩写,表示控制器,用于逻辑处理。mvc设计模式是一种编程思想,是一种将应用程序的逻辑层和表现层进行分离的方法。

本篇文章给大家带来了关于thinkphp的相关知识,其中主要介绍了使用jwt认证的问题,下面一起来看一下,希望对大家有帮助。

thinkphp扩展有:1、think-migration,是一种数据库迁移工具;2、think-orm,是一种ORM类库扩展;3、think-oracle,是一种Oracle驱动扩展;4、think-mongo,一种MongoDb扩展;5、think-soar,一种SQL语句优化扩展;6、porter,一种数据库管理工具;7、tp-jwt-auth,一个jwt身份验证扩展包。

thinkphp查询库是否存在的方法:1、打开相应的tp文件;2、通过“ $isTable=db()->query('SHOW TABLES LIKE '."'".$data['table_name']."'");if($isTable){...}else{...}”方式验证表是否存在即可。

本篇文章给大家带来了关于ThinkPHP的相关知识,其中主要整理了使用think-queue实现redis消息队列的相关问题,下面一起来看一下,希望对大家有帮助。

在thinkphp3.2中,可以利用define关闭调试模式,该标签用于变量和常量的定义,将入口文件中定义调试模式设为FALSE即可,语法为“define('APP_DEBUG', false);”;开启调试模式将参数值设置为true即可。


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

mPDF
mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

记事本++7.3.1
好用且免费的代码编辑器

MinGW - 适用于 Windows 的极简 GNU
这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。

Atom编辑器mac版下载
最流行的的开源编辑器

SublimeText3 Linux新版
SublimeText3 Linux最新版