composer-unused 是目前最轻量、最准的未引用包检测工具,通过静态扫描 use/new/static:: 等类引用比对 composer.json 中的依赖,不依赖自动加载或运行时行为。

composer-unused 能直接扫出没被引用的包
它不是 Composer 内置命令,但目前最轻量、最准的方案。原理是扫描所有 use 语句和 new、static::、self:: 等类引用,再比对 composer.json 里 require 和 require-dev 的包名,不依赖自动加载器或运行时行为。
安装和运行很简单:
composer require --dev composer-unused/composer-unused ./vendor/bin/composer-unused
默认只检查 require(生产依赖),加 --dev 才扫 require-dev;加 --no-progress 可关掉进度条,CI 里更干净。
- 它会忽略
psr-4映射外的文件(比如 tests/ 下没配置 autoload 的测试类),所以确保测试目录也进了 autoload 或手动用--path指定 - 遇到
class_exists('SomeClass')这种动态加载,它识别不了——这类得人工核对 - 如果项目用了
include/require直接载 PHP 文件(非类自动加载),也会漏判
为什么 deptrac 不适合纯“未使用依赖”检测
deptrac 是做依赖层级分析的,目标是发现违反架构规则的调用(比如 controller 直接用了 infrastructure 层),不是查“哪个包完全没被 import”。它需要你先写一堆 layers 和 rules 配置,跑出来一堆“违规路径”,但你没法一眼看出 symfony/yaml 到底有没有被任何地方用过。
如果你已经用 deptrac 管理架构,可以顺便看看哪些包没出现在任何 layer 的类列表里——但这属于间接推断,不是它设计用途。
- 它不解析
composer.json,也不对比包名,纯粹靠 AST 扫描类引用 - 没有
--unused这类开关,也没有“未引用包”汇总报告 - 误报率比
composer-unused高,尤其在有大量 trait、interface 继承的项目里
composer show -i 不能代替检测,只是罗列已安装包
composer show -i 输出的是当前 vendor/ 里装了什么,跟“代码里有没有用到”完全无关。哪怕你删光所有 use 语句,只要 composer.json 还留着那行 "monolog/monolog": "^2.0",它就一直显示在列表里。
有人试过用 grep -r "monolog\" vendor/autoload.php 这类方式反向查,但不可靠:autoload.php 里注册的是 PSR-4 映射,不是实际调用痕迹;而且很多包根本没进 autoloader(比如只提供 bin 脚本的)。
-
composer show -i的输出顺序、格式不稳定,不适合脚本解析 - 它不区分
require和require-dev,也没法告诉你某个包是否只被 test 用到 - 执行速度慢(要读取整个 vendor 目录元数据),不如
composer-unused单扫 src/ 和 tests/ 快
CI 中集成要注意 autoload 配置和排除路径
在 GitHub Actions 或 GitLab CI 里跑 composer-unused,最容易卡在 autoload 未覆盖的目录上——比如 tests/ 没配进 autoload-dev,工具就默认不扫,结果把真实使用的测试依赖标成“未使用”。
推荐做法是显式指定路径,并确保 dev autoload 完整:
./vendor/bin/composer-unused --path=src --path=tests --dev
- 别依赖
composer dump-autoload后的vendor/autoload.php,composer-unused自己解析文件,不走自动加载逻辑 - 如果项目有
phpstan.neon或rector.php这类配置文件,它们可能改了扫描范围,要同步检查 - 某些 IDE 自动生成的 stubs(如
_ide_helper.php)会被误认为是真实引用,建议用--exclude排除
真正麻烦的不是工具选错,而是团队对“未使用”的定义不一致:是“代码里完全没出现”,还是“运行时没加载过”,或是“测试通过的前提下可删”。这些边界得在第一次跑 composer-unused 前对齐,否则删完才发现某个 bin/console 命令悄悄依赖了那个包。










