执行 Composer 安装或更新时,自定义补丁报 Hunk FAILEDCannot apply patch。这不是应该自动忽略的噪声:补丁修改的上游文件已经不同,可能官方已修复,也可能代码结构变化后原补丁只应用了一半。

先确认补丁解决的原问题

记录补丁来源、适用包与版本、业务缺陷、测试用例和计划移除条件。没有这些信息的“神秘补丁”会成为每次升级的风险。对比新版本 release notes 与实际源码,判断修复是否已合入,而不是只看文件相似。

composer show vendor/package
git apply --check path/to/fix.patch
git apply --stat path/to/fix.patch

在干净的目标包副本上检查,不能直接修改 vendor 后再判断。Composer 安装目录通常不是业务源码仓库的一部分。

三种正确处理

上游已等价修复:删除补丁并运行原回归;问题仍在但代码移动:以新版本为基线重做最小补丁;模块已经不再使用相关路径:证明无影响后移除。不要用降低 fuzz、删除失败 hunk 或在脚本里永远返回成功来“通过部署”。

重做补丁要保持最小

只包含必要代码和测试,不混入格式化、调试日志或生成文件。补丁头记录目标版本与原因,CI 在 composer install 后验证目标行为。能通过 plugin/preference 安全扩展的业务改动,优先移出 vendor patch,减少升级冲突。

生产发布边界

补丁失败后部署必须停止,不能继续用半更新 vendor。构建产物应在独立目录完成并经过依赖锁文件验证,再切换流量。修复后从空 vendor 目录执行两次可重复构建,并测试与补丁直接相关的场景及核心结账流程。

补丁顺序也会制造假冲突

同一文件有多个补丁时,前一个改变上下文,后一个才失败。逐个在原始新版本上检查,再按声明顺序应用,确认是否应该合并。两个补丁修改同一方法但解决不同问题时,重做后的最终代码必须同时保留两项行为。

锁文件与构建缓存

composer.lock 未更新、插件缓存了旧 patch 文件或构建节点复用 vendor,可能让不同机器产生不同结果。CI 应从锁文件与空 vendor 构建,记录补丁哈希和实际包版本;产物推广到生产,不在生产服务器临时 composer update。

回归范围

除了原缺陷用例,还要运行该类周围的单元/集成测试和关键交易路径。补丁应用成功只说明文本匹配,不说明逻辑仍适配新 API。若新版本改变方法签名、返回类型或事务边界,旧修复可能编译通过却造成数据错误。

给每个补丁设置退出条件

在补丁清单写明:首次引入版本、覆盖包版本区间、对应测试和预计移除版本。升级评审时逐项判断,而不是让补丁永久累积。若官方实现与本地修复不同但目标相同,应以行为测试证明可移除,避免两套逻辑叠加。

保留可逆的升级提交

依赖升级、锁文件变化、补丁重做和业务代码修改应拆成可审查提交。这样可以精确比较补丁移除前后的行为,也能在验证失败时回退整个构建产物,而不是在线编辑 vendor。发布记录中保存包版本、补丁哈希与测试结果。