FrankenPHP 的 Symfony 中间件支持需启用 worker 模式并正确包裹 Kernel::handle(),否则中间件不触发;public/index.php 必须替换为官方 frankenphp-worker 入口,避免重复初始化 Kernel。

FrankenPHP 对 Symfony 的中间件支持不是开箱即用的“自动适配”,而是依赖于它如何接管请求生命周期——关键在于你是否启用 worker 模式,以及 Symfony 的 Kernel::handle() 是否被正确包裹。
worker 模式下中间件只执行一次?
是的,这是最常被忽略的陷阱。在 worker 模式下,Symfony Kernel 实例常驻内存,Kernel::boot() 只运行一次,但中间件(尤其是前置/后置)仍会在每个请求中按顺序执行——前提是它们被注册在标准 HTTP Kernel 栈里(即 config/packages/framework.yaml 中的 http_middleware 或通过 Kernel::configureContainer() 注入)。但如果你手动绕过 Kernel::handle()(比如直接调用控制器),中间件就完全不会触发。
常见错误现象:Request 对象里看不到自定义中间件注入的属性、日志没打、CORS 头缺失、Auth token 未校验。
- 确保你的中间件类实现了
MiddlewareInterface或继承AbstractSessionListener等标准基类 - 不要在
public/index.php里提前new Kernel()或调用$kernel->boot(),FrankenPHP 的frankenphp-worker入口会自动处理 - 检查
APP_ENV=prod下中间件是否被缓存优化掉:运行php bin/console debug:middleware确认列表完整
Symfony 的 public/index.php 必须改写
默认 Symfony 的 public/index.php 是为传统 CGI/FPM 设计的,会重复初始化 Kernel,跟 FrankenPHP 的 worker 模式冲突。必须替换成 FrankenPHP 官方推荐的入口文件。
立即学习“PHP免费学习笔记(深入)”;
使用 frankenphp-worker 入口时,Symfony 的启动逻辑要收束到一个可复用的闭包里:
<?php
// public/index.php
use SymfonyComponentHttpFoundationRequest;
if (!class_exists('FrankenPHP\Worker')) {
throw new RuntimeException('FrankenPHP not available');
}
FrankenPHPWorker::start(function (Request $request) {
// 这里才是你真正的 Kernel 初始化点
$kernel = new Kernel($_ENV['APP_ENV'], (bool) $_ENV['APP_DEBUG']);
$kernel->boot();
return $kernel->handle($request);
});
注意:$kernel->boot() 放在闭包外会导致每次请求都重 boot;放在闭包内又失去常驻优势。正确做法是让 FrankenPHPWorker::start() 内部管理单例生命周期——所以实际应把 $kernel 提前实例化并缓存,或依赖 FrankenPHP 自动复用已 boot 的 Kernel(需确认 Symfony 版本 ≥ 6.3 + FrankenPHP ≥ 1.2)。
前置中间件访问不到 $_SERVER 或 $_COOKIE?
FrankenPHP 默认不自动填充 PHP 超全局变量($_GET、$_POST、$_COOKIE、$_SERVER),它把原始请求数据以 Request 对象形式传入,而 Symfony 中间件默认读取的是超全局数组。这会导致依赖 $_SERVER['HTTP_AUTHORIZATION'] 的 JWT 验证中间件失效,或 $_COOKIE 为空。
- 在
public/index.php的 worker 闭包开头,手动同步关键字段:$_SERVER = $request->server->all(); $_COOKIE = $request->cookies->all(); - 更推荐的方式:重写中间件,直接从
$request对象取值,例如$request->headers->get('authorization'),避免耦合超全局变量 - 若使用
symfony/runtime,确认SymfonyRuntime配置已启用populateServerVars: true
真正难调的不是配置,而是中间件里那些隐式依赖“每次请求都全新启动”的假设——比如单例服务里缓存了上个请求的用户 ID,或数据库连接在 __destruct() 里才关闭。FrankenPHP 的常驻模型会让这类 bug 在压测时突然爆发,而不是开发阶段报错。



















