项目级 repositories 字段存在时全局镜像被完全接管而非覆盖,因 composer 仅识别项目 composer.json 中显式声明的 repositories 数组,空数组也会禁用全局镜像;{"packagist.org": false} 必须作为独立对象置于数组首位以禁用官方源兜底行为,且镜像 url 末尾必须带 /,否则请求 packages.json 会 404。

项目级 repositories 字段一旦存在,全局镜像即被完全屏蔽,不存在“覆盖”或“合并”,只有“接管”。
为什么 composer.json 里的 repositories 会直接废掉全局镜像
Composer 不做配置叠加——它只认项目 composer.json 中显式声明的 repositories 数组。哪怕你只写了 "repositories": [],全局通过 composer config -g repo.packagist 设置的镜像也会彻底失效。
常见误触发场景:
- CI 脚本自动生成
composer.json并注入空"repositories": [],导致构建时走官方源卡死 - 执行了
composer config repo.packagist ...(漏掉-g),结果把镜像写进了当前项目的repositories字段,变成项目级覆盖 - 用
create-project初始化的模板自带repositories字段,你刚配好的全局镜像根本没机会加载
{"packagist.org": false} 必须是独立对象且放在数组最前
很多人加了镜像 URL 却没禁用官方源,结果 Composer 在镜像返回超时、502、TLS 错误时直接报错,不 fallback;只有明确返回 HTTP 404 才查下一个源。而 {"packagist.org": false} 是唯一能关掉这个隐式兜底行为的开关。
关键细节:
- 必须是
repositories数组里的一个独立对象,不能塞进其他仓库定义里 - 必须出现在数组第一个位置,否则后续条目可能覆盖其语义(Composer 按顺序解析)
- 键名严格为
"packagist.org",写成"packagist"或"packagist.org": true都无效 - 禁用后若还需公共包,得手动加回:
{"type": "composer", "url": "https://packagist.org/"}(注意末尾/)
多个镜像不会自动 fallback,只按顺序短路查找
Composer 对每个包只请求 repositories 数组中第一个返回有效元数据(HTTP 200 + JSON 结构正确)的源,其余全部跳过。这不是“多源容灾”,而是硬性中断。
这意味着:
- 阿里云镜像同步延迟 → 返回 404,才会查第二个源;但若它卡住(超时/TLS失败),Composer 等满默认 30 秒就报错,不再往下走
- 清华、华为等镜像 URL 若漏掉末尾
/(如https://mirrors.tuna.tsinghua.edu.cn/composer),拼出的路径是/composerpackages.json,直接 404,被误判为“包不存在”,错误触发 fallback - 想缓解超时卡顿,得全局调大:
composer config -g http.timeout 600
项目级配置比全局更可控,但要注意结构类型
用 composer config repo.packagist composer https://mirrors.aliyun.com/composer/(不带 -g)命令写入项目,它会尝试安全合并:如果原 repositories 是对象(不是数组),就会以 "packagist" 为 key 插入新源;但如果已是数组,该命令会直接覆盖整个数组,可能删掉私有源。
所以:
- 已有私有源时,别依赖这条命令,应手动编辑
composer.json的repositories数组,确保顺序和{"packagist.org": false}正确 - 改完后必须删掉
vendor/和composer.lock,再跑composer install,否则旧 lock 文件里的源哈希仍指向原站 - 验证是否生效:运行
composer config repositories,看输出是否包含你配的镜像 URL;再加-vvv跑一次composer require,确认日志里出现的是镜像域名而非packagist.org
最容易被忽略的一点:镜像 URL 末尾的 / 不是风格问题,是路径拼接逻辑的硬性要求;少了它,packages.json 请求必然 404,而 Composer 会把它当成“包不存在”,而不是“源不可用”。











