从管道说起
Pipeline
是管道的意思。比如作为一个合格的韭菜,我们首先需要读小学、初中、高中等等,每个阶段你都会被修剪处理保证最后的产品是符合社会预期的,然后毕业了你就可以被收割了。
上面的小学、初中、高中就是管子,韭菜就是工序上需要处理的东西。反映到Laravel
中,各种内置中间件、自定义中间价都是管子,每一个Request
都是需要处理的东西。
这里我们先不探讨Laravel中的设计,我们先来看array_reduce
这个函数,最常见的例子就是求和。
array_reduce([1,2,3], function($carry, $item) {
return $carry + $item;
}, 0)
上面的例子返回了6,简单而富有表现力。再来下面的代码
$a = function() {
echo 'a' . PHP_EOL;
};
$b = function() use($a) {
echo 'b' . PHP_EOL;
$a();
};
$c = function() use($b) {
echo 'c' . PHP_EOL;
$b();
};
var_dump($c);
$c();
以上代码会输出如下内容,简单来说就是形成了嵌套的闭包。调用结果显示,最后面的闭包是最先执行的,最开始的闭包是最后执行的,这个行为类似于栈。
object(Closure)#3 (1) {
["static"]=>
array(1) {
["b"]=>
object(Closure)#2 (1) {
["static"]=>
array(1) {
["a"]=>
object(Closure)#1 (0) {
}
}
}
}
}
c
b
a
再来看下面的结构
/*定义一个 中间件接口*/
interface Middleware
{
public static function handle(Closure $next);
}
/*定义一个 csrf验证类*/
class VerifyCsrfToken implements Middleware
{
public static function handle(Closure $next)
{
echo "验证Csrf-Token"."<br/> \n";
$next();
}
}
/*定义一个 错误分享类*/
class ShareErrorsFromSession implements Middleware
{
public static function handle(Closure $next)
{
echo "如果session中有'errors'变量,则共享它"."<br/> \n";
$next();
}
}
/*定义一个 开启session类*/
class StartSession implements Middleware
{
public static function handle(Closure $next)
{
echo "开启session,获取数据"."<br/> \n";
$next();
echo "报错数据,关闭session"."<br> \n";
}
}
/*定义一个 添加cookie队列至响应 类*/
class AddQueuedCookiesToResponse implements Middleware
{
public static function handle(Closure $next)
{
$next();
echo "添加下一次请求需要的cookie"."<br/> \n";
}
}
/*定义一个 加密cookie类*/
class EncryptCookies implements Middleware
{
public static function handle(Closure $next)
{
echo "对输入请求的cookie进行解密"."<br/> \n";
$next();
echo "对输出响应的cookie进行加密"."<br/> \n";
}
}
/*定义一个 检测维护状态类*/
class ChecKForMaintenanceMode implements Middleware
{
public static function handle(Closure $next)
{
echo "确定当前程序是否处于维护状态"."<br/> \n";
$next();
}
}
function getSlice()
{
return function($stack, $pipe)
{
return function() use ($stack, $pipe) {
return $pipe::handle($stack);
};
};
}
function then()
{
$pipes = [
"ChecKForMaintenanceMode",
"EncryptCookies",
"AddQueuedCookiesToResponse",
"StartSession",
"ShareErrorsFromSession",
"VerifyCsrfToken"
];
$firstSlice = function() {
echo "请求向路由器传递,返回响应"."<br/> \n";
};
// print_r($pipes);
$pipes = array_reverse($pipes);
$reduce = array_reduce($pipes, getSlice(), $firstSlice);
// var_dump($reduce);
call_user_func($reduce);
}
以上例子会有如下输出:
确定当前程序是否处于维护状态<br/>
对输入请求的cookie进行解密<br/>
开启session,获取数据<br/>
如果session中有'errors'变量,则共享它<br/>
验证Csrf-Token<br/>
请求向路由器传递,返回响应<br/>
保存数据,关闭session<br>
添加下一次请求需要的cookie<br/>
对输出响应的cookie进行加密<br/>
重新审视Laravel的Pipeline设计
再回头看Laravel
的Pipeline
设计,和我们上面的例子差别不大。其中carry
函数的设计更加复杂些。
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);
// 如果管道不是对象,例如Laravel中的can:update,post 这类用字符串表示,但是背后是绑定的中间件的东西,从容器中拿到实例然后组成参数。
$pipe = $this->getContainer()->make($name);
$parameters = array_merge([$passable, $stack], $parameters);
} else {
// 管道是对象,直接组成参数使用即可。
$parameters = [$passable, $stack];
}
// 调用
$response = method_exists($pipe, $this->method)
? $pipe->{$this->method}(...$parameters)
: $pipe(...$parameters);
// 返回
return $response instanceof Responsable
? $response->toResponse($this->container->make(Request::class))
: $response;
};
};
}
整个Laravel Http Kernal其实就是使用的管道处理的Request
。
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());
}
最后 我还使用 Js 实现了一个类似的设计。