require-dev 仅在本地开发或CI/CD中执行 composer install/update 且未加 --no-dev 时安装,生产环境应加 --no-dev;其包不可用于运行时逻辑,否则会导致体积膨胀、自动加载污染和版本兼容问题。

require-dev 什么时候会被安装
它只在你本地执行 composer install 或 composer update 时生效,前提是没加 --no-dev 参数。CI/CD 构建、生产环境部署脚本里如果写了 --no-dev,那 require-dev 下的所有包压根不会下载、不会写进 vendor/、也不会出现在 autoload.php 中。
常见错误现象:Class 'PhpUnit\Framework\TestCase' not found —— 很可能是因为你在生产环境跑了 composer install --no-dev,但测试代码却没做条件判断就直接 new 了测试类。
- 本地开发:默认安装
require-dev,方便跑测试、生成文档、检查代码风格 - CI 流水线:通常也装(比如用
phpunit执行测试),所以不加--no-dev - 线上部署:必须加
--no-dev,否则会多出几十 MB 无用依赖,还可能引入安全风险
require-dev 和 require 写混了会怎样
表面上看都能装上、能跑通,但实际埋了三类坑:
-
体积膨胀:把
phpstan/phpstan放到require,所有用户升级你的包时都会被迫下载这个静态分析工具,哪怕他们从不跑检查 -
自动加载污染:
require-dev的包也会被 Composer 自动生成 autoload 规则,但如果它们的命名空间和主逻辑冲突(比如都用了Tests\),可能导致本地new Tests\FooTest()在生产环境意外可用,掩盖了路径引用错误 -
版本锁定错乱:你在
require-dev里写了"phpunit/phpunit": "^10.0",但主项目用的是 PHP 8.0 —— 这没问题;可一旦挪到require,就变成所有下游项目都得适配 PHP 8.0+,限制了兼容性
如何确认某个包该放 require 还是 require-dev
核心判断标准只有一个:这个包的代码是否在运行时被业务逻辑直接调用?
- 是 → 放
require:guzzlehttp/guzzle(发 HTTP 请求)、monolog/monolog(记录日志) - 否 → 放
require-dev:phpunit/phpunit(仅测试时 new TestCase)、infection/infection(只在 CI 里跑变异测试)、laravel/pint(本地格式化代码用) - 模糊地带要小心:
symfony/var-dumper常被开发者写在调试代码里(如dd($data)),但它不是运行必需的 —— 应放require-dev;若你封装了一个调试组件并公开给其他项目使用,那它就得进require
require-dev 里的包能被 require 中的包依赖吗
可以,但不推荐。Composer 允许 A 包在 require 中声明依赖 B,而 B 恰好在你的 require-dev 里定义过版本 —— 此时 Composer 会优先复用那个版本,避免重复安装。
但这会让依赖关系变隐晦。比如你把 doctrine/annotations 放在 require-dev,而 symfony/cache(在 require)内部要用它,结果上线后因 --no-dev 导致 symfony/cache 启动失败,报错:Class 'Doctrine\Common\Annotations\AnnotationReader' not found。
- 真正需要的运行时依赖,必须明确定义在
require中 -
require-dev不是“备用仓库”,不能指望它兜底解决主依赖的子依赖问题 - 查依赖树用:
composer depends doctrine/annotations,看谁真正需要它
roave/security-advisories)只能放在 require-dev,因为它本身不提供任何类,只是个冲突规则集合;放到 require 不但没用,还会让 Composer 解析依赖时更慢。










