笔者在研读guzzlehttp
源码时,对中间件的使用研究了一翻,在此纪录学习笔记。
我的理解
在执行主要逻辑时,完成一些附带的逻辑,可以分为前置和后置中间件。附带的逻辑往往带有条件判断,比如验证是否有权限,判断配置信息从而做出相应操作。目的是使各个业务逻辑更清晰、独立、可拆卸。
举例
// 主要逻辑
$meet = function($name){
echo "nice to meet you, $name \n";
};
// 前置中间件
$hello = function($handler){
return function($name)use($handler){
echo "hello ".$name.", may I have your name\n";
$name = 'Lucy';
return $handler($name);
};
};
// 前置中间件
$weather = function($handler){
return function($name)use($handler){
echo 'what a day'."\n";
return $handler($name); // weather_return_handler行
};
};
// 后置中间件
$dinner = function($handler){
return function($name)use($handler){
$return = $handler($name);
$name = 'Lucy';
echo "OK, $name. Will you have dinner with me?\n";
return $return;
};
};
// 中间件栈
$stack = [];
// 打包
function prepare($handler, $stack){
foreach(array_reverse($stack) as $key => $fn){
// echo $key; 记为echo_key行
$handler = $fn($handler); // 记为iterator行
}
return $handler;
}
// 入栈
$stack['dinner'] = $dinner;
$stack['weather'] = $weather;
$stack['hello'] = $hello;
// 把所有逻辑打包成一个闭包(closure)
$run = prepare($meet, $stack);
$run('beauty'); // 记为run_prepare
/* 执行结果:
what a day
hello beauty, may I have your name
nice to meet you, Lucy
OK, Lucy. Will you have dinner with me?
*/
编写规范
中间件要要满足一定的规范:总是返回一个闭包,闭包中总是传入相同的参数(由主要逻辑决定), 闭包总是返回句柄(handler)的执行结果;
如果中间件的逻辑在返回句柄return $handler($name)
前完成,就是前置中间件,否则为后置。这只是一个代码规范,你完全可以在一个中间件中的任何位置写逻辑代码,只要在最后正确返回。
打包程序
中间件的执行顺序是由打包函数(prepare)决定,这里是先入栈先执行,比如weather比hello先执行。
打包函数是一个这里有趣的地方。如果把prepare函数中的echo_key行 注释打开,会发现打包顺序是hello, weather, dinner。
所以,返回的闭包实际上相当于:
$closure = $dinner($weather($hello($meet))); // 记为make_closure行
$closure('beauty'); // 记为run_closure
困惑的地方在于,为什么weather的执行顺序在hello前。常规的函数如A(B(C('hehe')))
, 执行顺序应该是C,B,A。
因为中间件是一个闭包,而且返回一个闭包(满屏尽是闭包),意味着一个中件间要运行两次。外层闭包在打包的时候执行,也就是iterator行 或者 make_closure行,同时决定里层的执行顺序。里层闭包在调用时执行, run_prepare行或run_closure行。
表达式$hello($meet)
返回的是$hello
的里层闭包而不是句柄,因为里层未执行,所以hello beauty
不会出现。也就是说,$weather
的形参$handler
对应的实参就是$hello
的里层闭包。当程序运行到weather_return_handler
行时,$hello
的里层闭包才真正执行,这时$weather
的逻辑已经执行完了。
打包程序的作用可以概括为执行当前中间件的外层闭包,得到里层闭包作为参数,再传给下一个中间件的外层闭包,直至迭代结束。