Composer不能直接写1.2.3-custom.1,因其违反SemVer 2.0规范——预发布标识符中点号后接数字再跟点号非法;合法格式仅支持如1.2.3-alpha.1等字母开头、点分隔纯字母数字的组合。

Composer 为什么不能直接写 1.2.3-custom.1 这样的版本号
Composer 的版本解析器严格遵循 SemVer 2.0 规范,而 1.2.3-custom.1 中的 custom.1 属于非法预发布标识符(含非字母数字和连字符以外的字符,比如点号后接数字再跟点号)。Composer 会直接报错:Invalid version string "1.2.3-custom.1",根本不会进入安装流程。
真正能被识别的预发布格式只有:1.2.3-alpha、1.2.3-beta.2、1.2.3-rc.1 等——即主副版本后仅允许一个连字符 + 字母开头的标识,且后续分段只能用点号分隔纯字母或数字组合。
- ✅ 合法:
1.2.3-dev、1.2.3-alpha.1、1.2.3-hotfix - ❌ 非法:
1.2.3-custom.1、1.2.3-20240501、1.2.3-my_fix_v2
想打定制包,正确做法是用 dist + version 手动覆盖
当你要发布一个内部修改版但又不想改原包源码时,最稳妥的方式是在 composer.json 中显式声明该包的 dist 源和精确版本号。Composer 会跳过 Packagist 的元数据校验,直接按你给的 URL 和 version 下载。
示例:覆盖 monolog/monolog 为私有构建版
{
"repositories": [
{
"type": "package",
"package": {
"name": "monolog/monolog",
"version": "2.9.2-custom",
"dist": {
"url": "https://internal.example.com/monolog-2.9.2-patched.zip",
"type": "zip"
},
"autoload": { "psr-4": { "Monolog\": "src/" } }
}
}
],
"require": {
"monolog/monolog": "2.9.2-custom"
}
}
-
version字符串可以任意命名(如2.9.2-custom),只要不和现有稳定版冲突即可 -
dist.url必须可访问,且压缩包结构要和原包一致(否则 autoload 会失效) - 必须用
"type": "package",不能用vcs或composer类型,否则 version 会被忽略
用 branch-alias 让 dev-main 兼容稳定版本约束
如果你在 fork 的仓库上持续开发定制功能,并希望 "monolog/monolog": "^2.9" 能自动匹配到你的 main 分支,就得在 fork 仓库的 composer.json 中设置 branch-alias:
{
"extra": {
"branch-alias": {
"dev-main": "2.9.x-dev"
}
}
}
这样 Composer 解析 ^2.9 时,会把你的 dev-main 当作 2.9.x-dev 版本候选。但注意:
- 必须将 fork 仓库注册为
vcs类型 repository,且 URL 指向 Git 地址 -
2.9.x-dev是“虚拟版本”,实际安装时仍显示为dev-main,composer show可验证 - 若原包已发
2.10.0,而你的分支没同步,^2.9可能降级拉取官方2.9.2,反而绕过你的定制版
本地开发调试时,优先用 path repository
改完代码立刻测试?别打包、别推远程、别改 version —— 直接用 path 类型让 Composer 软链接本地目录:
{
"repositories": [
{
"type": "path",
"url": "../my-monolog-patch"
}
],
"require": {
"monolog/monolog": "*"
}
}
- 本地目录
../my-monolog-patch必须含有效的composer.json,其中version可写dev-custom或留空(Composer 自动设为dev-main) - 执行
composer update monolog/monolog后,vendor 中会生成符号链接,改代码实时生效 - 上线前务必删掉
path配置并切回正式源,否则 CI 会失败
自定义版本号不是拼字符串游戏,关键在让 Composer 的版本解析器“认得出来”且“选得到”。多数翻车都出在把 version 当自由字段乱填,或者忘了 repositories 顺序导致私有源被忽略。


















