composer why用于查“谁显式要求该包”,只显示直接在require或require-dev中声明它的上游包及版本约束;输出为空说明该包是项目根composer.json中直接声明的。

直接运行 composer why vendor/package-name 就能定位——但它只告诉你“谁显式 require 了它”,不是“谁间接用了它”。输出为空,大概率是你自己写在 composer.json 里的。
为什么 composer why 没输出?先确认三件事
执行后返回 “No dependencies exist for…” 或 “Could not find package…” 不代表命令失效,而是前提没满足:
- 包名必须全小写、带斜杠,
monolog/monolog≠Monolog/Monolog≠monolog - 该包必须已安装并记录在
composer.lock中:先跑composer show monolog/monolog确认存在 - 如果它只出现在
require-dev里(比如phpunit/phpunit),不加--dev参数就查不到
composer why --tree 才能看到真实安装路径
默认不带参数只显示第一层引用者,容易误判。加 --tree 才能看清从根项目开始的完整链路:
my-project
└── laravel/framework ^10.0
└── monolog/monolog ^2.0
注意两点:
- 缩进反映的是当前
composer.lock的实际解析结果,不是composer.json的理论声明;刚改完配置但没composer update,--tree显示的就是旧链 - 若某一层突然中断(比如停在
psr/log就没了),大概率是上游包用了"provide": {"psr/log-implementation": "*"},Composer 不会继续展开虚拟包
composer why 和 composer depends 别混用
这两个命令语义相反,行为也不同:
-
composer why monolog/monolog:回答“谁把我拉进来的?”——只查require或require-dev中显式声明它的包,带版本约束,结果即时可信 -
composer depends monolog/monolog:回答“我让谁离不开?”——默认只返回扁平列表,不带版本号,且 Composer 2.2+ 才支持;要查 dev 依赖得加--all - 想查“谁在死守旧版”,用
composer prohibits guzzlehttp/guzzle:^7.8,它会列出所有硬性要求旧版的包及其具体版本
真正卡升级的,往往不在 why 显示的那条链上
composer why 只返回最短路径(字典序最小),但冲突常藏在另一条未显示的依赖链里。比如它显示 foo/bar 拉入了旧版 guzzlehttp/guzzle,可实际拦住升级的是 bar/baz 的 "conflict": {"guzzlehttp/guzzle": ">=7.0"} 字段。
这时候必须换命令:composer why-not target/package:desired-version。它会模拟安装过程,告诉你第一个阻断点在哪——可能是根项目的 conflict、某个已装包的硬性约束,甚至 PHP 版本不匹配。











