composer install 不检查代码中实际使用的依赖,仅依据 composer.json 的 require 字段安装,导致未声明但被间接引入的包在本地正常、新环境报错;应使用 composer-unused 扫描真实依赖并接入 ci 拦截。

composer install 时不会报未声明依赖的错
Composer 只管 require 字段里写了什么,它不扫描你的 PHP 代码。哪怕你在 src/ 里写了 new GuzzleHttpClient(),只要 guzzlehttp/guzzle 没进 composer.json 的 require,composer install 依然成功——前提是这个包碰巧被其他依赖“顺带装上了”。这恰恰是最危险的情况:本地能跑,CI 或新环境直接 Class not found。
用 composer-unused 扫描实际使用的类和函数
这是目前最贴近你需求的工具。它会解析所有 PHP 文件,提取 new、use、call_user_func 等调用,再比对 composer.json 中声明的包,标出“用了但没声明”或“声明了但根本没用”的包。
- 安装:
composer require --dev composer-unused/composer-unused - 运行:
vendor/bin/composer-unused - 注意它默认跳过
tests/和vendor/,如需扫描测试代码,加--scan-tests - 它识别不了动态类名(比如
$class = 'Foo\Bar'; new $class()),这类必须人工核对
为什么 phpstan / psalm 不适合干这事
静态分析工具关注类型正确性,不是依赖完整性。它们在发现 Class not found 时,通常是因为 autoloader 没加载到——而这往往已经是 Composer 依赖缺失的**结果**,不是原因。更关键的是:
围绕关键发现、作用机制、临床相关性及研究局限性展开讨论。适用于撰写或优化任何生物医学论文的“讨论(Discussion)”部分——包括结果解读、与既往文献关联、阐释意外发现、界定研究局限性,以及撰写结论。当用户输入以下任一指令时也会自动触发该功能: - “write my discussion” - “help me discuss my findings” - “how do I compare to prior studies” - “write the limitations par
- 如果某个包已被其他依赖间接引入,phpstan 就不会报错,但它确实没在你的
require里 - 它无法区分“这个类来自我直接依赖的包”还是“来自它的子依赖”,所以没法告诉你该不该补进
require - 配置复杂,误报多,目标不匹配
CI 中自动拦截未声明依赖的实践
光靠本地扫描不够,得让 CI 拒绝合并“偷偷用包”的代码。关键是把扫描逻辑变成失败门槛:
- 在
.github/workflows/ci.yml或gitlab-ci.yml中加入步骤:vendor/bin/composer-unused --no-interaction --error-on-change -
--error-on-change表示只要发现差异(比如多了个未声明依赖)就返回非零退出码,CI 自动失败 - 首次接入时先用
--json导出报告,人工确认一批“合理但未声明”的依赖(如psr/log被框架带入),再通过ignore配置排除 - 别把它和
composer install放同一个 job——前者要读源码,后者只读composer.lock,职责分开更稳
真正的麻烦点不在工具链,而在“谁来维护 ignore 列表”和“怎么说服队友补 require 而不是删掉 use 语句”。这两件事,没有命令能帮你决定。










