工作之余抽空看了篇中间件的文章,结合之前自己写过的例子,弄了一个PHP8.0原生注解实现中间件的小demo。核心其实借鉴了一下Laravel的中间件思想,使用array_reduce来处理中间件执行顺序。

首先定义好中间件基类规范接口

interface MiddlewareBase {
    public static function handle(Closure $next) : void;
}

接着先写好两个中间件,分别在前置及后置中加入操作

class AuthMiddleware implements MiddlewareBase {
    public static function handle(Closure $next) : void
    {
        echo '验证是否登陆' . PHP_EOL;
        $next();
        echo PHP_EOL . '返回token';
    }
}
class LogMiddleware implements MiddlewareBase {
    public static function handle(Closure $next) : void
    {
        echo '记录日志:访问用户中心' . PHP_EOL;
        $next();
        echo PHP_EOL . '记录日志:执行时间';
    }
}

再把中间件注解基类写好,构造方法使用可变参数来接收中间件传参,然后提供中间件执行的方法

#[Attribute(Attribute::TARGET_METHOD)]
class Middleware {
    protected array $middleware;

    public function __construct(string ...$middleware){
        $this->middleware = $middleware;
    }

    public function handle(Closure $func) : void
    {
        // 中间件列表去重、过滤一下非中间件类,并反转一下顺序
        $middleware = array_reverse(
            array_filter(array_unique($this->middleware), static fn ($class) => is_subclass_of($class, MiddlewareBase::class))
        );

        call_user_func(array_reduce($middleware, static function ($stack, $pipe) {
            return static fn () => $pipe::handle($stack);
        }, $func));
    }
}

继续完善控制器,定义好需要绑定的中间件

class User {
    #[Middleware(
        AuthMiddleware::class,
        LogMiddleware::class
    )]
    public function info() : void
    {
        echo '姓名:中间件示例';
    }
}

最后把框架执行逻辑补充上,主要是利用反射来追踪注解然后调取中间件执行方法

class Application {
    public function run() : void
    {
        $user = new User();

        try {
            // 使用反射操作控制器
            $reflection = new ReflectionClass($user::class);
            // 获取所有方法
            $methods = $reflection->getMethods();

            foreach ($methods as $method) {
                // 获取方法的所有注解
                $funcAttributes = $method->getAttributes();

                if (!empty($funcAttributes) && $method->isPublic()) {
                    $func = $method->getClosure($user);

                    foreach ($funcAttributes as $attribute) {
                        // 将注解实例化
                        $obj = $attribute->newInstance();

                        // 如果注解为中间件,则执行中间件逻辑
                        if ($obj instanceof Middleware) {
                            $obj->handle($func);
                        }
                    }
                }
            }
        } catch (ReflectionException $e) {
            echo 'error: ' . $e->getMessage();
        }
    }
}

入口文件直接调取框架执行就好

$app = new Application();
$app->run();

当然真实的框架可能不止这些操作,还会涉及到路由、容器、DI、配置读取、ORM、日志及缓存处理等等功能,这里只是简单阐述一下PHP8.0原生注解中间件的实现方式。对于array_reduce处理中间件的原理不懂的童鞋,可以去PHP技术论坛查看大神的文章去理解,放上链接:middleware 原理