vendor未更新目标包的根本原因是Composer依赖解析未选中期望版本,核心取决于composer.lock锁定结果及conflict/replace等硬性约束;需用composer why/why-not定位阻塞点,通过--with-dependencies或写死版本强制更新。

vendor 覆盖失败不是文件没复制过去,而是 Composer 在解析依赖时压根没选中你期望的版本——它连下载都不会触发。核心得看 composer.lock 里最终锁定的是哪个版本,以及谁把它锁死的。
为什么 vendor 目录没更新目标包?
常见现象是:改了 composer.json 里的版本号,执行 composer update vendor/package 后 vendor/ 下还是旧版,composer show vendor/package 显示的也是旧版本。
- 根本原因不是命令没跑,而是 Composer 判断“当前已安装版本已满足所有约束”,所以跳过更新 —— 即使你写了新版本,只要旧版仍在允许范围内(比如你写
"^2.0",而当前装的是2.1.0),它就不会动 - 另一种情况是:你写的版本根本无法被满足(比如
"3.0.0"被某个conflict或其他包的require拦住了),Composer 静默忽略,不报错也不更新 -
composer update vendor/package默认只更新该包及其直接require,不升级它的子依赖;如果子依赖卡在旧版,可能导致主包也无法升到新版
用 composer why 和 composer why-not 看清真实依赖链
别靠猜,直接问 Composer 谁在用、谁在拦:
- 查“谁在用这个包”:
composer why vendor/package—— 显示哪些已安装包显式或隐式依赖它,以及它们各自要求的版本范围 - 查“谁在拦这个版本”:
composer why-not vendor/package:3.0.0—— 输出所有阻止安装3.0.0的已存在依赖,包括require、conflict、replace字段 - 特别注意输出里是否出现
require-dev中的包(如phpunit/phpunit、infection/infection),它们常悄悄拉进高版本依赖,却不在生产环境需要
强制覆盖 vendor 的安全操作顺序
想让 vendor/ 真的变成你指定的版本,不能只改 composer.json 就跑 update。必须确保 Composer 有唯一解:
- 先运行
composer update --dry-run -v,确认日志里是否出现Resolving dependencies并最终选定目标版本;如果卡住或回退多次,说明约束无交集 - 若要精确覆盖,直接写死版本:
"vendor/package": "3.0.0"(去掉^或~),再执行composer update vendor/package --with-dependencies - 避免用
composer install强制覆盖 —— 它只按composer.lock还原,不会重新解析;想换版本,必须先让lock文件更新 - 更新后立刻检查
git diff composer.lock,确认只有vendor/package及其直系依赖被修改,没有意外波及其他包
conflict 和 replace 字段会静默干扰 vendor 覆盖
这两个字段不是提示,是硬性规则,且全局生效,极易被忽略:
-
conflict:哪怕你没require某个包,只要它被任何间接依赖拉进来,就会触发冲突。例如某 SDK 声明"conflict": {"guzzlehttp/guzzle": ">=7.0"},你装laravel/sanctum:^3.0(需 Guzzle ^7.2)就必然失败 -
replace:如果一个包声明"replace": {"monolog/monolog": "*"},Composer 就认为 monolog 已被“提供”,不会再安装任何版本 —— 即使你手动 require,也会被跳过 - 排查时务必运行
composer show vendor/package,看输出里是否有replaces或conflicts字段,再顺藤摸瓜查源头
真正卡住 vendor 覆盖的,往往不是网络或权限问题,而是 conflict 的全局拦截、replace 的隐式替代,或者 require-dev 里某个测试工具偷偷锁死了关键依赖。这些点不显式报错,但会让 composer update 彻底失效。











