Composer按repositories数组顺序线性匹配,首个返回200且含目标包元数据的源即被采用,后续全跳过;私有仓须置首位并显式禁用packagist.org。

repositories 数组顺序就是实际查找顺序
Composer 不会“合并”多个源的包列表,也不会按版本新旧、稳定性或下载速度做二次排序。它只做一件事:从 repositories 数组第一个元素开始,逐个向每个源发起 packages.json 请求,一旦某个源返回 HTTP 200 + 可解析的 JSON(且该 JSON 中包含你要的包名),就立刻停止后续所有查询,锁定这个源作为该包的唯一来源。
- 私有仓库写在数组末尾 →
composer require myorg/utils会先查镜像,404 后 fallback 到 packagist.org(如果没禁用),根本不会走到你的私有源 - 镜像 URL 少了末尾
/→ 请求变成https://mirrors.aliyun.com/composerpackages.json,404,直接切官方源 - 误用对象写法
"repositories": {"internal": { ... }}→ Composer 只读第一个 key,其余静默丢弃,私有源形同虚设 - 正确写法必须是索引数组,且私有源置顶:
"repositories": [ {"type":"composer","url":"https://pkgs.internal/artifactory/api/composer/internal/"}, {"type":"composer","url":"https://mirrors.tuna.tsinghua.edu.cn/composer/"}, {"packagist.org": false} ]
packagist.org 是隐式兜底源,必须显式禁用
即使你把阿里云镜像放在 repositories 第一位,只要它对某个新包返回 404,Composer 就会自动切到 https://repo.packagist.org——这不是你配的,是 Composer 2.x 默认行为。它不关心你有没有写进数组,只要没显式关掉,就永远存在。
- 禁用方式只能是数组末尾加一个独立项:
{"packagist.org": false},不能写成"url": "https://packagist.org",也不能嵌套在其他对象里 - 执行
composer config repositories查看真实生效顺序,输出里不应出现https://repo.packagist.org - 超时、500、DNS 失败等非 404 错误,不会触发下一源,而是直接报错退出
依赖解析本身不按源排序,SAT 求解器才管版本
很多人以为“私有源排第一,就能强制装我 fork 的 monolog:3.0”,其实不是。源顺序只决定「从哪下载元数据」,而最终装哪个版本,由 SAT 求解器统一计算所有约束:你写的 require、间接依赖的 require、conflict、PHP 版本、minimum-stability 全部参与逻辑建模。
- 如果你的私有
monolog/monolog声明了"require": {"php": ">=8.4"},但当前环境是 PHP 8.3,就算它被选为元数据源,安装也会失败 -
composer why-not monolog/monolog:3.0才能看清谁在拦路,而不是调换repositories顺序 - 私有源提供的包,若未在
providers字段中声明替代关系(如"monolog/monolog": "^2.0"),则无法覆盖其他源中同名包的约束效力
vcs 和 package 类型源不参与全局元数据匹配
type: "vcs"(如 Git 地址)和 type: "package"(手动定义的包)不会出现在 packages.json 扫描流程中。它们只在你明确运行 composer require vendor/name 且本地没缓存时,才被单独拉取信息——也就是说,它们不跟镜像抢顺序,但也无法靠顺序“压过”镜像里的同名包。
- 想让 Git 私仓优先于镜像中的
laravel/framework?不行。vcs 源只响应显式require,不会影响guzzlehttp/guzzle这类间接依赖带进来的包 - vcs URL 带
.git后缀在某些 Composer 版本会失败,应写成"url": "https://git.example.com/my/laravel" - package 类型必须完整提供
version、dist、autoload等字段,否则composer install会跳过该条目
真正容易被忽略的是:源顺序只管“找得到”,不管“装得上”。哪怕你把私有源放第一位、禁用了 packagist.org、也确认了 packages.json 返回正常,最终 install 失败,问题大概率出在 SAT 求解阶段——比如私有包的 conflict 和你项目里另一个依赖的 require 冲突了,而这个冲突不会在 composer config repositories 里显示出来。











