Composer 全局求解依赖树而非逐个安装,因此实际安装版本需满足所有约束(如 symfony/console 强制 monolog ^1.25 || ^2.0 会导致即使声明 "^3.0" 也降级为 2.9.2);lock 文件锁定的是求解出的完整快照,非仅 composer.json 声明。

Composer 为什么总是装出你没指定的版本?
因为 Composer 不按你 require 里写的版本号“照单全收”,而是把所有包的依赖约束合起来算一棵树,再找一个满足全部条件的解。它不是逐个安装,是全局求解——就像多人约饭,得挑一个所有人都不冲突的时间点。
常见错误现象:composer install 后发现 monolog/monolog 装了 2.9.2,但你在 composer.json 里明明只写了 "monolog/monolog": "^3.0"?其实不是它无视你,是某个你引入的包(比如 symfony/console)强制要求 monolog ^1.25 || ^2.0,Composer 只能妥协选 2.9.2。
- 用
composer show --tree看谁在拉低版本:它会列出每个包引入了哪些依赖及版本范围 - 用
composer prohibits monolog/monolog:3.0查哪个包卡住了升级 - 别直接改
vendor/里的composer.lock——锁文件是求解结果,手动改大概率下次install被覆盖
lock 文件到底锁了什么?
composer.lock 锁的不是“你写了什么”,而是“这次求解出的完整依赖快照”:包括每个包的精确版本、来源(dist / source)、校验和、以及它们各自的依赖声明。它保证 composer install 在任何机器上都复现同一棵树。
容易踩的坑:composer update 会重算整棵树并更新 lock;而 composer install 只读 lock、不看 composer.json 的新约束。如果你改了 composer.json 但忘了 update,别人 install 还是旧版本——根本不会报错,静默失效。
- CI 环境必须用
composer install --no-interaction,且确保composer.lock已提交 - 删掉
composer.lock再install?等于重新update,结果不可控 -
platform配置(如"php": "8.1.0")也参与求解,写错会导致奇怪的降级行为
dev-main 和分支名版本怎么解析?
Composer 把 "package/name": "dev-main" 当作一种“不稳定版本”,优先匹配 main 分支的最新 commit,但前提是该包在 packagist.org 上启用了自动更新或你配置了 VCS repository。它不是 git clone,而是走 composer 的元数据协议。
工作流任务拆解与模型编排技能。使用场景:(1) 收到复杂工作任务需要拆解为可执行步骤,(2) 需要为不同步骤选择最合适的模型,(3) 需要跟踪工作流进度和模型使用情况,(4) 长时间任务卡住需要问题诊断和解决方案。
性能影响明显:每次 update 都要远程查分支 HEAD,比稳定版慢;而且如果分支被 force push,composer.lock 里存的 commit hash 可能失效,导致下次 install 失败。
- 生产环境禁止用
dev-前缀,CI 会拒绝安装(除非设"minimum-stability": "dev") - 想固定到某次提交?写成
"dev-main#abc1234",lock 文件会记下这个 hash - 私有 GitLab/GitHub 仓库必须加
"type": "vcs"到repositories,否则 Composer 根本不去查
require-dev 里的包会影响主依赖解析吗?
会,但只在 composer update 时参与计算;install 默认不装 require-dev,所以它不影响运行时依赖树。不过——只要你在 update 时没加 --no-dev,Composer 就会把 require-dev 里的所有约束一并塞进求解器。
典型翻车场景:你 dev 里写了 "phpunit/phpunit": "^10.0",但主项目依赖的 symfony/framework-bundle 最新版只支持 PHPUnit 9.x。Composer 为满足 dev 要求,可能把整个框架降到旧版,连带拖垮你的 runtime 兼容性。
- CI 测试环境跑
composer install --no-dev,避免意外装入 dev 包污染依赖 - 本地开发想隔离?用
composer install --with-dependencies控制范围 - 不要为了“方便”把工具类库(如
larastan)写进require——它真会参与主解析
依赖解析真正的复杂点不在语法多难,而在于它永远在平衡:你的显式声明、其他包的隐式约束、平台限制、稳定性策略、甚至远程仓库的响应一致性。稍有松动,树就歪了——而且歪得悄无声息。

















