答案是必须用相对路径、被引用包声明symlink、name/version严格匹配、提交composer.lock且禁用vendor/:如"packages/mylib",且packages/mylib/composer.json需含"options": {"symlink": true}和匹配的name与dev-main等版本。

path仓库必须用相对路径,否则在他人机器上直接失效
Composer 的 repositories.type: "path" 只认项目根目录下的相对路径。一旦写成 /home/alex/mylib 或 ../mylib,该仓库就会被跳过,composer install 时完全不生效,也不会报错提示——静默忽略是最危险的。
正确写法只有这一种:假设你的包放在 packages/mylib/composer.json,那么主项目的 composer.json 中应写:
"repositories": [
{
"type": "path",
"url": "packages/mylib"
}
]
-
url值不能以./开头(如./packages/mylib会失败) - 不支持环境变量插值,
${HOME}、$_ENV全部无效 - 路径不能跨出项目根目录,
../outside是非法的,Composer 2.5+ 会直接跳过整条仓库配置
软链接(symlink)必须在被引用包里声明,主项目配了也没用
很多人在主项目的 repositories 里加 "options": {"symlink": true},结果发现 vendor 下还是复制而非链接——因为 path 类型的 symlink 控制权不在主项目,而在被引用包自己的 composer.json 里。
必须在 packages/mylib/composer.json 中显式写:
"options": {
"symlink": true
}
- 该配置只在
composer update mylib/package-name或全量composer update时生效;composer install不重建链接 - CI 环境通常禁用 symlink(如 Docker 容器无权限创建),此时应确保被引用包本身能独立运行测试,不依赖“实时编辑”
- 如果被引用包没声明
"options": {"symlink": true},哪怕主项目再怎么配,最终都是硬拷贝
require 版本号必须与被引用包的 name + version 字段严格一致
path 仓库不是绕过版本校验的后门。如果被引用包的 composer.json 写着 "name": "acme/utils" 和 "version": "dev-main",而主项目 require 里写的是 "acme/utils": "^1.0",Composer 就会报 Could not find package acme/utils。
- 最稳妥的组合是:被引用包设
"version": "dev-master",主项目写"acme/utils": "dev-master" - 更推荐语义化写法:
"version": "1.0.x-dev"+"acme/utils": "1.0.x-dev",兼容性检查正常,且明确表达开发分支意图 - 切勿省略
version字段——Composer 2.5+ 会直接拒绝加载无 version 的 path 包
composer.lock 必须提交,vendor/ 必须禁止提交
有人觉得 “path 包是本地的,lock 文件无所谓”,这是团队协作中最常见的认知偏差。不提交 composer.lock,不同成员执行 composer install 时,可能因依赖解析顺序、缓存状态差异,导致 autoload 映射不一致或类加载失败。
-
composer.lock锁定了所有依赖(包括 path 包的 exact version 和 autoload 规则),是多人协同的唯一事实来源 -
vendor/绝对不能进 Git:它体积大、易冲突、含环境敏感路径(如 symlink 目标),且 CI 构建时必然重装 - 若某次
composer install后 vendor 下出现异常文件(比如意外生成的 .git 目录),说明有包在 post-install-cmd 里做了不该做的事,需排查该包的 scripts 配置
path 依赖真正难的不是配置,而是让所有人对“相对路径”“symlink 权限归属”“version 字段强制存在”这些约束达成共识并持续遵守——任何一环松动,都会在某个周五下午三点集体爆发。


















