frankenphp 的 watch: true 仅向当前 worker 发送 sigterm,不自动重启;是否拉起新 worker 取决于 caddy 进程管理逻辑,且需配合 opcache 重置、框架缓存预热及路径精准过滤才能生效。

FrankenPHP 的 watch: true 会触发 Worker 重启,但不是“监听→重启”那么简单
FrankenPHP 1.2+ 的 watch: true 确实会在文件变更时向当前 Worker 进程发送 SIGTERM,但它不会“自动重启”——真正决定是否拉起新 Worker 的是 Caddy 的进程管理逻辑。如果你看到 Worker 频繁重启,大概率不是 watch 本身有问题,而是:文件被高频写入(如日志、缓存、IDE 临时文件)、watch 路径没过滤 vendor 或 node_modules、或 OPcache 没配合重置,导致新 Worker 启动后立刻因字节码不一致而异常退出。
怎么配置 watch 才不误杀 Worker
默认开启 watch: true 是危险的,它会监听整个项目目录下所有 .php、.yaml、.env 文件,包括你根本不想让它反应的路径。必须显式收窄范围:
-
watch只应启用在明确需要热更的业务层,例如:watch: ["src/", "config/", "templates/"],绝对不要写watch: ["."] - 排除
vendor/和node_modules/—— Composer 包更新必须走完整重启,watch 对它们无效,反而引发冲突 - 禁用对
var/cache/和var/log/的监听:这些目录被框架频繁写入,每次写入都会触发一次SIGTERM,Worker 来不及处理完请求就被终止 - 确保
frankenphp.yaml中未同时启用max_requests和watch:二者叠加会导致“刚启就杀”,因为新 Worker 可能还没处理完第一个请求,watch 就又检测到某个文件时间戳变了
watch 生效时 PHP 代码为何还是旧的?三个关键漏点
即使 watch 正确触发了 SIGTERM 并拉起新 Worker,你也可能发现控制器没变——这不是 watch 失效,而是运行时没刷新:
-
opcache.validate_timestamps=0在 CLI 模式下默认为 false:FrankenPHP 的 Worker 进程启动于 CLI SAPI,若 php.ini 中该值为 0,即使文件变了,OPcache 也不会重新编译,新 Worker 加载的仍是旧字节码 - 没有在入口文件中调用
opcache_reset():watch 不会自动清理 OPcache,你得在frankenphp_handle_request()回调开始前手动加一句(仅开发环境推荐) - Symfony/Laravel 缓存未失效:新 Worker 启动后仍加载
var/cache/prod/Container*.php,而这个文件是由旧代码生成的;必须确保cache:warmup在部署后执行,且新版本的APP_ENV与缓存目录名严格匹配
比 watch 更稳的替代方案:用 stop-workers 控制节奏
watch 适合开发调试,但生产发布必须换方式。FrankenPHP 提供的 frankenphp stop-workers 命令才是可控滚动更新的核心:
- 它不依赖文件系统监听,而是主动向所有活跃 Worker 发送
SIGTERM,让它们完成手头请求后再优雅退出 - 配合
max_requests: 5000使用,可实现“自然轮换”:Worker 达到请求数上限后自动退出,Caddy 拉起新实例,全程无请求中断 - 发布脚本里应先部署新代码、预热缓存、执行
opcache_reset()(如果用了 OPcache),再执行frankenphp stop-workers,而不是靠 watch 被动响应 - 别在 CI/CD 流水线里用
watch:它无法保证多实例间的一致性,集群环境下极易出现部分节点用新代码、部分仍跑旧逻辑
真正容易被忽略的是:watch 不解决“代码已变但运行时没重载”的问题,它只负责发信号。Worker 是否能正确加载新逻辑,取决于 OPcache、框架缓存、以及你有没有在入口里重置关键状态——这些都得自己兜底。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











