composer why-usage 能查到谁在偷偷拉进一个包:它显示某包被哪些依赖链引入,如 laravel/framework 9.52.16 requires guzzlehttp/psr7 (^2.4),需 Composer 2.4+,旧版可用 composer depends。

composer why-usage 能查到谁在偷偷拉进一个包
很多团队发现 vendor 越来越大,但 composer.json 里明明只写了几个包——问题往往出在“传递依赖”上。比如你没直接 require guzzlehttp/psr7,但它被 aws/aws-sdk-php 或 laravel/framework 拉进来了,还可能被多个包重复拉入不同版本。
composer why-usage 是最直接的溯源命令,它能告诉你某个包被谁依赖、在哪一层被引入:
composer why-usage guzzlehttp/psr7
输出会列出所有引用链,比如:
laravel/framework 9.52.16 requires guzzlehttp/psr7 (^2.4)
注意:这个命令需要 Composer 2.4+;低于该版本请用 composer depends guzzlehttp/psr7(效果类似但不显示版本约束)。
- 如果某包只被一个 dev-only 包引用(如
phpunit/phpunit),可考虑加--ignore-platform-reqs或检查是否真需要它在生产环境存在 - 输出中出现多个路径?说明它被多处间接依赖,删它前务必确认是否破坏核心功能
- 别只看顶层 require,
composer show --tree虽然慢,但能一次性看到整棵树,适合排查嵌套过深的依赖
require --no-update 不会立刻装包,但能安全删掉“幻影依赖”
有些包明明没在代码里用,却长期躺在 composer.json 的 require 里,可能是历史遗留、CI 误加、或早期开发时临时引入忘了清理。这类包不会报错,但会拖慢 install、增大镜像体积、增加安全扫描噪音。
安全删除分两步走:
- 先运行
composer remove vendor/package-name --no-update—— 它只改composer.json,不碰vendor和composer.lock - 再手动检查代码里是否真没调用
use VendorPackageClass或new VendorPackageThing(),尤其注意 traits、事件监听器、配置文件里的类名字符串 - 最后跑
composer update --lock(不是install)更新锁文件,验证依赖图是否仍完整
常见踩坑点:composer remove 默认会执行 update,加 --no-update 才能跳过,否则可能意外升级其他包导致行为变化。
replace 和 provide 可以让 Composer “假装”某个包已存在
某些轻量级项目想用 Laravel 的 Contracts,但不想拉整个 laravel/framework;或者你写了自定义日志实现,想替代 psr/log 的默认行为——这时 replace 和 provide 就派上用场了。
在 composer.json 里写:
"replace": {
"psr/log": "*"
}Composer 就不会再安装 psr/log,哪怕其他包声明了它为依赖。但注意:replace 是硬屏蔽,必须确保你的代码真能兼容该包的全部接口;而 provide 更温和,用于声明“我实现了这个虚拟包”,适合 SDK 类项目。
-
replace后记得运行composer update --lock,否则旧 lock 文件可能还保留着被替换掉的包 - 别用
replace屏蔽ext-*扩展(如ext-curl),那属于运行时检查,Composer 不管 - 查看哪些包被 replace 掉了?
composer show --platform会列出所有被跳过的平台包和虚拟包
vendor/bin/composer-unused 能扫出真正没被引用的类和命名空间
composer remove 只解决“声明了但不用”的问题,但更多时候,包还在 require 里,只是你的代码根本没调用它的任何类——比如装了 symfony/console 却只用 Laravel 的 Artisan 命令,或者引入 monolog/monolog 但全程走 Laravel 日志门面。
composer-unused 这个工具能静态扫描 src/ 下所有 PHP 文件,比对 vendor/ 中每个包导出的类、接口、函数,标出完全未被引用的包:
composer global require composer-unused/composer-unused composer-unused
它不会自动删包,但会输出类似这样的结果:
Unused packages: - symfony/console (0 usages)
这时候再结合 composer why-usage 确认它是不是纯传递依赖,就能放心移除。
- 扫描结果有误报?常见于动态加载(
class_exists("Foo\Bar"))、配置驱动(YAML/JSON 里写类名)、或注解(@Route、@Entity)——这些它识别不了 - 它不扫描
tests/目录,默认只看src/,如需包含测试,请加--scan-tests - 别在 CI 上直接用它做自动清理,它只负责“发现”,决策还得人来把关
真正难的不是删哪个包,而是判断“这个包的某部分功能是否被隐式依赖”。比如删掉 symfony/polyfill-* 看似省了几百 KB,但可能让 PHP 8.0+ 的新函数在低版本环境里崩掉——这种边界得靠实际运行时验证,不是静态分析能覆盖的。


















