升级后版本冲突源于composer.lock被污染,需先用git diff composer.lock定位非目标包变动,再通过composer why、composer why-not反向排查依赖阻碍,发现replace/provide等危险字段须立即核查。

升级后出现版本冲突,不是 Composer 失效了,是 lock 文件里混进了不兼容的依赖路径——得先看清改了什么,再决定动不动手。
composer update 后为什么突然报 conflict?
这不是新 bug,而是 composer.lock 被意外“污染”:比如你执行了 composer update 没带参数,它就把所有间接依赖全重算了一遍;或者团队成员在不同 PHP 版本下跑过 composer install,导致 lock 文件记录了多个平台不一致的 dist URL 和哈希。
- 检查是否真有冲突:运行
composer install --dry-run,看是否仍报Your requirements could not be resolved - 确认是不是 lock 文件本身坏了:删掉
vendor/和composer.lock,再跑composer install—— 如果能过,说明旧 lock 里存了不可复现的解析结果 - 别信 “只是本地问题”:
composer.lock是跨环境契约,PHP 8.4 下生成的 lock 在 8.5 上可能因扩展可用性差异直接失败
如何精准识别哪些包被意外升级了?
升级后第一件事不是提交,是盯住 composer.lock 的 diff。Composer 不会告诉你“这个更新安全”,它只保证“满足约束”,而约束可能来自一个已废弃的 require-dev 包。
- 用
git diff composer.lock,过滤出name和version字段变动 - 重点关注非目标包:比如你只想升
laravel/framework,但monolog/monolog、guzzlehttp/guzzle也变了 —— 这说明--with-dependencies拉得太宽 - 查某包为何被连带升级:运行
composer why monolog/monolog,如果输出里含orchestra/testbench或laravel/pint,基本可断定是require-dev里的老工具反向锁死了主依赖
composer why-not 输出看不懂怎么办?
composer why-not 不是因果链,是拒绝链——必须从最后一行倒着读,每一行都是“上一级包不允许下一级装这个版本”的声明。
- 最后一行通常是你的根项目(
your-project-name),往上才是阻塞源 - 看到
(conflict with vendor/package >=2.0)?说明那个包自己写了"conflict": {"vendor/package": ">=2.0"},不是你写错了,是它封杀了 - 看到
phpunit/phpunit:^9.0?大概率它硬绑了symfony/console:^5.4,而你要升的框架要求^6.0,中间没交集 - 输出为空?别折腾了,那个版本根本没发布,去 packagist.org 查最新 tag
更新记录里出现 replace 或 provide 是危险信号
这些字段在 lock 文件里不是装饰,是 Composer 解析时的替代规则。一旦某个包用 "replace": {"old/package": "*"} 声明自己完全替代旧包,后续所有依赖它的包都会被重定向到这个“假包”,而你代码里可能还调着旧 API。
- 搜
composer.lock里的"replace"或"provide",看是否来自你没主动 require 的包(比如某个测试工具) - 运行
composer show --tree | grep -A5 -B5 replace,确认有没有包在悄悄接管关键组件 - 若发现
illuminate/support被某个非 Laravel 包provide了,立刻停手 —— 这类替换极少完全兼容,运行时大概率Class not found
复杂点从来不在命令怎么敲,而在哪一行 lock 记录悄悄替换了你信任的类加载路径。每次升级后不看 diff,等于把解析权交给运气。











