这类问题最容易被一句“再跑一次 setup:di:compile”带偏。最近遇到的现场是:模块里的构造函数已经增加一个依赖,构建日志显示编译成功,但线上请求仍然报 ArgumentCountError,而且堆栈指向的 Interceptor 明显是旧版本。最终不是 DI 配置写错,而是编译目录、Web进程使用的发布目录以及 OPcache 里缓存的脚本并不属于同一次发布。
先确认你到底在编译哪一份代码
多版本发布通常通过 current 软链接切换。SSH进入的目录可能是旧 release,而 PHP-FPM 已经指向新目录;也可能正好相反。不要凭 pwd 判断,用 realpath 把三个关键路径打印出来。
readlink -f /var/www/shop/current
readlink -f /var/www/shop/current/app/code/Vendor/Module
php -r 'echo realpath("/var/www/shop/current/generated/code"), PHP_EOL;'
然后在报错类和生成类中搜索构造函数。生成的 Interceptor 本身通常不重写构造函数,但 Factory、Proxy 或编译元数据可能仍保存旧参数。
grep -R "class .*Factory" -n generated/code/Vendor/Module | head grep -R "OldDependency" -n generated/code generated/metadata | head
正确的清理顺序
先开启维护模式或把节点摘出负载均衡,再删除当前 release 里的生成物。不要在仍有流量时一边删 generated/code 一边让 PHP 自动生成,否则多个请求会同时创建文件,问题会更乱。
bin/magento maintenance:enable rm -rf generated/code/* generated/metadata/* composer dump-autoload -o bin/magento setup:di:compile bin/magento cache:flush
如果是只读镜像,编译必须在镜像构建阶段完成,并确认 generated 目录随同该 release 发布。容器启动后临时编译,下一次滚动更新很可能又恢复旧内容。
别忘了OPcache和常驻进程
PHP-FPM可能继续执行旧脚本,队列Consumer也不会因为代码目录切换自动重启。完成文件切换后,优雅重载FPM并重启Consumer。具体服务名按环境调整。
sudo systemctl reload php8.2-fpm bin/magento queue:consumers:restart
如果启用了 opcache.validate_timestamps=0,只清 Magento 缓存没有任何作用;必须 reload PHP-FPM,或者让部署系统对每个节点执行等价操作。
最后怎么确认
- 在新 release 中执行 bin/magento dev:di:info 目标类,确认首选实现正确
- 用 php -r 加载目标 Factory,确认文件 realpath 属于 current release
- 连续访问两个以上节点,避免只验证到已经更新的那一台
- 检查新启动Consumer的进程时间,确保不是发布前的旧进程
这套检查的重点不是“多清几次缓存”,而是保证源代码、generated、自动加载器、FPM与Consumer处在同一个发布版本。只要其中一个仍旧,DI编译成功也不能证明运行时一定使用了新代码。

