PHP 项目如何自动生成高质量变更日志(Changelog)?从手动到 CI/CD 的完整实践指南**

📚 目录导读(Table of Contents)
- 为什么你的 PHP 项目需要一个变更日志?
- 手动编写 vs 自动化生成:优劣对比
- PHP 生态中的最佳工具:解析
cliff与conventional-commits - 实战:用 PHP 脚本 + Git 历史自动生成
CHANGELOG.md- 1 前置条件:规范化提交信息(Commit Message)
- 2 核心代码实现:解析
git log并分类 - 3 生成 Markdown 表格与版本链接
- 进阶:集成到 GitHub Actions / GitLab CI 实现全自动
- 常见问题问答(FAQ)
- 总结与最佳实践建议
为什么你的 PHP 项目需要一个变更日志?
变更日志(Changelog)是软件项目中易被忽视但极其重要的文档,它记录了每个版本“新增、修复、破坏性变更”的清单,对于使用 PHP 开发的框架(如 Laravel)、库(如 Guzzle)或大型业务系统,变更日志能解决三大痛点:
- 协作效率:团队成员或外部贡献者无需阅读全部提交记录,即可快速了解新版本影响。
- 故障排查:当线上出现回归 Bug 时,可迅速定位是哪个版本引入了变更。
- 发布规范:强制团队遵循语义化版本(SemVer),让
composer update变得可控。
手动编写 vs 自动化生成:优劣对比
| 方式 | 优点 | 缺点 |
|---|---|---|
| 手动 | 可人工筛选重要内容,措辞更人性化 | 易遗漏、滞后,开发者常因“忘记更新”而使文档失效 |
| 自动化 | 实时同步代码变更,无遗漏;强制提交信息规范 | 需安装依赖、配置规则,若提交信息杂乱则输出质量差 |
对于任何活跃的 PHP 项目,自动化是唯一可扩展的路径。
PHP 生态中的最佳工具:解析 cliff 与 conventional-commits
虽然 Node 生态有 standard-version,但在 PHP 领域,我们推荐两个轻量级方案:
git-cliff:基于 Rust 编写的高性能工具,但可通过 Composer 包wyzheng/git-cliff调用,它默认遵循 Conventional Commits 规范,支持模板引擎(Tera),能生成高度定制化的 Markdown。- 纯 PHP 脚本(自研):优点是零依赖,适合对代码有绝对掌控欲的团队,核心逻辑是执行
git log命令,解析输出并映射到Added/Changed/Deprecated/Removed/Fixed/Security标记。
综合搜索引擎的高频建议:大多数团队最终选择 自研脚本 + 标准化提交信息,因为无需引入系统级依赖,且易于测试。
实战:用 PHP 脚本 + Git 历史自动生成 CHANGELOG.md
1 前置条件:规范化提交信息(Commit Message)
这是整个方案的基石,请在项目根目录创建 commitlint.config.js 或使用 PHP 工具 captainhook 强制校验:
# 正确的格式 git commit -m "feat(core): 添加缓存抽象层" git commit -m "fix(api): 修复用户登录时 500 错误" git commit -m "BREAKING CHANGE: 变更配置项参数名"
2 核心代码实现:解析 git log 并分类
以下是一个精简但可运行的 PHP 脚本(保存为 generate-changelog.php):
<?php
// 获取 Git 历史(从最新 tag 到 HEAD)
$tags = shell_exec('git tag --sort=-v:refname | head -n 1');
$since = $tags ? trim($tags) : 'HEAD~10';
$log = shell_exec("git log --pretty=format:'%h|%s|%ad' --date=short $since..HEAD");
$entries = ['Added' => [], 'Fixed' => [], 'Changed' => [], 'Removed' => []];
foreach (explode("\n", trim($log)) as $line) {
[, $subject] = explode('|', $line);
if (preg_match('/^feat(?:\(.+\))?: (.+)$/', $subject, $m)) {
$entries['Added'][] = $m[1];
} elseif (preg_match('/^fix(?:\(.+\))?: (.+)$/', $subject, $m)) {
$entries['Fixed'][] = $m[1];
} elseif (preg_match('/^perf(?:\(.+\))?: (.+)$/', $subject, $m)) {
$entries['Changed'][] = $m[1];
} elseif (preg_match('/^refactor(?:\(.+\))?: (.+)$/', $subject, $m)) {
$entries['Removed'][] = $m[1];
}
}
// 生成 Markdown
$md = "## [Unreleased] - " . date('Y-m-d') . "\n\n";
foreach ($entries as $type => $items) {
if (count($items) > 0) {
$md .= "### $type\n";
foreach ($items as $item) {
$md .= "- $item\n";
}
$md .= "\n";
}
}
file_put_contents('CHANGELOG.md', $md);
echo "✅ 变更日志已生成!\n";
3 生成 Markdown 表格与版本链接
为了符合主流开源项目风格(如 Laravel 的 changelog),你可以在上述循环外追加版本比较链接:
$latestTag = $since; $md .= "**完整变更记录**: [查看对比](https://github.com/your-name/your-repo/compare/$latestTag...HEAD)\n";
进阶:集成到 GitHub Actions / GitLab CI 实现全自动
在 .github/workflows/release.yml 中添加 Job:
- name: 生成变更日志
run: php generate-changelog.php
- name: 提交文件
run: |
git config user.name "GitHub Action"
git config user.email "action@github.com"
git add CHANGELOG.md
git commit -m "docs: 自动更新变更日志"
git push
这样每次发布新版本时(打 tag),系统都会自动生成并提交变更日志,无需人工介入。
常见问题问答(FAQ)
Q1:我已经有很多不规范的历史提交信息,怎么办?
A:不必纠结,插件 bumbal/commit-checker 或脚本可只从“当前 tag”开始计算,历史记录可以后期通过 git rebase 重写,但建议谨慎操作(仅限团队内部分支)。
Q2:如何识别 “BREAKING CHANGE” 并高亮?
A:在解析代码中使用 strpos($subject, 'BREAKING CHANGE') !== false 判断,然后将其单独归类到 ### ⚠️ 破坏性变更 小节。
Q3:生成的日志没有包含 security 修复分类?
A:在 preg_match 中加入 security 前缀,或者监控 git log --grep='security'。
总结与最佳实践建议
- 推荐策略:采用 Conventional Commits 规范 + 自研 50 行 PHP 脚本,即可满足 90% 需求。
- 注意时区:在 PHP 脚本中
date_default_timezone_set('UTC')避免时间偏差。 - 结合 Composer 脚本:将
generate-changelog.php放入composer.json的scripts段,执行composer changelog即可。 - 不要忽略文档更新:在
CONTRIBUTING.md中写明提交规范,否则自动化就是无源之水。
遵循以上步骤,你的 PHP 项目将拥有媲美 Laravel 官方仓库的整洁变更日志,这不仅提升了专业性,也让团队协作更高效。
延伸阅读提示:若想深入学习 git-cliff 的配置模板,可以访问官方文档 example 目录;对于更复杂的多包仓库(Monorepo),可在脚本中增加 --path 参数过滤子目录。