Worker模式下Mercure消息发送失败的典型现象是消息静默丢弃、metrics中publish_errors_total持续上涨、事件流响应为空;根本原因是未在Worker循环外调用frankenphp_mercure_init(),且必须同时配置Caddyfile mercure块和FRANKENPHP_CONFIG="mercure=true"。

Worker模式下Mercure消息发送失败的典型现象
用 frankenphp_handle_request() 包裹入口后,Mercure 消息发不出去,或只在首次请求生效、后续请求静默丢弃;curl http://localhost:2019/metrics 查看时发现 frankenphp_worker_mercure_publish_errors_total 持续上涨;浏览器 DevTools 的 Network 面板看不到 Mercure 事件流响应(HTTP 200 但 body 为空)。
Mercure需要显式启用且与Worker生命周期对齐
FrankenPHP 默认不自动加载 Mercure 模块,即使 Caddyfile 中启用了 mercure 指令,Worker 进程也不会继承该能力——它必须在 Worker 脚本启动时主动初始化。常见错误是只在普通 PHP-FPM 风格入口里调用 mercure_publish(),但在 Worker 模式下,这个函数会因底层连接未建立而静默失败。
- 必须在 Worker 循环外、
while(true)之前调用\frankenphp_mercure_init(),否则每次请求都重连,性能崩坏且易超时 - 若使用 Laravel,不能依赖
MercureBundle的自动注册机制,需手动在chat.php或worker.php中补全初始化逻辑 -
FRANKENPHP_CONFIG环境变量中设置mercure=true仅影响 HTTP 路由层,不影响 Worker 内部的发布能力
Worker内发布Mercure消息的正确调用链
FrankenPHP 的 Mercure 发布不是纯函数调用,而是依赖底层 Go 层预置的 publisher 实例。Worker 脚本必须通过 \frankenphp_mercure_publish()(而非 cURL 或 Guzzle),且参数必须为数组格式,否则会被忽略。
- 错误写法:
file_get_contents('http://localhost/.well-known/mercure', false, $context)—— Worker 不共享主进程的 HTTP 客户端上下文 - 正确写法:
\frankenphp_mercure_publish(['topic' => 'https://example.com/chat', 'data' => json_encode($payload)]) - topic 必须是绝对 URL,相对路径或裸字符串(如
'chat')会被拒绝,日志中报invalid mercure topic - data 字段必须是字符串,不能传 raw array,否则底层序列化失败,无错误抛出但消息不达
Caddyfile 与环境变量的双重约束不能漏
Mercure 功能在 FrankenPHP 中是“可插拔”的:Caddyfile 控制是否暴露 Mercure 端点,环境变量控制 Worker 是否获得发布能力,二者缺一不可。
立即学习“PHP免费学习笔记(深入)”;
- Caddyfile 中必须有
mercure块(哪怕只写mercure { }),否则/mercure路由不生效,Worker 发布时返回 404 - 启动命令必须带
-e FRANKENPHP_CONFIG="worker ./public/worker.php;mercure=true",注意分号分隔多个配置项,不能用空格或换行 - Docker 部署时,
FRANKENPHP_CONFIG必须在docker run命令中显式传入,镜像默认配置不启用 Mercure - 独立二进制运行时,需在 Caddyfile 全局块中加
{ mercure },并确保frankenphp指令后跟mercure=true参数
最容易被忽略的是:Worker 脚本里调用 \frankenphp_mercure_publish() 前,没有检查返回值。这个函数失败时不抛异常,只返回 false,必须手动判断并记录日志,否则问题完全不可见。



















