composer update --no-dev 仅跳过顶层 require-dev 包,无法阻止传递性 dev 依赖(如 symfony/console 的 require-dev 中的 phpunit)被安装,主因是 autoload 映射、lock 文件残留、平台配置或依赖包自身 autoload-dev 泄露。

composer update --no-dev 为什么有时还是装了测试包
它只跳过 require-dev 顶层声明的包,但无法阻止“传递性 dev 依赖”被拉入——比如你 require 的某个生产包(如 symfony/console)在它自己的 require-dev 里声明了 phpunit/phpunit,而该包又通过 provide 或 replace 被间接满足,Composer 可能仍会解析并安装它。
常见触发场景:
-
composer.json中autoload(非autoload-dev)误映射了tests/目录,导致测试类被主 autoloader 加载,Composer 认为“需要这些类存在”,进而尝试安装对应 dev 包 - 用了
config.platform模拟低版本环境,某些 dev 包因平台约束被“降级保留”,--no-dev不影响其平台兼容性判断逻辑 - CI 环境执行了
composer update前没清掉旧composer.lock,而 lock 文件里packages-dev字段非空,update --no-dev仅跳过新解析,不清理已有记录
如何确认 --no-dev 真生效了
别只看终端输出,得验证最终状态:
- 运行
composer show --dev:输出为空才说明 dev 包未被识别为已安装 - 检查
vendor/autoload.php是否仍加载了autoload-dev规则:用grep -n "autoload-dev" vendor/autoload.php,有结果就说明dump-autoload阶段没配合--no-dev - 查
composer.lock的packages-dev数组:若非空,说明上次update没加--no-dev,或用了update --lock这类不重算依赖的命令 - 部署后执行
composer show --installed | grep -E "(phpunit|mockery|phpstan)",任何命中都代表漏控
CI/CD 中最稳妥的更新流程
开发中改了 composer.json(比如升级 Laravel 主版本),又不想带入任何 dev 依赖,推荐这个组合:
围绕关键发现、作用机制、临床相关性及研究局限性展开讨论。适用于撰写或优化任何生物医学论文的“讨论(Discussion)”部分——包括结果解读、与既往文献关联、阐释意外发现、界定研究局限性,以及撰写结论。当用户输入以下任一指令时也会自动触发该功能: - “write my discussion” - “help me discuss my findings” - “how do I compare to prior studies” - “write the limitations par
- 先跑
composer update --no-dev --dry-run:预检是否真会跳过所有 dev 包,同时观察是否有冲突或警告 - 确认无误后执行
composer update --no-dev --lock:只重写composer.lock,不碰vendor/,快且安全 - 再执行
composer install --no-dev --no-scripts --no-autoloader:按锁文件还原,跳过脚本和 autoload 生成(留给后续显式控制) - 最后补上
composer dump-autoload --optimize --classmap-authoritative --no-dev:确保 autoload 干净、高效、不含测试路径
这个链条里最容易被跳过的是 --no-dev 在 dump-autoload 阶段的缺失——只要 autoload 文件里混进了 tests/ 的 PSR-4 映射,生产环境就可能 runtime 加载失败或类冲突。
本地开发与部署 autoload 必须区分配置
很多人把 "optimize-autoloader": true 写死在 composer.json 里,结果本地改个类要等 5 秒 dump-autoload,还常遇到“类删了但还能 new 出来”的问题。
正确做法是:
- 本地开发:设
"optimize-autoloader": false,且不加--classmap-authoritative,让 PSR-4 动态发现生效 - CI 构建或部署镜像:用命令行参数覆盖,例如
composer install --no-dev --optimize-autoloader --classmap-authoritative - 绝对不要在
autoload块里包含tests/、examples/或docs/—— 这些只能放autoload-dev,否则--no-dev彻底失效
最隐蔽的坑是:你没在 autoload 里写 tests/,但某个依赖包的 composer.json 里写了,而你又用了 "preferred-install": "source",它的 tests/ 就会进 vendor/ 并被你的主 autoloader 扫到。这时候得靠 archive.exclude 或 .gitattributes 从源头掐断。










