Composer 不自动解决依赖冲突,只在约束下寻找兼容版本;冲突时需用 composer why-not 定位阻断链,用 composer update vendor/package --with-dependencies 精准升级,并检查 conflict/replace 规则、PHP 平台配置等根本原因。

Composer 不会“自动仲裁”冲突,它只做一件事:在所有约束下找一组能共存的版本。找不到就报错,不妥协、不猜测、不降级——这是设计使然,不是 bug。
composer why-not 是定位冲突的唯一可靠起点
报错里那句 Your requirements could not be resolved 没告诉你谁拦了路,composer why-not 才是真能查清阻断链的命令。它比看错误日志快得多,也比瞎猜准确得多。
- 运行
composer why-not monolog/monolog:^2.10,它会列出所有阻止该版本安装的包及其约束条件 - 如果输出里出现
your-project-name,说明你自己的composer.json里写了冲突的 require 条目(比如同时锁了 Laravel 9 和一个只兼容 8.x 的插件) - 如果全是第三方包,就去 Packagist 查它们各自声明的
require,确认有没有一个中间版本能被双方接受(比如 v2.9.3 同时满足^2.0和>=2.8.0)
composer update vendor/package --with-dependencies 是最安全的定点升级方式
盲目 composer update 会让 SAT 求解器重算整棵树,可能卡住、可能升到不兼容的大版本;删 composer.lock 更是把问题藏起来。真正可控的操作,是显式指定包并带上 --with-dependencies。
-
composer update monolog/monolog --with-dependencies会让 Composer 同时考虑它的直接依赖(如psr/log),但不会动phpunit或guzzlehttp/guzzle这类无关项 - 别写
composer update "monolog/monolog:^2"—— 引号 + 波浪号会让解析行为变模糊,精确指定版本更稳,比如composer update monolog/monolog:2.9.3 - 执行后立刻
git diff composer.lock,确认改动范围是否符合预期;如果多了十几个包,说明你没加--with-dependencies或版本本身触发了连锁升级
conflict 和 replace 字段不是提示,是硬性规则
很多人把 conflict 当成文档注释,其实它一写进去,Composer 就会在解析阶段主动拒绝任何匹配组合,哪怕那个冲突包根本没被 require 进来。
-
"conflict": {"laravel/framework": ">=11.0"}一旦存在,只要项目里任何依赖(哪怕是间接的)拉进了 L11,安装就会失败 -
replace是用来声明“我这个包能完全替代另一个”的,比如symfony/polyfill-mbstring替代ext-mbstring;但如果用mockery/mockery去replacephpunit/phpunit,运行时大概率炸——因为 PHPUnit 内部调用路径绕不开自己的 mock 实现 - 临时加
"prefer-stable": true可以压制 dev 分支带来的宽松约束干扰,但别长期开着,它只是让不稳定版本更难被选中,不是解决冲突的根本办法
最常被忽略的点是:冲突往往不是版本号本身的问题,而是 PHP 版本、扩展缺失或 platform 配置不一致导致的“假冲突”。运行 composer show php 和 composer config platform.php 对比一下,再看报错里有没有 requires php ^8.1 这类提示——环境不匹配时,再怎么调 composer.json 都白搭。


















