Composer update 后无法自动生成版本变更日志,需通过脚本在更新前后分别执行 composer show --installed --format=json 获取 before.json 和 after.json,再比对差异生成变更日志;推荐使用 post-update-cmd 钩子调用 PHP 脚本实现自动化。

Composer update 后怎么自动提取包版本变更
Composer 本身不生成更新日志,但 composer show --outdated 和 composer show --installed 能提供足够原始数据。关键不是“让 Composer 写日志”,而是用脚本在 composer update 前后抓取差异。
典型做法是:更新前保存一份 composer show --installed --format=json 到 before.json,更新后再导出 after.json,用工具比对。PHP 原生就能做,不用额外装 diff 工具。
- 务必在
composer update前加--no-scripts,否则可能触发未就绪的钩子导致日志截断 - JSON 输出含
version和source字段,但有些包(如 path repo)的version是dev-main,需统一用dist.reference或source.reference做哈希比对才准 - 忽略
platform类型包(如ext-zip),它们不会出现在composer.lock的 packages 数组里,硬比会报错
用 composer-script 钩子自动触发日志生成
把日志逻辑封装成命令,再挂到 post-update-cmd 钩子,是最省心的方式。注意:钩子执行时工作目录是项目根,composer.json 一定存在,但 vendor/ 可能未写入完成——所以日志脚本必须放在 post-update-cmd,不能放 pre-update-cmd。
在 composer.json 里加:
"scripts": {
"log:update": "php scripts/log-changes.php",
"post-update-cmd": ["@log:update"]
}
scripts/log-changes.php 示例逻辑:
$before = json_decode(file_get_contents('before.json'), true);
$after = json_decode(file_get_contents('composer.lock'), true);
// 遍历 $after['packages'],对比 name + version(或 reference)
// 过滤掉没变的、被移除的、新添的,只留 version/reference 变更项
// 输出到 CHANGELOG-UPDATE-20240520.md
- 别直接读
composer.lock的packages数组——它不含平台包,也不含 require-dev 里被禁用的包,和composer show结果维度不一致 - 钩子中调用的 PHP 脚本,建议用
#!/usr/bin/env php开头并chmod +x,避免 Windows 下路径解析失败
如何处理 monorepo 或 path repository 的版本识别
当项目依赖本地 "path" 包(如 "my/local-pkg": "*" → "path": "../local-pkg"),composer show 返回的 version 恒为 dev-main,无法体现真实变更。这时得切到包目录里跑 git rev-parse HEAD。
脚本里要判断 source.type === "path",然后:
- 拼出绝对路径:
realpath($source.url) - 执行
exec("cd {$path} && git rev-parse --short HEAD 2>/dev/null", $out) - 把短哈希当作“版本号”参与比对(长哈希太占空间,短哈希在单仓内够用)
- 如果
git命令失败(非 Git 仓库),回退到filemtime()或跳过该包——强求反而让日志不可靠
生成的日志文件怎么避免重复和覆盖
每次 composer update 都生成新文件,而不是追加到一个大日志里。用日期+时间戳命名最稳妥:UPDATE-$(date +%Y%m%d-%H%M%S).md。CI 环境下还要考虑并发:同一时间多个 job 更新,可能撞名。
- 加锁不现实(跨机器难同步),改用
uniqid('', true)生成微秒级唯一 ID 更轻量 - 不要写死路径如
./changelog/,先mkdir -p确保目录存在,否则脚本静默失败 - Git 提交时,建议把日志文件设为
export-ignore(在.gitattributes里),避免污染主代码历史;CI 构建产物日志可另存对象存储
真正麻烦的是多层嵌套依赖的传递性变更——比如 A 依赖 B,B 升级了 C,但 A 的 composer.lock 里 C 的版本变了,A 自己却没改 require。这种隐式升级必须靠比对 lock 文件的 packages 全集才能捕获,漏掉就等于日志失效。


















