搜索
首页php框架ThinkPHPThinkPHP6.0管道模式与中间件的实现分析

说明

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中文网其他相关文章!

声明
本文转载于:learnku。如有侵权,请联系admin@php.cn删除
thinkphp是不是国产框架thinkphp是不是国产框架Sep 26, 2022 pm 05:11 PM

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

一起聊聊thinkphp6使用think-queue实现普通队列和延迟队列一起聊聊thinkphp6使用think-queue实现普通队列和延迟队列Apr 20, 2022 pm 01:07 PM

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

thinkphp的mvc分别指什么thinkphp的mvc分别指什么Jun 21, 2022 am 11:11 AM

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

实例详解thinkphp6使用jwt认证实例详解thinkphp6使用jwt认证Jun 24, 2022 pm 12:57 PM

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

thinkphp扩展插件有哪些thinkphp扩展插件有哪些Jun 13, 2022 pm 05:45 PM

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 怎么查询库是否存在thinkphp 怎么查询库是否存在Dec 05, 2022 am 09:40 AM

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

一文教你ThinkPHP使用think-queue实现redis消息队列一文教你ThinkPHP使用think-queue实现redis消息队列Jun 28, 2022 pm 03:33 PM

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

thinkphp3.2怎么关闭调试模式thinkphp3.2怎么关闭调试模式Apr 25, 2022 am 10:13 AM

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

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

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

AI Clothes Remover

AI Clothes Remover

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

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
2 周前By尊渡假赌尊渡假赌尊渡假赌
仓库:如何复兴队友
1 个月前By尊渡假赌尊渡假赌尊渡假赌
Hello Kitty Island冒险:如何获得巨型种子
4 周前By尊渡假赌尊渡假赌尊渡假赌

热工具

mPDF

mPDF

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

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

MinGW - 适用于 Windows 的极简 GNU

MinGW - 适用于 Windows 的极简 GNU

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

Atom编辑器mac版下载

Atom编辑器mac版下载

最流行的的开源编辑器

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版