“升级所有依赖到最新版”不是一条命令能安全解决的事——它本质是重新求解整个依赖图,受composer.json约束严格限制,主版本跃迁必须手动改约束,否则composer update永远不会跨大版本。

很多刚接触 Composer 的朋友,一看到“升级所有依赖”这个需求,第一反应就是:composer update 总该搞定了吧?结果跑完命令,发现包版本纹丝不动,或者只升了几个小版本,主版本号动都不动。其实这不是 Composer 在偷懒,而是它严格遵守了你写在 composer.json 里的“游戏规则”。
composer update 默认只升小版本,不碰主版本
运行 composer update 时,Composer 会读取 composer.json 中每个包的版本约束,比如 "monolog/monolog": "^2.0"。^ 号的意思是“兼容主版本的小版本更新”,所以它只会安装 2.x 系列里最新的那个,比如从 2.9.1 升到 2.10.3。但 3.0.0?绝对不会碰,因为那已经超出了约束的“安全区”。
- 常见误判:执行完
composer update后composer show monolog/monolog结果仍是2.x,很多人就以为命令失效了——其实只是约束卡住了脖子。 - 那怎么知道哪些包有主版本更新?先跑
composer outdated,输出里带!标记的才是主版本跃迁(比如lara vel/framework 9.52 → 10.38 !)。 - 有人会加
--ignore-platform-reqs或--with-all-dependencies参数,但这并不能解决根本问题,它们依然服从原来的版本约束。
真要跨主版本升级,必须手动改 composer.json
Composer 不会替你做出架构决策。比如从 Lara vel 9 升级到 10、Symfony 5 到 6、Monolog 2 到 3,官方文档都明确要求你先编辑 composer.json,再运行 update。这才是正确的流程。
- 把
"lara vel/framework": "^9.0"改成"^10.0",保存后执行composer update lara vel/framework --with-all-dependencies。 - 别忘了它的直系依赖:比如 Lara vel 10 要求
phpunit/phpunit≥ 10,symfony/console≥ 6.3,这些也必须同步调整约束。 - 改完别急着 commit,先用
composer why-not lara vel/framework:10.*查一下谁在拦着——可能是你项目里某个旧插件硬锁了symfony/event-dispatcher5.x 版本。
批量更新多个指定包,比全量更可控
与其赌一把 composer update 全量更新不翻车,不如明确列出要升级的几个关键包,让 Composer 只重算这部分依赖树。这样风险更可控,也更容易排查问题。
- 命令格式:
composer update guzzlehttp/guzzle monolog/monolog symfony/http-foundation --with-all-dependencies - 它只会更新这三者及其全部子依赖,其他包(如
phpunit/phpunit或doctrine/dbal)完全不动,互不干扰。 - 前提是这些包已经在
composer.json的require或require-dev中声明过;没有声明的包不会被识别。 - 升级后立刻执行
composer dump-autoload -o,否则新类可能会报Class not found的错误。
升级后最常被忽略的三件事
很多人跑完 update 就以为万事大吉了,结果上线后才发现问题,回头还得花时间排查。下面这三件事,建议你每次升级后都检查一遍。
composer.lock必须提交到版本库:它记录了实际安装的每个包的 SHA 值。如果不提交,团队其他人执行composer install时安装的还是旧版本,甚至可能因为依赖冲突而报错。- 别跳过测试:哪怕只是小版本升级,
symfony/console6.4.10 可能悄悄改了Command::execute()的返回值类型,导致 CI 流水线直接亮红灯。 - 全局 Composer 版本也要检查:
composer --version输出里如果包含snapshot或beta字样,立刻用composer self-update回退到稳定版。生产环境只认稳定版,这是原则。