composer install 严格按 composer.lock 安装已锁定版本,确保环境一致;composer update 重解析依赖并更新 lock,用于升级。lock 文件必须提交 Git,它是依赖一致性的唯一事实来源。

composer install 是“照单抓药”,只认 composer.lock
它不查远程包仓库、不重新解析依赖、不改任何版本决策——只要 composer.lock 存在,就按里面写死的版本号、哈希值、源地址,一模一样地装进 vendor/。结果完全可预测,速度也快。
- 有
composer.lock→ 严格安装 lock 里记录的每个包的确切版本(比如monolog/monolog v2.10.2) - 没有
composer.lock→ 退化为首次解析composer.json,生成新 lock 并安装(仅发生在全新项目或误删 lock 后) - 生产部署、CI/CD、新人拉代码后,必须用
composer install --no-dev,这是唯一安全做法 - 即使
composer.json里写着"^2.0",install 也绝不会升级到v2.11.0—— 它根本不看那行
composer update 是“重开处方”,主动忽略 composer.lock
它把现有 composer.lock 当废纸,重新联网请求 packagist,运行依赖求解器,找出所有满足 composer.json 中约束(如 ^3.5 或 dev-main)的最新组合。这个过程可能引入新 bug、破坏性变更,且必然改写 lock 文件。
- 执行后一定会更新
composer.lock,哪怕只是微小修订版(v4.2.1 → v4.2.3) - 耗时明显更长:大量 HTTP 请求 + PHP 计算依赖图 + 下载新包
- 可以精准控制范围:
composer update doctrine/dbal只更新这一个包及其子依赖 - 别在服务器上跑
update:它不区分环境,--no-dev只影响安装行为,不影响求解过程本身
为什么 composer.lock 必须提交到 Git?
因为它是团队和线上环境一致性的唯一事实来源。不提交 lock,等于让每个人、每次部署都随机“抽卡”——今天装的 symfony/http-kernel v6.4.3,明天 CI 跑出来可能是 v6.4.7,而后者悄悄改了某个事件监听器的参数顺序。
-
composer.json是“需求说明书”,描述“想要什么”;composer.lock是“验收清单”,记录“实际拿到什么” - 删除 lock 后运行
install,等价于一次隐式update,风险不可控 - 如果某次
update导致测试失败,回滚只需git checkout上一版 lock,再install即可复原
常见错误现象与应对
这些不是报错,而是你已经在踩坑的信号:
- 「本地能跑,CI 报 Class not found」→ 很可能 CI 用了
install,但你本地没提交新生成的 lock,或者有人偷偷跑了update没提交 lock - 「
composer install提示 “lock file is not up to date”」→ 说明composer.json被改过(比如加了 require),但没运行update或require更新 lock,此时应先确认是否需要升级,再决定是update还是require xxx - 「
update后 vendor 里多出一堆没声明的包」→ Composer 自动拉入了传递依赖的新版本,而旧 lock 里没锁死它们的版本,暴露了语义化版本不严谨的问题
composer.lock 不是缓存,也不是临时文件;它是你在 PHP 依赖世界里的锚点。一旦理解它被谁读、被谁写、被谁忽略,install 和 update 就不再是两个相似命令,而是两种截然不同的操作意图——前者是交付,后者是实验。










