Composer无fallback机制,镜像宕机不会自动切源;config仅支持单URL,多源需脚本探活+动态写入+清缓存实现真容灾。

Composer 没有 fallback 机制,镜像站宕机时不会自动切源——你看到的“备用”全是假象,真容灾必须靠你自己控制流程。
composer config -g repo.packagist 只能设一个 URL
这个配置项是单值字段,不是列表。执行 composer config -g repo.packagist.org https://mirrors.aliyun.com/composer/ 之后再执行 composer config -g repo.packagist.org https://packagist.org,后者会直接覆盖前者,不会叠加,也不会报错提示“已存在”。官方明确拒绝在核心中加入多源 fallback(GitHub issue #8729),这不是 bug,是设计决定。
- 常见错误:用空格拼两个 URL,如
composer config -g repo.packagist.org https://a.com https://b.com—— 命令会失败或只生效第一个 - 参数差异:
repo.packagist.org接收字符串,不是 JSON、不是数组,也不是 URL 列表 - 兼容性:所有 Composer 2.x 版本行为一致,不存在版本差异带来的“意外支持”
repositories 数组不是 fallback,是顺序匹配 + 静态元数据合并
你在 composer.json 里写多个 repositories,Composer 不会在下载失败时重试下一个源。它只做两件事:1)按数组顺序请求每个源的 packages.json,合并成一张索引表;2)安装时,从**第一个声明了该包完整版本信息的源**拉 ZIP 包,其余源完全不参与下载阶段。
- 常见错误现象:
composer install卡在私有源超时,不自动切到阿里云镜像;Could not find package xxx报错,其实是因为私有源排在后面,而包名和公共包冲突,Composer 根本没查它 - 必须显式关闭默认源:
"packagist.org": false要作为独立对象写在repositories数组末尾,否则 Composer 仍会偷偷连官方站,导致镜像失效 - 兜底源必须放最后:
{"type": "composer", "url": "https://packagist.org"}放在数组末尾,且前面所有镜像都得是composer类型,否则元数据合并会出错
真·容灾:curl 探活 + 动态写入 + 清缓存
CI/CD 或本地开发中防止单点失效,唯一可靠路径是外部脚本驱动三步操作:探测镜像根路径是否可访问 → 切换当前生效源 → 清缓存避免旧元数据干扰。
- 探活命令示例:
curl -I -s -o /dev/null -w "%{http_code}" https://mirrors.aliyun.com/composer/packages.json,返回200才认为可用 - 动态写入项目级配置:
composer config repositories.packagist composer https://mirrors.tencent.com/composer/,这条命令会新增或覆盖repositories.packagist条目,不影响其他自定义源 - 清缓存必须跟上:
composer clear-cache,否则 Composer 仍用旧packages.json,导致“明明切了源却还在走老地址” - 容易踩的坑:脚本直接改
composer.json的 JSON 结构但没处理嵌套对象,或没保留原始私有源位置,结果把内部依赖源给删了
别碰 hosts 和代理层,除非你真需要全局透明路由
有人建议改 /etc/hosts 或起本地 composer-proxy,这确实能让所有 PHP 工具无感切换,但代价是运维复杂度陡增——你要维护一个额外服务,还要处理证书、缓存一致性、并发连接等问题。对大多数团队,不如把三步脚本封装成 ci-install 命令,加进 Makefile 或 GitHub Actions 的 step 里,简单、可控、可 debug。
最常被忽略的一点:即使你写了完美的 repositories 数组,只要没在每次 composer install 前运行探测脚本,就等于没容灾。Composer 本身不关心网络状态,它只忠实地执行你上次写死的配置。











