composer install 的本质是“照 lock 文件还原环境”,严格按 composer.lock 中记录的精确版本、hash、url 和依赖树执行下载解压,完全忽略 composer.json 的变更;lock 缺失时才退化为重算并生成新 lock。

composer install 不是“装新包”,而是“照 lock 文件还原环境”——只要 lock 存在且合法,它就绝不会读 composer.json 的任何变更,也不会升级、添加或删除任何包。
composer install 为什么只认 composer.lock?
因为它的设计目标就是复现一个已验证过的依赖快照。lock 文件里存的是每个包的精确版本号、完整 hash、下载地址和依赖树结构,install 命令直接按这个清单执行 I/O 操作:下载、解压、写入 vendor/,全程跳过依赖求解。
- 如果
composer.lock缺失,install 会退化为“按composer.json重算 + 生成新 lock”,这不是常态,而是 fallback 行为 - 如果 lock 和 json 不兼容(比如 json 删了某个包但 lock 还留着),install 会报错或尝试“修复”——这不是更新,而是纠错
- CI/CD 脚本里漏掉
--locked参数,就可能在 lock 异常时悄悄重算,导致生产环境漂移
常见错误现象:install 后 lock 文件变了,但没动过 json
这通常不是 install 主动改了 lock,而是 lock 和 json 出现了不一致,Composer 在“强制对齐”。典型场景包括:
- 手动删了
composer.json里的某个require,但没运行composer update清理 lock - lock 文件被编辑过(比如手改了版本号),导致校验失败
- PHP 版本或平台配置(如
ext-curl)与 lock 中记录的不匹配,触发 platform-check 修正
此时 composer install 实际执行的是“partial update”,而非纯还原。
composer install --locked 是生产部署唯一安全姿势
加 --locked 会让 install 在 lock 缺失或损坏时直接失败,而不是自作主张重算。这是防止意外升级的硬性开关。
- 不加
--locked:lock 缺失 → 按 json 重算;lock 格式异常 → 尝试修复;lock 内容不全 → 补全 - 加
--locked:lock 缺失 → 报错退出;lock 校验失败 → 报错退出;一切以 lock 为准 - GitHub Actions / Docker 构建中必须显式声明
composer install --no-interaction --no-scripts --locked
底层到底做了什么?不是“解析依赖”,而是“搬运文件”
install 的核心流程极简:读 lock → 查缓存 → 下载 zip → 解压 → 写 autoload → 完事。它不调用 Solver,不查 Packagist API,不递归分析子依赖兼容性。
- 本地有缓存包(
~/.composer/cache/files/)时,直接复制,几乎零网络请求 - 所有包路径由
vendor/composer/installed.json统一维护,autoload 逻辑由vendor/autoload.php驱动 - 若项目用了
composer/installers,则每个包的安装路径由其type和对应 installer 的getInstallPath()决定,但 install 本身不参与路径计算——它只把解压后的内容扔到那个路径
真正复杂的部分(比如怎么算出 lock 里那个版本号)发生在 composer update 或首次 install 生成 lock 时,install 自己只是个确定性的搬运工。











