composer dump-autoload出现Ambiguous class resolution警告,项目仍能启动,所以一直被忽略。后来预生产正常、生产报方法不存在,才发现两个扩展都打包了Vendor\Library\Client类;哪个类被加载取决于扫描顺序和已生成的classmap。这个警告不是“无害提示”,而是部署不确定性。

先让Composer告诉你冲突双方

composer dump-autoload -o -vvv 2>&1 | tee /tmp/autoload.log
grep -n "Ambiguous class resolution" /tmp/autoload.log

再从生成的classmap确认当前选择。

php -r '$m=require "vendor/composer/autoload_classmap.php"; echo $m["Vendor\\Library\\Client"]??"missing",PHP_EOL;'
composer why vendor/library
composer why-not vendor/library 2.0.0

判断是哪一种重复

  • 两个Composer包各自带了一份同名库
  • app/code中的模块又复制了一份vendor依赖
  • 旧扩展目录升级后未删除,新旧目录同时存在
  • 补丁工具生成了备份.php文件并被PSR-4扫描
  • 大小写不同的路径在开发机不冲突,到Linux后表现不同

正确修复是只保留一个所有者

如果两个模块依赖同一库,让根composer.json统一require兼容版本,并从模块包里移除私有副本。若厂商扩展把依赖硬复制进自己的目录,优先升级厂商版本或联系厂商提供scoped依赖;不要长期靠调整autoload顺序决定谁赢。

短期无法改第三方包时,可以用replace/conflict或patch移除重复autoload,但必须写清楚版本边界并锁定composer.lock。exclude-from-classmap只能让警告消失,不会解决两个模块期待不同API的问题。

清理后要删除旧生成物

rm -rf generated/code/* generated/metadata/*
composer install --no-dev --prefer-dist --optimize-autoloader
bin/magento setup:di:compile
php -r 'require "vendor/autoload.php"; $r=new ReflectionClass("Vendor\\Library\\Client"); echo $r->getFileName(),PHP_EOL;'

Reflection输出必须指向预期包。所有节点、构建容器和CLI环境都执行同一检查,防止某台机器还有旧vendor目录。

兼容性回归

  • 运行冲突类相关模块的主要API调用
  • 比较两个扩展声明的依赖版本范围
  • 检查生产使用--no-dev后是否少了被错误依赖的dev包
  • 重新构建镜像,确认没有依赖本地未跟踪目录
  • CI把Ambiguous class resolution当成失败而不是warning忽略
autoload顺序不是公共契约。只要两个文件声明同一个完全限定类名,项目就处于不可重复构建状态。