Composer将vendor视为只读快照,手动修改必被丢弃;恢复需用--discard-changes强制重装,或通过cweagans/composer-patches插件打可版本化补丁。

直接改 vendor 里的文件,下次 composer install 或 composer update 就丢——这不是 bug,是 Composer 的设计逻辑:它把 vendor/ 当作只读快照,不区分你改的是 README 还是核心类。
为什么 vendor 修改会被丢?
Composer 检测到任意包下有未提交的修改(哪怕只加了 var_dump),就会中断安装并报错:The package vendor/package-name has modified files. Changes will be discarded.
-
--discard-changes必须显式带上才生效;不写就卡住不动 -
composer dump-autoload和composer clear-cache完全无效——它们不碰vendor文件内容 - 删整个
vendor/再重装比--discard-changes更慢,还会丢失 classmap 缓存等优化
用 cweagans/composer-patches 打补丁
这是目前最稳、兼容 Composer 2.x 的方案。vaimo 版本虽可用,但 cweagans 更新更勤、文档更清晰。
- 安装插件:
composer require --dev cweagans/composer-patches:^1.7 - 必须删掉
vendor/和composer.lock后再运行composer install,否则旧缓存包跳过补丁阶段 - 全局配置如
composer config --global store-auths false可能干扰插件读取本地composer.json,临时取消再试
extra.patches 配置怎么写才有效?
补丁声明必须放在项目根目录 composer.json 的 extra.patches 字段下,格式和路径错一个字符就失效。
- 键名必须是标准包名:
"monolog/monolog",不能写成"./vendor/monolog/monolog"或"monolog" - 值必须是对象:
"Fix log truncation": "patches/monolog-truncate.patch",描述只是日志显示用 - 补丁路径相对于
composer.json,所以patches/xxx.patch表示项目根目录下的patches/子目录 - 补丁文件必须是
git diff或diff -u格式,且路径前缀要匹配解压后的包结构,例如src/Handler/StreamHandler.php,不是vendor/monolog/monolog/src/Handler/StreamHandler.php
补丁生成和路径最容易踩的坑
补丁文件名别含空格或中文;推荐用短横线命名,如 laravel-session-race.patch。补丁内容里路径前缀不对、hunk offset 偏移错、版本变动导致上下文不匹配——这三处出问题,90% 的“打补丁失败”就发生在这里。
生成补丁时,不要从 vendor/ 目录直接 diff 出带 vendor/xxx 前缀的路径;正确做法是复制一份干净包到临时目录,改完再用 diff -uprN 对比,确保输出路径是 a/src/... 和 b/src/... 这种相对包根的结构。











