Composer无法解决版本冲突时会明确报“Conclusion: don’t install”,而非卡住;应优先用composer why-not定位冲突源头,再据来源决定改约束或换包。

别指望 Composer 自己“想通”,它只做一件事:找一组满足所有约束的版本。找不到,就停在 Conclusion: don't install 那行——这是明确拒绝,不是卡住。
用 composer why-not 立刻定位谁在挡路
报错里写 don't install laravel/sanctum:^3.0,但没说谁不让装。这时候别改 composer.json,先问 Composer:
composer why-not laravel/sanctum:^3.0
输出会直指冲突源头,比如:
myapp/myproject dev-main requires guzzlehttp/guzzle (^6.5)laravel/sanctum ^3.0 requires guzzlehttp/guzzle (^7.2)
这就锁定了 guzzlehttp/guzzle 是交集断点。接着查谁在死守 6.x:
composer why guzzlehttp/guzzle
结果常指向:acme/payment-sdk(你控制的私有包)、phpunit/phpunit(require-dev 里混进来的老工具),或某个已废弃的 SDK。
注意:composer why 查的是“为什么这个包被装进来”,composer depends 查的是“谁依赖它”——别用反。
区分“改约束”和“换包”,别硬顶
冲突来源不同,解法完全不同:
- 如果你能改
acme/payment-sdk的composer.json,就把"monolog/monolog": "1.26.1"放宽成"monolog/monolog": "^1.26 || ^2.0"—— 前提是你的代码真能兼容两套 API - 如果冲突来自
aws/aws-sdk-php:2.x这类已停止维护的包,别加--with-all-dependencies强升,去找aws/aws-sdk-php:3.x或async-aws/core - 某些包用
"conflict": {"laravel/framework": ">=11.0"}硬互斥,哪怕你项目没装 L11,只要其他依赖间接拉进来了,就会触发——conflict是全局生效的,不是提示
盲目 composer update xxx 往往无效,因为冲突不在顶层,而在传递依赖链深处。
别乱碰 composer.lock 和 --ignore-platform-reqs
删 vendor/ 和 composer.lock 再重装,只是用当前环境重跑一遍解析逻辑——如果 php -v 还是错的,或者 composer.json 里还写着 "php": "^8.1",报错原样重现。
--ignore-platform-reqs 不是绕过问题,是关掉校验开关。比如在 PHP 7.4 上强装只支持 PHP 8.1 的包,composer install 成功了,但运行时直接 ParseError: unexpected token "match"。
真正该配的是 composer config platform.php "8.2.0",但必须紧跟着 composer update --lock,否则 composer.lock 里还是旧版本记录。
CI 脚本中务必前置 php -v 打日志,确认实际生效版本;本地开发机上,用完整路径调用目标 PHP,比如 /usr/bin/php8.2 composer install。
复杂递归冲突看 --dry-run -v 的末尾 Conclusion
当 composer update 卡住不动,或报错信息太笼统,就跑:
composer update --dry-run -v
重点看输出末尾的 Conclusion 段,它会指出具体哪个包在哪个版本被强制排除,例如:
Root composer.json requires package-a ^2.0, but package-b v1.5.0 requires package-a 1.2.0 -> satisfiable by package-a[1.2.0].
这说明 package-b 是铁板一块,要么降级它,要么让它滚蛋。此时再配合 composer show --tree package-b,扫一眼它的 require-dev 分支,常能发现隐藏的污染源(比如 sebastian/exporter 被 phpunit 拉进来,又和主业务的 symfony/console 冲突)。
真正的难点从来不是命令怎么敲,而是读懂 Conclusion 之后那几行真实依赖链——那里藏着谁没妥协、谁在强绑、谁悄悄混进了 require-dev。


















