离线更新依赖必须在线机完整执行composer install --no-dev --prefer-dist后,打包整个~/.composer/cache/files/(Linux/macOS)或对应Windows缓存目录,连同composer.lock一并迁移至离线机;迁移前需删除离线机vendor/和composer.lock,且在线机与离线机Composer版本必须一致,否则缓存格式不兼容。

离线环境不能直接运行 composer update,它会卡在包下载阶段;依赖替换不是改个 replace 就完事,漏掉 conflict 或没 remove 旧包,运行时必报 Fatal error: Cannot declare class。
离线更新依赖必须打包完整缓存目录
在线机执行 composer install --no-dev --prefer-dist 后,~/.composer/cache/files/(Linux/macOS)或 C:\Users\用户名\AppData\Roaming\Composer\Cache\files(Windows)里所有 ZIP 必须整体打包迁移。只拷部分 ZIP 或只传 vendor/ 目录,子依赖(transitive dependencies)会缺失,composer install 在离线机上直接失败。
常见错误现象:Could not find package vendor/name in a version matching "x.y.z",但你明明打包了那个包——因为它的某个子依赖没进缓存,Composer 解析时找不到完整依赖链。
- 务必确认在线机已执行过完整安装,而非仅
composer update或require主包 - 离线机的 Composer 版本需与在线机一致,否则缓存格式不兼容(如 Composer 2.5 缓存无法被 2.2 读取)
- 迁移后首次运行
composer install前,先删掉本地vendor/和composer.lock,避免残留干扰
用 replace 替换旧包前必须手动 remove + 配 conflict
replace 字段只影响依赖解析,不删文件、不卸载包。如果你还在 composer.json 的 require 里留着 "old-vendor/package": "^1.0",Composer 就会坚持装它,replace 完全无效。
正确流程只有三步,缺一不可:
- 运行
composer remove old-vendor/package(强制卸载旧包) - 在新包的
composer.json中写明"replace": {"old-vendor/package": "*"}和"conflict": {"old-vendor/package": "*"} - 运行
composer install(此时旧包已不在依赖图中,replace才起声明作用)
漏掉 conflict 是最常见翻车点:两个同名类同时存在,PHP 自动加载器直接崩溃,报错信息就是 Fatal error: Cannot declare class X,和 Composer 安装过程无关。
离线环境下依赖版本升级要靠锁文件+人工校验
离线机无法访问 Packagist,所以 composer outdated 和 composer update 全部失效。想升版本,只能靠“在线机生成新 composer.lock → 离线机 install”这一条路。
操作要点:
- 在线机先改好
composer.json(比如把"guzzlehttp/guzzle": "^7.2"改成"^8.0"),再运行composer update guzzlehttp/guzzle --with-all-dependencies - 确保在线机的
composer.lock已提交到 Git,且vendor/未被忽略(便于后续比对) - 离线机拉取新
composer.lock后,执行composer install --no-dev,不要加--ignore-platform-reqs,否则可能装错 PHP 扩展依赖
注意:如果新 composer.lock 里某包的 URL 指向的是 https://api.github.com/ 这类外部源,离线机仍会失败——说明在线机制作时没走 --prefer-dist,得重来。
依赖树里还有“钉子户”时 replace 就是摆设
即使你本地 clean 干净、replace 和 conflict 都配齐,只要下游某个已安装包(比如 some-tool)的 composer.json 里还写着 "require": {"old-vendor/package": "^1.0"},离线机上 composer install 就会重新拉下旧包。
查谁在拖后腿:
- 运行
composer depends old-vendor/package,输出非空即表示存在硬依赖 - 对应包必须升级到支持新包的版本,或你 fork 后修改其
composer.json中的require - 长期不更新的“钉子户”,只能放弃
replace,改用repositories+require强制指向你的 fork 分支
别信 composer.lock 里没出现旧包就万事大吉——它可能被某个子依赖间接拉入,而 lock 文件只记录最终结果,不记录来源路径。











