没有“一键排除未被引用过渡依赖”的安全命令,必须通过composer-unused扫描、composer depends验证及grep人工确认三步交叉验证才能确认冗余;盲目清理易破坏autoload或运行时反射加载。

没有“一键排除未被引用过渡依赖”的安全命令,所谓“过渡依赖”是否冗余,必须靠三步交叉验证才能确认;盲目清理反而容易破坏 autoload 或运行时反射加载。
composer remove 不会自动清理过渡依赖
执行 composer remove vendor/package-name 只删你显式声明的包,以及它在 composer.json 中直接 require 的子依赖(前提是这些子依赖没被其他包 require)。它不会扫描整个 vendor/ 目录,把只被刚删包引用、但当前项目代码里也没用到的“孤儿包”一并踢掉。
- 比如删了
guzzlehttp/guzzle,guzzlehttp/promises和psr/http-message可能还留在vendor/里——只要symfony/http-client还 require 它们,Composer 就认为它们“有用” -
--with-dependencies参数只对当前被删包的直接子依赖生效,且仅限于它自己require的部分,不递归清理“被子依赖 require 的孙子包” - 强行删掉这些包,可能导致
class_exists('GuzzleHttp\Promise\Promise')这类动态检查失败,或配置里字符串驱动名(如'driver' => 'guzzle')触发运行时报错
真正能识别“未被引用过渡依赖”的工具是 composer-unused
composer-unused 是目前最贴近需求的方案,但它不是“全自动”,需配合人工判断:
- 先装插件:
composer require --dev composer-unused/composer-unused - 默认只扫
src/,若业务逻辑在app/或lib/,得加--scan-path=app --scan-path=lib - 测试文件里的使用会被忽略,需显式加
--scan-tests - 它无法识别字符串反射(如
new $className)、注解(Doctrine、PHPStan)、或.env里的驱动名,这些必须人工grep -r确认 - 输出列表只是“疑似冗余”,比如
symfony/polyfill-*常被核心组件悄悄 require,删掉后 PHP 低版本直接Fatal error
删完之后最容易被忽略的残留点
composer remove 只管 composer.json、vendor/ 和 autoload 映射,其余全靠手动:
-
config/app.php或app/Providers/里注册的 Service Provider 类仍存在,运行时会尝试加载已删包的类 -
autoload.psr-4或autoload.classmap里硬编码了已删包的路径,导致composer dump-autoload后仍尝试扫描不存在目录 -
database/migrations/或config/里的类名字符串(如'handler' => 'Monolog\Handler\StreamHandler')没改,运行时抛出Class not found -
composer clear-cache和composer dump-autoload --optimize需手动补上,否则旧 autoload 映射可能还在生效
真正干净的发布不是靠“删得多”,而是靠“删得准”——每个被移除的包,都得有 composer depends 为空、composer-unused 确认、grep 验证三重证据。中间任何一环跳过,都可能让线上报错出现在最意想不到的地方。











