cweagans/composer-patches是唯一可上线的补丁方案,因其能将补丁固化进composer.lock并每次安装重放;需显式安装v1.7+、补丁路径相对项目根、格式为git diff --no-prefix、配置在根composer.json的extra.patches中,且必须清缓存重装才能生效。

没有“热补丁发布机制”——Composer 本身不支持运行时动态打补丁,所谓“热”只是错觉,真正在用的只有 patch 插件 + 严格约束的临时方案。
为什么 cweagans/composer-patches 是唯一可上线的选择
本地改 vendor/、直接写 dev-branch、配 path repository,全都会在下次 composer install 或 CI 构建时失效。只有 cweagans/composer-patches 能把补丁逻辑固化进 composer.lock,且每次安装都重放。
- 必须运行
composer require --dev cweagans/composer-patches:^1.7,v1.6 及更早版本不兼容 Composer 2.x 的 lock 文件格式 - 补丁必须放在项目根目录下(如
patches/common-fix-null-deref.patch),路径写在extra.patches里,不能写绝对路径或vendor/前缀 - 补丁文件必须是
git diff --no-prefix输出,且路径从包根开始(如src/Service/OrderProcessor.php),不是vendor/myorg/common/src/... - 每次修改补丁后,必须删掉
vendor/和composer.lock,再跑composer install --no-dev -v,否则插件跳过应用阶段
extra.patches 配置写在哪?写错就等于没写
配置位置错误是补丁不生效的最常见原因。它必须出现在项目根 composer.json 的顶层 extra 字段里,和 require、autoload 同级。
- 错误写法:
"repositories": [{"type": "package", "extra": {...}}]—— 插件根本读不到 - 正确结构:
{ "require": { "myorg/common": "^1.5" }, "extra": { "patches": { "myorg/common": { "Fix order null coalesce": "patches/common-null-coalesce.patch" } } } } - 键名必须是完整包名(
myorg/common),不能是别名、路径或子目录名 - 值必须是对象,不能是数组或字符串;描述字段(如
"Fix order null coalesce")仅用于日志输出,不影响逻辑
微服务共用类库时,补丁如何避免误伤其他服务
一个 myorg/common 包被五个微服务依赖,你只在订单服务里打了补丁,但 extra.patches 写在订单服务的 composer.json 里,那其他服务不会受影响——补丁只对当前项目生效。
- 补丁作用域 = 当前项目的
composer.json所声明的依赖,跟其他服务完全隔离 - 但要注意:如果多个微服务共用同一个 Git 仓库(比如 monorepo 模式),且都 require
myorg/common,那每个服务必须各自维护自己的patches/目录和extra.patches配置 - 禁止在公共类库自身的
composer.json里写extra.patches—— 这会导致所有下游服务无条件继承该补丁,失控风险极高 - CI 流水线必须校验
git apply --check patches/*.patch,防止补丁因上游代码变更而失效(比如 hunk 偏移错位)
补丁上线后最易被忽略的三件事
补丁不是“打完就完”,它本质是技术债,必须有明确退出路径。很多人卡在最后一步,导致补丁长期滞留、冲突累积、升级失败。
- 每条补丁配置后面必须加注释,例如:
// TODO: remove after myorg/common v1.5.3, see PR #42 - 补丁文件名要带版本锚点,如
common-v1.5.2-null-deref.patch,而不是fix.patch—— 否则升级到 v1.5.3 后没人记得删 - 上线后立刻提 PR 给上游,并在 PR 描述里贴出补丁内容和复现步骤;等上游合并并发布新版本后,立刻在各微服务中执行
composer update myorg/common --with-dependencies并删掉对应补丁


















