composer找不到未声明的已安装包,因其仅以composer.json和composer.lock为依赖权威来源,vendor/目录仅为安装产物;需用composer-unused扫描代码引用或手动比对composer show与ls vendor/结果。

composer为什么找不到未声明的已安装包?
Composer 本身不维护“已安装但未声明”这类状态的索引,vendor/ 下的包只是安装产物,不是项目依赖图的权威来源。它只在 composer.json 和 composer.lock 里记录显式声明的依赖。所以直接跑 composer outdated 或 composer show 都不会标出“偷偷混进来”的包。
用 composer-unused 扫描未声明却实际被引用的包
这是目前最贴近需求的工具:它静态分析 PHP 代码(use、new、函数调用等),再比对 composer.json 的 require 和 require-dev,找出“用了但没声明”的包。
常见错误现象:composer-unused 报 Package xyz is required but not used(误报)或漏掉真正该报的(比如用了 class_alias 或字符串类名)。
- 安装:运行
composer global require composer-unused/composer-unused(推荐全局) - 运行:在项目根目录执行
composer-unused,它默认扫描src/和tests/,可通过--scan-dir扩展 - 注意:它不检测运行时
include或eval引入的代码,也不处理反射动态加载的类 - 若项目用 PSR-4 自动加载但命名空间和路径不严格匹配,可能漏判——先确保
composer dump-autoload -o能正常加载所有类
手动验证:检查 vendor/ 里哪些包不在 composer.json 中
这解决的是另一类问题:“谁被装进来了,但我根本没打算用”。比如团队成员手动 composer require xxx 后忘了提交 composer.json,或者 CI 环境残留了旧包。
操作很简单,但容易踩坑:
- 先运行
composer install --no-dev(或--no-scripts)确保环境干净,避免脚本自动拉包干扰 - 对比两份列表:
composer show --no-dev --name-only | sort(声明的) vsls vendor/ | sort(实际有的) diff 可快速看出差异(Linux/macOS)- Windows 用户可用 PowerShell:
Compare-Object (composer show --no-dev --name-only | Sort-Object) (Get-ChildItem vendor | ForEach-Object Name | Sort-Object) - 注意:有些包如
composer-plugin-api或psr/log是其他包的传递依赖,它们出现在vendor/里完全正常,别一看到就删
为什么不能只靠 composer.lock 做反向校验?
composer.lock 记录的是完整依赖树(含传递依赖),而“未声明但已安装”通常指那些**既不在 composer.json 的 require 里,也不在任何已声明包的 composer.json 中作为依赖存在**的包——这种情况极少见,一般是 vendor/ 被手动修改、CI 缓存污染,或用了非标准安装方式(如 git clone 直接扔进去)。
此时 composer-unused 也无能为力,因为没有代码引用它,它就是个“死包”。唯一可靠手段是清空 vendor/ 和 composer.lock,再从头 composer install,看哪些包会消失——消失的就是可疑对象。
这种场景下,真正难的不是发现,而是确认它是否被某些未纳入 Git 的脚本、配置或私有代码间接依赖。得查 grep -r "vendor/xxx" . --include="*.php",甚至翻 CI 日志。










