答案是composer why-not vendor/package:version为第一反应动作——它从composer.json逆推阻塞链,最上行“Root package requires”常暴露手动约束过窄(如"php": "7.4"),而插件冲突需查其Packagist/GitHub兼容性。

composer why-not vendor/package:version 是第一反应动作
报错里出现 Conclusion: don't install xxx 或 Your requirements could not be resolved,别翻日志找网络错误,这是 Composer 明确告诉你:它在当前所有已知包版本中,找不到一组满足全部约束的解。核心不是“装不了”,而是“算不出”。
立刻运行:composer why-not vendor/package:version(比如 composer why-not monolog/monolog:^2.9.0),它会从你写的 composer.json 开始逆推,逐层列出谁锁死了哪个版本范围。
- 输出最上面一行通常是
Root package requires—— 说明是你自己写的约束太窄,比如"php": "7.4",但目标包已放弃支持 PHP 7.4 - 如果阻塞源是某个插件(如
spatie/laravel-backup),先查它的 Packagist 页面或 GitHub Releases,确认是否已有兼容新版 Laravel 的正式版 - 别用
composer depends替代 —— 它只告诉你“谁依赖它”,不体现具体版本限制,无法定位冲突卡点
composer show --tree 和 composer depends -r 联合验证依赖链
单靠 why-not 可能漏掉间接依赖里的隐藏冲突。比如你没直接 require symfony/console,但它被两个不同包分别拉入了 ^5.4 和 ^6.0。
这时用:composer show --tree vendor/package 展开完整依赖树,观察每个节点实际选中的版本和锁定状态;再配合:composer depends -r vendor/package 查看谁在强制约束它。
-
-r是关键,它递归查找所有传递依赖,而不仅仅是直接 require 的包 - 输出中带
requires的行才是真正的约束来源;replaces或provides可忽略 - 注意
^4.4和~4.4.0允许的版本范围有细微差别,可能就是冲突触发点 - 树太深时加
grep过滤,例如:composer show --tree monolog/monolog | grep -A5 -B5 "laravel/framework"
platform 配置写错会导致 install 成功但 runtime 崩溃
composer install 成功不代表能跑起来。常见现象是:install 没报错,但 php artisan 或 index.php 启动就 Fatal error —— 根本原因是 composer.lock 里记录的包版本,是在更高 PHP 版本下生成的,含 PHP 8.1+ 语法或扩展依赖。
真正起作用的是 config.platform,它不是提示,是欺骗 Composer “当前环境只有这些能力”:
- PHP 小版本必须精确到补丁号,例如
"php": "7.4.33",不能写"7.4"或">=7.4.0" - 所有代码中用到的扩展都要列全,如
"ext-mbstring": "7.4.33"、"ext-openssl": "7.4.33" - 改完后必须执行:
composer update --with-all-dependencies,否则 lock 文件仍含高版本包 - 私有包或 dev 分支要额外检查
minimum-stability和repositories配置是否生效
composer update 必须定点 + 带依赖链,否则等于重开一局
默认 composer update 会重算整个依赖图,极易把 guzzlehttp/guzzle 从 7.x 升到 8.x,导致 new GuzzleHttp\Client() 直接报错。
真要更新,必须控制粒度:
- 写死包名,不加引号、不加波浪号:
composer update monolog/monolog,不是composer update "monolog/monolog:^3" - 加上
--with-dependencies:只允许升级该包及其直系依赖,不碰phpunit这类无关项 - 加
--dry-run预览:composer update monolog/monolog --with-dependencies --dry-run,确认改动范围再执行 - 升级后立刻
git diff composer.lock,如果多了十几个包,说明约束没控住,得回退
最常被忽略的一点:require-dev 里的包也会拖垮主依赖树,尤其是 phpunit/phpunit 对 PHP 版本和 ext-json 的强约束,往往比主 require 更早卡死解析过程。


















