composer不按项目minimum-stability约束间接依赖,只认上游包自身声明的稳定性;报“could not find package x with stability dev”是因上游包(如monolog)在require中硬绑了“psr/log”: "^3.0@dev”,与项目配置无关;需用composer show --tree --no-dev定位引入路径,配合composer depends -r找出真正锁定版本的间接依赖。

Composer 不会按你项目的 minimum-stability 去约束间接依赖,它只认上游包自己声明的约束。想靠降级项目稳定性来解决“找不到 psr/log dev 版本”这类报错,本质是绕开问题,不是解决问题。
为什么 composer install 报 “Could not find package X with stability dev” 却没在 require 里写它
这是典型的间接依赖版本解析失败。比如你 require 了 monolog/monolog,而它在自己的 composer.json 里写了 "psr/log": "^3.0@dev" —— 这个 @dev 是 monolog 自己定的,跟你项目里的 "minimum-stability": "stable" 完全无关。
- Composer 对间接依赖不做稳定性继承,只看上游包的
require字段和其发布的版本标签 - 错误不是出在你,而是出在某个上游包硬绑了一个不稳定版本范围
-
composer show --tree --no-dev是唯一能定位到这层关系的命令,别猜
用 composer show --tree 看清谁在拉哪个版本
这个命令不解析、不安装、不修改,只读 composer.lock 或已安装包元数据,把整个依赖树原样展开。缩进越深,依赖层级越低,越接近“背锅”的那个间接包。
工作流任务拆解与模型编排技能。使用场景:(1) 收到复杂工作任务需要拆解为可执行步骤,(2) 需要为不同步骤选择最合适的模型,(3) 需要跟踪工作流进度和模型使用情况,(4) 长时间任务卡住需要问题诊断和解决方案。
- 必须在项目根目录运行,否则输出为空
- 加
--no-dev:避免phpunit等开发依赖干扰主线判断 - 加具体包名聚焦:
composer show --tree --no-dev guzzlehttp/guzzle比全量树快十倍 - 看到同一包在不同缩进出现?说明多路径引入,版本很可能被某一层硬锁死
用 composer why 和 composer depends -r 找出真正卡版本的包
composer why vendor/package 告诉你“谁把它拉进来”,composer depends -r vendor/package 才告诉你“谁在死死卡住它的版本”。两者必须一起用。
-
composer why -t symfony/http-kernel能展开完整引用链,适合顺藤摸瓜 -
composer depends -r psr/log会列出所有 transitive dependencies 中对它的requires行——这才是有效约束来源 - 千万注意:漏掉
-r就只看到顶层依赖,真正锁版本的第二层包就藏过去了 - 输出里带
replaces或provides的行不用管,它们不参与版本决策
用 conflict 字段强制限制间接依赖版本
这不是“优雅方案”,但它是目前最可靠、无副作用、兼容所有 Composer 版本(包括 2.x 和 3.x)的硬控方式。原理是在解析阶段就拒绝你不想要的版本,而不是等装完再 patch。
- 在
composer.json根级加"conflict"字段:"conflict": { "guzzlehttp/guzzle": ">=8.0.0" } - 支持多个范围:
"conflict": { "phpunit/phpunit": ">=10.0", "symfony/console": ">=7.0" } - 不要用
require-dev“假装”需要它来锁版本——那会引入不必要的 autoloading 和 dev-only 类 -
conflict不影响已安装的包,只作用于下次composer update或composer install的解析过程
间接依赖管理最难的点不在命令怎么敲,而在你得习惯“不信任直接依赖的版本号”。哪怕你写了 "monolog/monolog": "2.9.1",只要它依赖的 psr/log 在自己 composer.json 里松绑到 ^3.0,你就得面对升级后的方法签名变化。看清树、找对人、卡死线,三步缺一不可。










