必须用静态分析工具(如phpstan)配合手动验证,因composer本身不分析代码调用;composer-unused虽可用但漏检动态加载、配置驱动等场景,误报率高,需人工交叉确认。

如何用 Composer 插件检测未被引用的依赖
Composer 本身不提供“哪些包没被代码调用”的分析能力,composer install 或 composer update 只管依赖图和 autoload 注册,不管实际使用。真正能发现“装了但没用”的包,得靠静态分析工具配合插件机制。目前最可行路径是:用 phpstan 或 psalm 扫描 vendor/ 中已安装包的类是否被项目代码引用,再结合 composer show --direct 筛出顶层依赖,交叉比对。
关键点在于——不能只看 composer.json 的 require 列表,还得确认这些包的类是否出现在 new XyzClient()、use Vendor\Package\Foo; 或反射调用中。否则容易把“仅用于 CLI 命令或测试工具”的包误判为冗余。
-
phpstan需启用--autoload-file=vendor/autoload.php并配置scanDirectories包含src/和tests/,否则不会发现对 vendor 类的引用 - 若项目用了
class_alias或字符串类名(如$class = 'Monolog\Logger'; new $class),静态分析大概率漏检,这类需人工核验 - 某些包(如
symfony/polyfill-*)虽无直接use,但被其他依赖间接 require,剔除会导致运行时 fatal error,必须保留
编写 PHP 脚本提取未被引用的顶层依赖
核心逻辑是三步:读取 composer show --direct --format=json 获取所有直接依赖;用 ReflectionClass 或 token_get_all 扫描项目源码中出现的完整命名空间类名;过滤出“在 vendor/ 中存在、且被声明为直接依赖、但从未在源码中被引用”的包名。
注意:不要解析 vendor/composer/autoload_classmap.php,它只反映 autoloader 映射,不等于实际使用;也不要依赖 composer dump-autoload -o 后的优化文件,那只是性能产物。
- 扫描时跳过
tests/目录下以Test.php结尾的文件,避免把测试依赖当成生产依赖 - 对每个候选包,检查其
autoload psr-4声明的命名空间前缀是否出现在任何use语句或new表达式中,而不是只查包名字符串(比如guzzlehttp/guzzle的类都在GuzzleHttp\下) - 用
json_decode(file_get_contents('composer.lock'), true)补充版本锁定信息,避免因 dev-main 或分支别名导致误判
为什么不能直接用 composer-unused 工具
composer-unused 确实能列出疑似未使用的包,但它默认只检查 use 语句和 new,不处理 class_exists('Foo') 、is_subclass_of()、interface_exists() 这类运行时判断,更不识别 DI 容器的字符串配置(如 Symfony YAML 中的 arguments: ['@monolog.logger'])。实际项目中,这类情况占比不小。
围绕关键发现、作用机制、临床相关性及研究局限性展开讨论。适用于撰写或优化任何生物医学论文的“讨论(Discussion)”部分——包括结果解读、与既往文献关联、阐释意外发现、界定研究局限性,以及撰写结论。当用户输入以下任一指令时也会自动触发该功能: - “write my discussion” - “help me discuss my findings” - “how do I compare to prior studies” - “write the limitations par
它的 --no-dev 模式会忽略所有 require-dev,但有些开发依赖(如 phpunit/phpunit)可能被 bin/phpunit 直接调用,脚本里没 use 却必不可少。
- 执行前务必加
--dry-run,它默认会修改composer.json,而修改后若没composer update,lock 文件仍保留旧记录 - 它不区分 PSR-4 和 classmap 加载方式,对用
filesautoload 的函数库(如ramsey/uuid的src/functions.php)完全无感知 - 报告中的 “unused” 状态只是静态推断,不是运行时证据,上线前必须跑一遍全量测试
手动验证与安全剔除的关键检查项
自动脚本只能给出候选列表,最终是否移除必须人工确认。最容易翻车的是那些“不写 use、不 new、但不可或缺”的包。
一个包即使没被直接引用,也可能通过以下方式生效:作为其他包的依赖被拉入;提供编译期宏(如 paragonie/random_compat);被 Composer 的 scripts 字段调用;或在 autoload files 中被全局加载。
- 运行
composer depends <package-name></package-name>查看谁依赖它,如果返回空,才说明它是顶层节点 - 搜索整个项目(包括
.env、config/、bin/)是否有该包名的字符串,比如DB_CONNECTION=pgsql虽不涉及类,但依赖ext-pdo_pgsql和对应驱动包 - 临时注释掉
composer.json中某条require,执行composer update --dry-run,观察是否报错“Package x is required by y”,这比静态分析更可靠
真正难处理的是那些既没被代码引用、又没被其他包声明为依赖,但删掉后 CI 突然失败的包——它们往往藏在 PHP 扩展绑定、环境变量开关或构建脚本里,得顺着失败日志一层层反查。










