require-dev 与 require 的根本区别在于是否为运行时必需:require 中的包缺则项目 fatal,require-dev 中的包仅用于开发测试,运行时完全不调用;安装与否取决于是否使用 --no-dev 参数,而非环境变量。

require-dev 不是“开发环境专用依赖”,而是“非生产依赖”——它不随环境变量变化,只由安装命令决定是否出现。
require-dev 和 require 的根本区别在哪
区别不在“谁用”,而在“谁必须存在”:require 里的包,PHP 运行时会 new 或 include,缺了直接 fatal;require-dev 里的包,只在写测试、跑 CI、执行 phpstan 命令时用,运行时完全不碰。
关键事实:
- Composer 不识别
APP_ENV=prod,也不自动跳过require-dev -
composer install默认装两者;composer install --no-dev才真正剔除 -
composer update默认更新全部;加--no-dev才只动require - 误把
monolog/monolog放进require-dev→ 线上Class not found - 误把
phpunit/phpunit放进require→ 生产镜像多出 20MB、CI 扫描报高危漏洞
哪些包该进 require-dev,判断标准就一条
看它是否在 PHP 运行时被直接调用(不是 CLI 命令,不是配置文件里引用,是代码里 new / use / class_exists()):
使用ydata-profiling(前身为pandas-profiling)生成全面的数据质量报告,包含相关性分析、缺失值模式和基数检测。导出交互式HTML仪表板和JSON摘要。
- ✅ 测试框架:
phpunit/phpunit、pestphp/pest - ✅ 静态分析:
phpstan/phpstan、psalm/phar - ✅ 格式化工具:
friendsofphp/php-cs-fixer、php-cs-fixer - ✅ Mock 工具:
mockery/mockery、prophecy/prophecy - ❌ 框架 Bundle:
doctrine/doctrine-bundle(哪怕只在 dev 环境启用,启动时就被容器注册) - ❌ 调试函数依赖:
symfony/var-dumper(若线上开 debug 模式,dump()会 fatal)
require-dev 导致的典型翻车现场
最常被忽略的不是“装没装”,而是“类能不能加载”:
- 写了
"foo/bar": "dev-main"在require-dev,但测试里new Foo\Bar\Client(),而autoload-dev没配对应psr-4→ 本地跑得通,CI 报Class not found - Dockerfile 里漏写
--no-dev,导致phpunit进了生产镜像,Docker layer 多出 15MB,安全扫描标红 - 执行
composer dump-autoload --optimize时没加--classmap-authoritative,dev-only 类无法自动加载(因为默认不处理autoload-dev) - 某包同时被
require-dev和某个require包间接依赖 →--no-dev后,它仍会出现在 vendor 里,但路径可能错乱,引发 autoloader 冲突
怎么安全添加一个 require-dev 包
永远用 composer require --dev,别手敲 JSON:
- 正确:
composer require --dev phpunit/phpunit - 错误:
composer require phpunit/phpunit(进了require,后续要手动删 +composer update phpunit/phpunit) - 验证是否生效:
composer show | grep phpunit,应只显示一行,且无required by your-project - CI 脚本里必须显式写
composer install --no-dev,不能依赖“默认行为” - 如果项目用了
autoload-dev,记得检查路径是否覆盖到你写的测试类,否则--no-dev下照样找不到类
真正难排查的从来不是“该不该放 require-dev”,而是 autoload 配置和安装命令之间的错位——require-dev 控制的是 vendor 目录有没有这个包,autoload 才决定 PHP 能不能找到它的类。

















