require-dev 是非生产安装依赖,仅在执行 composer install(无 --no-dev)时安装,不随环境自动切换;它控制 vendor/ 安装行为,而非运行时隔离,生产环境必须显式使用 --no-dev 避免混入测试工具。

require-dev 不是“开发环境专用依赖”,而是“非生产安装时可选的依赖”——它不随环境自动切换,只响应 composer install 是否带 --no-dev 参数。
require-dev 的真实作用:控制安装行为,不是运行时隔离
很多人误以为加了 require-dev 就等于“只在 dev 环境生效”,其实它只影响 composer install 和 composer update 时是否把包拉进 vendor/。只要没加 --no-dev,哪怕在生产服务器上执行,phpunit/phpunit 也会被装进去。
-
require中的包:PHP 运行时会new、use、调用,缺了直接Fatal error -
require-dev中的包:只在你手动执行命令时起作用,比如./vendor/bin/phpunit或phpstan analyse - autoload-dev 定义的路径(如
"Tests\": "tests/")不会进入生产 autoloader,但如果你在代码里硬写new TestsFooTest(),哪怕没装包,运行时照样报错
为什么生产环境还会装上 require-dev 的包?
因为部署脚本没加 --no-dev,或者用了默认行为不明确的命令(比如只写 composer install 却没确认 CI 配置)。2026 年多数新项目默认仍会装 require-dev,除非显式禁用。
- Dockerfile 里写
RUN composer install→ 危险,会装 phpunit、larastan 等 - 正确写法:
RUN composer install --no-dev --optimize-autoloader --no-interaction - CI 脚本中漏掉
--no-dev,会导致测试通过但线上启动失败(比如因 xdebug 扩展缺失或冲突) -
composer.lock文件里如果出现phpunit/phpunit的条目,说明上次install或update没加--no-dev
require-dev 包意外“活”在生产环境的三种常见方式
即使你严格用了 --no-dev,以下情况仍会让 dev 包参与运行,造成隐性风险:
- 项目代码里直接
require或include了 dev 包的 PHP 文件(例如在某个调试路由里require 'vendor/phpunit/phpunit/src/Util/Printer.php') - 配置文件(如
phpunit.xml、phpstan.neon)被误引入到生产 bootstrap 流程中 -
autoload-dev里注册的路径碰巧和autoload重叠,或用了files类型 autoload,导致某些工具函数被提前加载
真正需要按环境切换依赖?别动 require-dev,换思路
Composer 原生不支持 environment: prod 或 require-prod 字段。强行拆 composer.json 或靠脚本替换配置,会导致 composer.lock 哈希失效、本地与线上不一致。
- 若只是让 Composer “假装有某扩展”(如 xdebug),用
config.platform:"config": { "platform": { "ext-xdebug": "3.1.5" } } - 若真要互斥依赖(A 包只在 prod,B 包只在 dev),唯一可靠方案是拆成两个独立包 +
pathrepo,主项目通过repositories切换引用目标 - 所有环境共用同一份
composer.json和composer.lock,靠部署命令区分,是最小认知负担、最高一致性的方式
最容易被忽略的一点:autoload-dev 不是“开发专用自动加载”,而是“开发专用自动加载规则”;它不会被生产 autoloader 加载,但也不会阻止你手写 new —— 类名存在与否,取决于 vendor 目录有没有那个文件,而文件有没有,只取决于你有没有运行 composer install --no-dev。


















