ThinkPHP 6.0 多应用模式下的中间件机制详解

前言

在 ThinkPHP 6.0 中,中间件是一个非常重要的功能,特别是在多应用模式下,中间件的执行机制变得更加复杂。本文将详细介绍 ThinkPHP 6.0 + 多应用模式下的中间件触发机制,特别是中间件管道的执行原理。

一、中间件的分类

在 ThinkPHP 6.0 + 多应用模式中,中间件主要分为三类:

  1. 全局中间件 - 在 app/middleware.php 中定义,所有请求都会经过
  2. 应用中间件 - 在 app/admin/middleware.php 中定义,仅在访问 admin 应用时生效
  3. 路由/控制器中间件 - 在路由或控制器中定义,针对特定路由生效

二、服务发现与注册机制

2.1 Composer 自动发现

在项目安装依赖时,Composer 会执行 post-autoload-dump 脚本:

perl 复制代码
{
    "scripts": {
        "post-autoload-dump": [
            "@php think service:discover",
            "@php think vendor:publish"
        ]
    }
}

2.2 服务发现命令

think service:discover 命令会扫描 vendor/composer/installed.json,查找所有包的 extra.think.services 配置,例如 think-multi-app 的配置:

json 复制代码
{
    "extra": {
        "think": {
            "services": [
                "think\app\Service"
            ]
        }
    }
}

2.3 生成服务清单

命令执行后会生成 vendor/services.php 文件:

php 复制代码
<?php 
// This file is automatically generated at:2025-12-25 11:44:34
declare (strict_types = 1);
return array (
  0 => 'think\app\Service',
  1 => 'think\trace\Service',
);

2.4 服务注册流程

  1. 应用启动 → 执行初始化器
  2. 服务注册RegisterService 读取 vendor/services.php 并注册所有服务
  3. 服务启动BootService 调用所有服务的 boot() 方法

三、MultiApp 中间件的注册

3.1 事件监听器注册

think\app\Service::boot() 方法中注册了 HttpRun 事件监听器:

csharp 复制代码
public function boot()
{
    $this->app->event->listen('HttpRun', function () {
        $this->app->middleware->add(MultiApp::class);
    });
    // ...
}

3.2 事件触发机制

vendor/topthink/framework/src/think/Http.phprunWithRequest() 方法中:

kotlin 复制代码
protected function runWithRequest(Request $request)
{
    // 加载全局中间件
    $this->loadMiddleware();

    // 触发 HttpRun 事件
    $this->app->event->trigger(HttpRun::class);

    return $this->app->middleware->pipeline()
        ->send($request)
        ->then(function ($request) {
            return $this->dispatchToRoute($request);
        });
}

HttpRun 事件被触发时,之前注册的监听器会被执行,将 MultiApp 中间件添加到中间件队列中。

四、中间件管道的详细解析

4.1 中间件队列结构

Middleware 类中,中间件以队列形式存储:

dart 复制代码
protected $queue = [
    'global' => [/* 全局中间件 */],
    'app' => [/* 应用中间件 */],
    'route' => [/* 路由中间件 */],
    'controller' => [/* 控制器中间件 */]
];

4.2 管道执行机制详解

pipeline() 方法是中间件执行的核心,让我们详细分析:

php 复制代码
public function pipeline(string $type = 'global')
{
    return (new Pipeline())
        ->through(array_map(function ($middleware) {
            return function ($request, $next) use ($middleware) {
                [$call, $params] = $middleware;
                if (is_array($call) && is_string($call[0])) {
                    $call = [$this->app->make($call[0]), $call[1]];
                }
                $response = call_user_func($call, $request, $next, ...$params);
                
                if (!$response instanceof Response) {
                    throw new LogicException('The middleware must return Response instance');
                }
                return $response;
            };
        }, $this->sortMiddleware($this->queue[$type] ?? [])))
        ->whenException([$this, 'handleException']);
}

这段代码可以分解为以下几个步骤:

步骤1: 排序中间件

kotlin 复制代码
$this->sortMiddleware($this->queue[$type] ?? [])

首先获取指定类型的所有中间件,并根据配置的优先级进行排序。

步骤2: 包装中间件为闭包函数

php 复制代码
array_map(function ($middleware) {
    return function ($request, $next) use ($middleware) {
        [$call, $params] = $middleware;
        if (is_array($call) && is_string($call[0])) {
            $call = [$this->app->make($call[0]), $call[1]];
        }
        $response = call_user_func($call, $request, $next, ...$params);
        
        if (!$response instanceof Response) {
            throw new LogicException('The middleware must return Response instance');
        }
        return $response;
    };
}, $this->sortMiddleware($this->queue[$type] ?? []))

这里使用 array_map 将每个中间件包装成一个闭包函数。每个闭包函数都接收两个参数:

  • $request: 当前请求对象
  • $next: 下一个中间件的执行函数

闭包函数内部的执行逻辑:

  1. 解析中间件信息,获取调用方法和参数
  2. 如果是类方法形式,使用容器实例化中间件类
  3. 执行中间件的 handle 方法
  4. 验证返回值必须是 Response 实例

步骤3: 创建管道并设置中间件

scss 复制代码
(new Pipeline())
    ->through( /* 上面生成的闭包数组 */ )
    ->whenException([$this, 'handleException']);

创建 Pipeline 实例,并设置中间件列表和异常处理器。

4.3 Pipeline 类的执行原理

Pipeline 类使用了函数式编程的 array_reduce 模式来构建中间件执行链:

perl 复制代码
public function then(Closure $destination)
{
    $pipeline = array_reduce(
        array_reverse($this->pipes),  // 反转中间件数组
        $this->carry(),              // 传递函数
        function ($passable) use ($destination) {
            return $destination($passable);  // 最终执行目标
        }
    );
    return $pipeline($this->passable);  // 执行整个管道
}

carry() 方法的实现:

php 复制代码
protected function carry()
{
    return function ($stack, $pipe) {
        return function ($passable) use ($stack, $pipe) {
            try {
                return $pipe($passable, $stack);  // 执行当前中间件,传入下一个中间件作为 $next
            } catch (Throwable | Exception $e) {
                return $this->handleException($passable, $e);
            }
        };
    };
}

4.4 执行链的构建过程

假设有三个中间件 A、B、C,执行链的构建过程如下:

  1. 反转数组: [C, B, A]

  2. 从右到左构建执行链:

    • 初始值: final_handler (最终执行函数)
    • 处理 A: A(request, final_handler)
    • 处理 B: B(request, A(request, final_handler))
    • 处理 C: C(request, B(request, A(request, final_handler)))

这样就形成了一个洋葱模型的执行链,请求从外到内经过每个中间件,再从内到外返回响应。

五、MultiApp 中间件的特殊处理

MultiApp 中间件的 handle 方法会检查是否为多应用请求,如果是,则会执行应用级中间件:

perl 复制代码
public function handle($request, Closure $next)
{
    if (!$this->parseMultiApp()) {
        return $next($request);
    }

    return $this->app->middleware->pipeline('app')  // 执行应用中间件
        ->send($request)
        ->then(function ($request) use ($next) {
            return $next($request);  // 继续执行后续中间件
        });
}

六、应用级中间件的自动加载

MultiApp::loadApp() 方法中,会自动加载应用目录下的中间件配置:

php 复制代码
if (is_file($appPath . 'middleware.php')) {
    $this->app->middleware->import(include $appPath . 'middleware.php', 'app');
}

这解释了为什么在 app/admin/middleware.php 中定义的中间件会被自动加载。

七、完整执行流程图

markdown 复制代码
请求到达
    ↓
触发 HttpRun 事件
    ↓
MultiApp 中间件被添加到队列
    ↓
执行全局中间件管道
    ↓
MultiApp 检测是否为多应用
    ↓
如果是多应用 → 执行应用级中间件管道
    ↓
执行路由/控制器中间件
    ↓
执行最终控制器方法
    ↓
返回响应

八、实际示例

假设我们有以下目录结构:

perl 复制代码
app/
├── middleware.php          # 全局中间件
├── admin/
│   └── middleware.php      # admin应用中间件
└── index/
    └── middleware.php      # index应用中间件

当访问 http://yourdomain.com/admin/index 时,执行顺序为:

  1. 全局中间件 → app/middleware.php
  2. MultiApp 中间件 → 检测到是 admin 应用
  3. 应用中间件 → app/admin/middleware.php (通过 $this->app->middleware->pipeline('app'))
  4. 路由中间件 → 路由定义的中间件
  5. 控制器中间件 → 控制器中的中间件
  6. 控制器方法 → app/admin/controller/Index.php

总结

ThinkPHP 6.0 的多应用中间件机制通过事件系统、服务注册和管道模式实现了灵活的中间件执行流程。整个机制的核心在于:

  1. 服务发现 → 通过 Composer 自动发现和注册服务
  2. 事件驱动 → 通过 HttpRun 事件动态添加 MultiApp 中间件
  3. 管道模式 → 通过 Pipeline 模式串联所有中间件,使用函数式编程的 array_reduce 模式构建洋葱模型执行链
  4. 应用识别MultiApp 中间件负责识别应用并加载应用级中间件
相关推荐
格格步入1 天前
支付幂等:一锁二判三更新
后端
技术小泽1 天前
搜索系统架构入门篇
java·后端·算法·搜索引擎
+VX:Fegn08951 天前
计算机毕业设计|基于springboot + vue酒店预约系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
踏浪无痕1 天前
告警的艺术:从 node_exporter 指标到生产级规则
后端·架构·监控
源码获取_wx:Fegn08951 天前
基于springboot + vue酒店预约系统
java·vue.js·spring boot·后端·spring
我想问问天1 天前
【从0到1大模型应用开发实战】03|写一个可解释的RAG规则检索器
后端·aigc
豆浆Whisky1 天前
6小时从0到1:我用AI造了个分片SQL生成器
后端·sql·ai编程
风的归宿551 天前
解构内存迷宫:串联虚拟地址、页表与内存使用(一)
后端
武子康1 天前
大数据-202 sklearn 决策树实战:criterion、Graphviz 可视化与剪枝防过拟合
大数据·后端·机器学习