根本原因是Composer复用VCS缓存中的Git快照且未更新vendor内symlink;需同步清理~/.composer/cache/vcs/、删除vendor中对应包目录,并用--prefer-source重装以强制git clone最新HEAD。

为什么改了本地 Git 包,vendor 里还是旧代码?
根本不是 Composer 没拉新代码,而是它默认复用 ~/.composer/cache/vcs/ 下的 Git 克隆快照,甚至 vendor 目录里的 symlink 都可能指向旧 commit。尤其当你用 VCS 类型(如 type: "vcs")配置了本地 Git 路径("url": "../my-package"),但没清理对应缓存,Composer 就直接 git checkout 已有副本,压根不 fetch。
常见误判包括:
- 以为
composer clear-cache能清掉 VCS 缓存——其实它只清files/和部分repo/,vcs/完全不动 - 删了
vendor/后composer install,结果发现 vendor 里是完整复制而非 symlink,失去调试意义 - 运行
composer update --no-cache却仍看到旧类——因为--no-cache不删 vendor 目录,也不重置 Git 工作区
强制刷新 VCS 本地包的三步操作
要让 vendor 下的包实时反映本地 Git 修改(比如 git commit -am "debug" 后立刻生效),必须同时切断三个缓存层:
- 删掉 VCS 缓存:执行
rm -rf $(composer config --global cache-dir)/vcs/*(Linux/macOS)或手动删除%APPDATA%\Composer\Cache\vcs\(Windows) - 删掉 vendor 中对应包目录:
rm -rf vendor/vendor/name(别删整个 vendor) - 加
--prefer-source重装:composer update vendor/name --prefer-source --no-cache——这会触发重新git clone并checkout当前 HEAD,且跳过 ZIP 缓存
验证是否成功:进 vendor/vendor/name 执行 git log -1,输出应和你本地仓库最新 commit 一致;ls -l 应显示为真实目录(非 symlink),因为 --prefer-source 强制走源码模式。
composer update --no-cache 为什么有时还是不生效?
--no-cache 确实绕过了 ~/.composer/cache/files/ 和 repo/,但它对 VCS 包的控制力有限:
- 它不自动
git pull或git fetch,只影响“是否从缓存解压”,不影响“从哪个 commit checkout” - 若你本地 Git 仓库未
git push到远程(即使配置的是本地路径),Composer 仍可能基于旧 ref 做 shallow clone - CI 环境中 PHP-FPM 进程复用可能导致内存内元数据未刷新,此时单靠命令不够,得配合
composer dump-autoload和重启 worker
真正可靠的做法是:改完本地包后,在其目录下 git add . && git commit -m "tmp",再回到主项目执行上述三步。不要依赖“自动同步”,VCS 包没有 path 类型的硬链接语义,它是按 commit hash 锁定的。
path 仓库比 VCS 更适合本地调试?
是的,但仅限你不需要 Git 版本管理的场景。用 type: "path" 时,vendor 下是直接 symlink(Unix)或 junction(Windows),改源码即刻生效,完全绕过 Git 缓存链。但要注意:
-
url必须是相对路径(如"../my-package"),不能含~/或绝对路径,否则 Composer 拒绝加载 - 本地包
composer.json的version必须匹配 require 中的分支别名(如"dev-main"对应"version": "dev-main") - 一旦用了
path,就别混用vcs——两者冲突,Composer 会优先走vcs缓存,导致 symlink 被覆盖
VCS 适合模拟真实发布流程(打 tag、推远程),path 适合纯本地快速迭代。选哪个,取决于你当前卡在“验证逻辑”还是“验证发布机制”上。











