站点图标

中间件实现 [PHP]

2020-03-29折腾记录PHP / Middleware
本文最后更新于 410 天前,文中所描述的信息可能已发生改变

中间件是什么?

要实现中间件,首先就需要知道中间件是什么。中间件是很多 PHP 框架中都提供的功能,中间件提供了一种方便的机制过滤进入应用程序的 HTTP 请求。这么说可能会比较抽象,我们就举个具体的例子吧。

比如某个商城应用,当用户把商品加入购物车的时候和购买的时候,我们需要验证用户是否已经登录,传统的方式是在执行每个业务之前判断是否登录,如下演示代码:


_20
<?php
_20
function addToCart() {
_20
if (!isLogin()) {
_20
return "用户未登录";
_20
}
_20
// ... 添加到购物车操作
_20
return "添加到购物车成功";
_20
}
_20
_20
function buy() {
_20
if (!isLogin()) {
_20
return "用户未登录";
_20
}
_20
// ... 购买操作
_20
return "购买成功";
_20
}
_20
_20
function entry() {
_20
// addToCart or buy
_20
}

可以看到,当需要验证用户的时候就需要写重复的代码,那么我们为什么不把验证的部分另外抽出来呢,如下:


_17
<?php
_17
function addToCart() {
_17
// ... 添加到购物车操作
_17
return "添加到购物车成功";
_17
}
_17
_17
function buy() {
_17
// ... 购买操作
_17
return "购买成功";
_17
}
_17
_17
function entry() {
_17
if (!isLogin()) {
_17
return "用户未登录";
_17
}
_17
// addToCart or buy
_17
}

可以看到这样遇到相同需要验证的时候就不再需要写重复的验证代码了,而这种做法其实就是中间件的思想,抽离出来的验证登录就是一个中间件。有了中间件我们就可以这些操作,比如权限验证、CSRF 验证等等都写在中间件里,然后通过使用不同的中间件组合不仅能够实现需求还降低了代码的耦合度。

中间件

比较常见的中间件模型有两种,一种是洋葱模型,一种是切面模型,其实这两个可以看成是一种,不过分开来比较好理解 2333。

左:洋葱模型,右:切面模型

这两个图看起来是不是有点吓人,切面模型其实还比较好理解,洋葱模型看起来就有点懵了,是不是有点像函数的嵌套调用 Middleware2(Middleware1(App()))?其实这并不是这样的,因为函数的嵌套调用是先执行内层的函数,然后才会执行外层的函数。若你经常使用回调函数方式的编程,那么你就能发现这其实像闭包嵌套:


_28
<?php
_28
function middleware2() {
_28
echo "Start Middleware2\n";
_28
middleware1();
_28
echo "End Middleware2\n";
_28
}
_28
_28
function middleware1() {
_28
echo "Start Middleware1\n";
_28
app();
_28
echo "End Middleware1\n";
_28
}
_28
_28
function app() {
_28
echo "App\n";
_28
}
_28
_28
function entry() {
_28
middleware2();
_28
}
_28
_28
entry();
_28
_28
// Start Middleware2
_28
// Start Middleware1
_28
// App
_28
// End Middleware1
_28
// End Middleware2

优雅的实现

可以看到上面的实现是写死的,如果要增加或者动态使用中间件就极为麻烦,所以我们需要对其进行改造,改造成可以动态调用的中间件。这也是我在 XK-PHP 中使用的方法。


_58
<?php
_58
// 中间件
_58
class Authenticate
_58
{
_58
public function handle($request, Closure $next)
_58
{
_58
echo "Start 登录\n";
_58
$response = $next($request);
_58
echo "End 登录\n";
_58
return $response;
_58
}
_58
}
_58
_58
class SimpleMiddleware
_58
{
_58
public function handle($request, Closure $next)
_58
{
_58
echo "Start SimpleMiddleware\n";
_58
$response = $next($request);
_58
echo "End SimpleMiddleware\n";
_58
return $response;
_58
}
_58
}
_58
_58
// App
_58
class App
_58
{
_58
public function run($request)
_58
{
_58
return "App-$request\n";
_58
}
_58
}
_58
_58
// 中间件处理器
_58
$middlewares = [
_58
Authenticate::class,
_58
SimpleMiddleware::class
_58
];
_58
_58
$next = function ($request) {
_58
return (new App)->run($request);
_58
};
_58
_58
foreach ($middlewares as $middleware) {
_58
$next = function ($request) use ($next, $middleware) {
_58
return (new $middleware)->handle($request, $next);
_58
};
_58
}
_58
_58
$response = $next("request");
_58
_58
echo $response;
_58
_58
// Start SimpleMiddleware
_58
// Start 登录
_58
// End 登录
_58
// End SimpleMiddleware
_58
// App-request

这段代码如果不熟悉闭包的看起来可能有点懵,不过仔细理解下就很简单了。

在中间件的类中有一个 handle 函数用于处理请求或响应,外部向该函数传入了一个 $next 闭包,这个闭包其实就是后续的 中间件App 的打包成的 闭包。通过循环就可以不断的组成新的闭包,合并所有中间件后就可以执行了,此时只需要执行最终的闭包,最终的闭包会不断的嵌套调用 $next 闭包,直到最后的 App 其实就是个递归的过程。不过我这里干讲也无法讲清楚,最好还是要利用 IDE 一步一步调试才能更好的理解。

更优雅的实现

第二个实现虽然已经很好了,但是并不符合 PSR-15 的标准,所以如果要更优雅的方式来实现中间件的话需要按照 PSR-15 的标准来实现,这样就可以复用 PSR-15 的中间件,并且也符合 PSR-7 消息接口的规范。具体的代码这里就不贴了,请移步 Github 查看。

中间件实现 [PHP]

https://blog.ixk.me/post/middleware-implementation-with-php
  • 许可协议

    BY-NC-SA

  • 发布于

    2020-03-29

  • 本文作者

    Otstar Lin

转载或引用本文时请遵守许可协议,注明出处、不得用于商业用途!

浅谈 DI 和 IoC告别 Windows 终端的难看难用,打造好用的 PowerShell