packagist.org 响应本质是 composer 协议兼容的结构化元数据服务,含严格校验的 packages.json、带签名的 notify 字段及依赖图谱;离线必须用 satis 等语义级工具生成合法镜像,而非简单 mock http。

packagist.org 响应本质是什么?
Composer 离线时卡在 composer install 或 composer update,根本原因是它默认要请求 packagist.org/packages.json 和各包的 dist / source 元数据。这些不是“随便伪造一个 JSON 就行”,而是有固定结构、签名(部分带 packages.json 的 notify 字段)、以及依赖关系图谱。
离线模拟的关键不是“挡掉网络请求”,而是让 Composer 认为它正连着一个合法的 Packagist 镜像——哪怕这个镜像只返回本地已有的包信息。
用 satis 构建最小可用离线仓库
satis 是官方推荐的静态包仓库生成工具,适合离线场景。它不提供实时服务,但能生成 Composer 可直接读取的 packages.json 和压缩包链接(指向本地文件或内网路径)。
- 必须提前在有网环境运行
satis build,把所需包及其所有依赖递归下载并快照 - 生成的
packages.json要放在 Web 服务器根目录(如 Nginx 的/var/www/html),或用php -S快速起服务 -
composer.json中必须显式配置仓库:{ "repositories": [ { "type": "composer", "url": "http://localhost:8000" } ] } -
satis.json中禁用archive的skip-dev(否则 dev 分支包丢失),并确保require-all设为true或明确列出所有需要的包名+版本约束
常见错误:直接把 vendor 目录当仓库;或者只复制了 vendor/foo/bar 但没生成对应的 packages.json 条目 —— Composer 根本看不到这个包。
为什么不能只改 hosts + mock HTTP 响应?
有人想用 hosts 把 packagist.org 指向本地 127.0.0.1,再用 Python/PHP 写个简单 HTTP handler 返回假 JSON。这会失败,因为:
- Composer 对
packages.json有强校验:字段如packages、notify-batch、providers缺一不可,且嵌套层级严格 - 各包的
distURL 必须可访问(即使本地 file:// 协议也需开启allow_url_fopen,且 Composer 3.x 已默认禁用) - 如果用了
composer.lock中的content-hash,而生成的packages.json结构微小不一致(比如字段顺序、空格、多余逗号),就会触发 hash 不匹配报错:Content hash mismatch
所以,绕过 Packagist 的唯一可靠路径是用 satis 或 private Packagist(如 Toran Proxy)这类理解 Composer 协议语义的工具,而不是 HTTP 层面的“假装”。
离线环境必须预装的依赖项
离线机器上,光有 packages.json 不够。Composer 还要能:
- 解压
.zip/.tar.gz包:确认系统已安装unzip(Linux/macOS)或对应解压工具(Windows 需额外配置7z并在composer.json中设"archive": {"format": "zip"}) - 执行
install时的脚本(如post-install-cmd):确保 PHP 扩展(如zlib、openssl)已启用,否则解包或验证签名失败 - 若项目含
platform配置(如"php": "8.1.0"),离线机器的 PHP 版本必须严格匹配,否则composer install会跳过包安装并报Your platform matches the required version类似提示(实际是反向逻辑,容易误读)
最常被忽略的是:satis 生成的 dist URL 默认是 https://...,离线时得批量替换成 file:///path/to/dist/,并且该路径必须对运行 Composer 的用户可读。










