OPcache黑名单不是“开了就生效”的开关,而是静态排除文件路径,命中后文件永远不进缓存池,仅跳过编译而非禁用缓存。

为什么黑名单不是“开了就生效”的开关
OPcache 黑名单(opcache.blacklist_filename)只对文件路径做静态排除,不支持通配符动态匹配、不走 glob 展开、也不检查文件内容变更。它本质是“跳过编译”,而不是“禁用缓存”。一旦某个 PHP 文件被黑名单命中,它就永远不进 OPcache 缓存池——哪怕你改了代码、重启 FPM,只要路径还在黑名单里,它就始终直译执行。
常见误判:看到翻译文件更新了,就以为黑名单没起作用;其实恰恰相反——黑名单生效了,所以改完 .yml/.xlf 后 PHP 没去重编译缓存,反而让新翻译立刻可见。但如果你把 index.php 也加进黑名单,那它就彻底失去 OPcache 加速,页面响应会明显变慢。
怎么写 opcache_blacklist.txt 才不踩坑
黑名单文件必须是纯文本,每行一个路径或 glob 模式,且路径需与 realpath() 输出完全一致(注意软链接、相对路径、大小写)。PHP 8.5.5 不校验文件是否存在,只做字符串前缀匹配。
-
/var/www/app/translations/*.yml→ 有效,匹配所有 yml 文件 -
/var/www/app/var/cache/translations/→ 无效,结尾带斜杠不匹配任何文件;应写成/var/www/app/var/cache/translations/* -
translations/*.xlf→ 无效,相对路径不解析;必须用绝对路径 -
/var/www/app/src/Helper/→ 无效,目录本身不被缓存;要写成/var/www/app/src/Helper/*.php
建议用 find /var/www/app -name "*.yml" -o -name "*.xlf" | xargs realpath | sort -u > /etc/php/8.5/fpm/opcache_blacklist.txt 生成路径列表,避免手误。
立即学习“PHP免费学习笔记(深入)”;
黑名单和 opcache.validate_timestamps 冲突吗
不冲突,但逻辑上互斥。黑名单让文件“从不缓存”,opcache.validate_timestamps=1 让缓存文件“按需校验”。两者共存时,黑名单优先级更高——被列进去的文件根本不会进入缓存流程,自然也就绕过了时间戳校验。
典型错误配置:
- 把整个
translations/目录加黑名单,同时又设opcache.validate_timestamps=0→ 多余,后者对黑名单文件无意义 - 漏掉
var/cache/translations/,只加了translations/→ Symfony 运行时生成的缓存翻译文件仍会被缓存,改语言后不生效 - 开发环境用
opcache.revalidate_freq=0,却没配黑名单 → 每次请求都 stat 所有翻译文件,IO 拉满
验证黑名单是否真在工作
不能只看 opcache_get_status()['blacklist'] 返回数组长度,那只是读取了文件行数。真正要看的是目标文件是否出现在 opcache_get_status()['scripts'] 列表里。
实操步骤:
- 临时在项目里加一个测试文件
/var/www/app/translations/test.xlf - 触发一次请求(比如访问 /debug/translation)
- 运行
php -r "print_r(array_keys(opcache_get_status()['scripts']));" - 如果输出里没有
/var/www/app/translations/test.xlf的完整路径,说明黑名单生效
注意:opcache.blacklist_filename 修改后必须 systemctl reload php8.5-fpm(不是 restart),否则新规则不加载。FPM 子进程启动时才读一次该文件。
最易被忽略的一点:黑名单路径必须和 Web SAPI 实际打开的文件路径完全一致——比如 Nginx 配置了 root /var/www/app/public,而 PHP 脚本里用 __DIR__.'/../translations/' 包含文件,那黑名单就得写 /var/www/app/translations/,而不是 /var/www/app/public/../translations/。路径归一化这步,没人帮你做。



















