composer-unused是目前唯一能可靠识别“用了但没声明”的工具,它通过静态扫描PHP源码中的use、new等显式调用暴露隐式依赖,而Composer自身不扫描代码,install成功不代表声明完整;需项目级安装、先执行composer install、并根据实际目录结构指定--scan-dir,且对动态类名、反射、配置驱动等运行时行为完全无感,必须人工验证。

composer-unused 是目前唯一能可靠识别“用了但没声明”的工具
Composer 自身不扫描 PHP 代码,composer install 成功不代表所有 use 或 new 都有对应声明。真正被代码调用却漏写进 require 的包,只有靠静态分析源码才能暴露。
常见错误现象:Class not found 在 CI 报错,但本地能跑——因为某个包(如 guzzlehttp/guzzle)被其他依赖顺带装上了,而你的代码里明明写了 new GuzzleHttp\Client(),却没在 composer.json 中声明。
- 安装推荐用
composer require --dev composer-unused/composer-unused(项目级,避免全局冲突) - 运行前必须先执行
composer install,否则 autoload 信息缺失,大量误报 - 默认只扫
src/,若业务代码在app/或lib/,需显式加--scan-dir app - 它不识别动态类名:
$class = 'Foo\Bar'; new $class()这类永远标为“未使用”,得人工补白名单
为什么 phpstan / psalm / grep 都不能替代
这些工具目标不同:phpstan 检查类型是否合法,不是依赖是否声明;grep 容易漏掉别名、字符串类名、class_exists() 调用等。
典型误判场景:
-
use Illuminate\Support\Str as Helper→ grepIlluminate\Support\Str找不到 -
config/services.php里写了'cache' => \Illuminate\Cache\CacheManager::class→ 静态分析不读 PHP 配置数组 - 框架服务容器注入的类(如 Laravel 的
App\Services\PaymentService)→ 没new也没use,但实际被调用
运行时行为和配置驱动是最大盲区
composer-unused 只看源码中显式语法,对以下三类完全无感:
- 运行时反射加载:
class_alias()、spl_autoload_register()、eval() - 注解驱动:
@ORM\Entity、@PHPStan\Extension等,不触发use - DI 容器或框架配置:
services.yaml、routes/web.php、bootstrap/app.php中的字符串类名
这类情况必须人工确认。比如 symfony/polyfill-mbstring 虽然没被任何 use 引用,但它在 autoload 后注册了 mb_strlen 函数——删了就炸。
CI 中拦截隐式依赖的关键参数
本地扫出问题只是第一步,CI 才是防线。关键不是让它“报告”,而是让它“失败”:
- 加
--fail-on-unused:发现“用了但没声明”就让 CI 步骤退出 - 配合
--min-confidence=high:过滤掉低置信度结果(如仅出现在注释或测试中的类名) - 排除干扰路径:
--exclude tests --exclude vendor --exclude bootstrap/cache - 生产环境检查务必加
--no-dev,避免把phpunit类误当主依赖
真正容易被忽略的是:某些包虽未在 PHP 代码中显式引用,但被 composer.json 的 replace 或 provide 字段间接启用,或者通过 autoload.files 触发副作用——这种“静默依赖”只能靠全量测试 + 错误堆栈反推,工具扫不出来。











