必须显式声明所有直接依赖,否则 composer remove 或 update 可能静默删除实际在用但未写入 composer.json 的包;Composer 仅依据 require/require-dev 字段判断依赖,不扫描代码中 new、use 等调用,导致“幽灵依赖”被误删或缺失。

必须显式声明所有直接依赖,否则 composer remove 或 composer update 可能静默删掉你实际在用、但没写进 composer.json 的包。
为什么 vendor 里明明在用的类,composer remove 却说“not required”?
Composer 不看代码里有没有 new SomeClass(),只认 composer.json 的 require 和 require-dev 字段。如果某个包是被另一个依赖“顺带拉进来”的(比如 laravel/framework 拉了 symfony/console),而你又没在自己的 composer.json 里显式声明它,那它就是“幽灵依赖”——composer show 能看到,composer remove 却拒绝操作,composer update 还可能把它整个踢出 vendor/。
- 典型现象:
php artisan tinker突然报Class 'Symfony\Console\Application' not found,但项目里明明没动过命令行逻辑 - 根本原因:
symfony/console是被其他包间接引入的,且你没在require中锁定它 - 验证方式:运行
composer show --tree symfony/console,如果顶层不是你的项目名,而是某个第三方包,就说明它是幽灵的
require 里漏写包,--no-dev 部署时直接崩
线上部署常用 composer install --no-dev,但它不会跳过 require 里的任何包——哪怕那个包你只在本地 CLI 脚本里用。一旦它被当成“间接依赖”塞在别人包的 require 里,--no-dev 就可能让它消失,因为它的父包本身在 require-dev 中。
- 常见翻车点:
monolog/monolog被phpunit/phpunit带进来,但你测试完忘了加到自己的require;上线后日志全挂 - 安全边界:所有被
new、use、class_exists()直接调用的类,其所在包都应出现在你的require - 检查动作:本地执行
composer install --no-dev && php -r "include 'vendor/autoload.php'; new Monolog\Logger('test');",能过才算稳
如何把幽灵依赖“扶正”为顶级依赖
不能靠猜,也不能靠 composer require 盲装——得确认版本兼容性、避免冲突,再写死约束。
- 先查当前装的是哪个版本:
composer show symfony/console,记下输出里的versions(比如* v6.4.10) - 再看它被谁带进来的:
composer show --tree symfony/console,确认是否真需要“继承”父包的版本策略 - 手动加进
composer.json:"symfony/console": "^6.4"(别用*或dev-main) - 立刻执行
composer update symfony/console --with-all-dependencies,强制重算并锁进composer.lock - 最后跑一次
composer dump-autoload -o,确保新命名空间映射生效
幽灵依赖最危险的地方不在开发期,而在 CI 流水线里——缓存的 vendor/ 可能掩盖问题,直到某次 clear-cache 后才爆发。只要 composer.json 没写全,你就永远不知道下次 install 会少装什么。











