Worker模式下框架初始化必须在循环外完成,即自动加载、配置加载、服务容器构建、数据库连接等仅执行一次;请求处理逻辑(如$request捕获、$kernel->handle()、$response->send()及$kernel->terminate())须置于frankenphp_handle_request()回调内,并手动调用gc_collect_cycles()清理资源。

worker 模式下框架初始化必须在循环外完成
FrankenPHP 的 worker 模式本质是让 PHP 进程常驻,所以框架初始化(自动加载器注册、配置加载、服务容器构建、数据库连接建立)**只能执行一次**,否则就退化成传统 FPM 模式,失去性能优势。关键不是“能不能”,而是“不这么做就会白开 worker”——每次请求都重跑 bootstrap/app.php,CPU 和内存开销几乎没降。
入口脚本要拆成「启动期」和「请求期」两段逻辑
以 Laravel 为例,public/index.php 原来是“一次请求、一整套流程”,现在得手动切开:
- 把
require __DIR__.'/../vendor/autoload.php';、$app = require_once __DIR__.'/../bootstrap/app.php';、$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);这些**只执行一次**的代码,移到frankenphp_handle_request()循环外; - 把
$request = Illuminate\Http\Request::capture();、$response = $kernel->handle($request);、$response->send();、$kernel->terminate($request, $response);这些**每个请求都要跑**的逻辑,包进while (frankenphp_handle_request()) { ... }循环内; - 别漏掉
$kernel->terminate()—— 它负责清理响应级资源(如 session 写入、日志刷盘),不调用会导致 session 丢失或日志不落盘。
常见错误:静态变量/单例跨请求污染
即使你把初始化挪到循环外,框架内部若用了 static 或全局缓存(比如 MyClass::$cache = [];),这些值会在下一个请求里原样存在。FrankenPHP 不提供自动重置机制,也不存在 ResetInterface 这种东西。
- 检查所有服务提供者(
AppServiceProvider等)里有没有在boot()中赋值static属性; - 避免在中间件或控制器里直接修改
$_SESSION或$_SERVER全局数组,它们不会自动清空; - 数据库连接复用没问题,但必须确保事务已提交或回滚(
$db->commit()或$db->rollback()),否则下一个请求会卡在未结束事务上; - 用
gc_collect_cycles()在terminate()后手动触发 GC,尤其当有大量临时对象或闭包引用时。
验证是否真复用了初始化结果
上线前必须实测,不能只看代码结构。最简单的验证方式:
立即学习“PHP免费学习笔记(深入)”;
- 在
bootstrap/app.php开头加file_put_contents('/tmp/app_booted.log', date('H:i:s')."\n", FILE_APPEND);,发起 10 次请求,确认日志只新增一行; - 在某个服务提供者的
register()方法里打日志,同样只应出现一次; - 打印
spl_object_id(app('db.connection')),连续请求中 ID 必须不变;如果变了,说明连接被重建了,初始化没真正移出去。
最容易被忽略的是框架底层对 $_SERVER 的依赖——比如 Laravel 的 url() 辅助函数会读取 $_SERVER['HTTPS'] 和 $_SERVER['HTTP_X_FORWARDED_PROTO'],如果 Caddy 没配好这些字段,每次请求都会触发 URL 生成器的 fallback 逻辑,间接导致额外初始化。



















