Composer子依赖版本由SAT求解器全局决定,非父包或仓库指定;install时直接按composer.lock精确安装,不重新解析;冲突报错源于求解器返回的最小不可满足子集。

Composer 不会“递归安装子依赖”,而是把整个依赖图一次性建模为逻辑命题,再用 SAT 求解器全局求解——你看到的 monolog/monolog 依赖 psr/log,不是它自己去拉取,而是 solver 在推演时已把 psr/log 的所有可用版本纳入约束系统。
composer update 时,子包的版本由谁决定?
不是父包说了算,也不是仓库返回的第一个版本,而是 SAT 求解器在满足全部约束前提下选出的“唯一可行解”。这个解必须同时满足:
-
require中所有直接依赖的版本范围 - 每个直接依赖的
composer.json里声明的require(即子依赖) - 所有间接依赖的
conflict、replace、provide规则 - PHP 平台版本、稳定性标记(如
stable)、config.platform等全局限制
例如:你的项目 require "monolog/monolog": "^3.0",而 monolog 3.5.0 的 composer.json 写着 "psr/log": "^1.0 || ^2.0",但另一个包又 conflict "psr/log": "^2.0",那么 solver 只能选 psr/log:1.2.0 ——哪怕 2.0.0 更新,也因逻辑矛盾被排除。
为什么 composer install 不解析子依赖,却能装对版本?
因为 composer.lock 里已经存好了完整解:不仅记录了你 require 的包,还精确列出了每个子依赖(包括子依赖的子依赖)的 name、version、source 和 dist 校验和。执行 composer install 时,它只做两件事:
围绕关键发现、作用机制、临床相关性及研究局限性展开讨论。适用于撰写或优化任何生物医学论文的“讨论(Discussion)”部分——包括结果解读、与既往文献关联、阐释意外发现、界定研究局限性,以及撰写结论。当用户输入以下任一指令时也会自动触发该功能: - “write my discussion” - “help me discuss my findings” - “how do I compare to prior studies” - “write the limitations par
- 校验当前
composer.json的require是否被composer.lock.packages中的版本满足(不重算) - 按
lock文件逐条下载并校验哈希,跳过所有逻辑推理
所以如果你手动删掉 lock 里某个子包(比如 "psr/container": "2.0.2"),composer install 会直接报错 Package psr/container is not installed,而不是尝试去“重新解析”它该装哪个版本。
子依赖冲突报错里出现的 “xxx requires yyy ^2.0, but zzz requires yyy ^1.0” 是怎么来的?
这不是 Composer “查到一半” 发现的,而是 SAT 求解器完成全局搜索后,反向提取出的最小不可满足子集(MUS)。它意味着:在当前所有约束下,yyy 无法同时满足 ^2.0 和 ^1.0 的交集(即空集),且去掉其中任一约束,问题就可解。
- 常见诱因:
laravel/frameworkv10 要求guzzlehttp/guzzle: ^7.5,而aws/aws-sdk-phpv3.280 要求guzzlehttp/guzzle: ^5.3 || ^6.2 || ^7.2 ——表面看有重叠,但若前者实际依赖的是guzzlehttp/psr7v2.5.0,后者又 conflictguzzlehttp/psr7: "^2.0",就构成隐性断链 - 关键点:报错里列出的包,未必是你
require的,很可能是某一层子依赖的子依赖;你不能只盯着第一层composer.json改 - 验证路径:用
composer why-not vendor/package:version查谁在阻止该版本被选中
最易被忽略的是:子依赖的约束可能来自 require-dev 包(尤其在 CI 上跑 composer update --no-dev 却仍卡住时),或来自 replace 声明导致的隐式替代(比如一个私有 fork 替换了 symfony/polyfill,却没同步更新其 provide 列表)。

















