composer用require-dev声明非生产依赖,是否安装仅取决于是否执行composer install --no-dev;--no-dev跳过安装及autoload-dev注册,app_env等变量对其无影响。

Composer 本身不提供“开发环境配置”功能,require-dev 不是环境开关,而是依赖作用域声明;能否在某台机器上装上 dev 包,只取决于你执行的是 composer install 还是 composer install --no-dev。
require-dev 不是“开发环境专用”,而是“非生产安装时可选”
很多人误以为把 phpunit/phpunit 放进 require-dev 就等于“它只在本地生效”。其实不是:只要你在服务器上运行 composer install(没加 --no-dev),它照样会被装进去,且 vendor/autoload.php 会尝试加载它的类——哪怕 APP_ENV=production。
关键事实:
-
require-dev中的包,其autoload-dev规则不会进入生产环境的 autoloader classmap(前提是用了--no-dev) - 但如果你漏掉
--no-dev,这些包不仅被安装,还会注册到自动加载器,可能触发class_exists('PHPUnit\Framework\TestCase')报错 -
COMPOSER_DEV_MODE=0可让 Composer 忽略require-dev区块,但它和--no-dev是并行机制,--no-dev优先级更高 -
APP_ENV、SYMFONY_ENV等环境变量对 Composer 安装行为完全无影响
怎么安全地加一个 require-dev 包?命令细节不能错
最常见翻车点:少打一个 --dev,就把测试工具塞进了生产依赖树。
正确操作:
- 用
composer require --dev phpunit/phpunit:^10—— 包写入require-dev,且只在未加--no-dev时安装 - 别用
composer require phpunit/phpunit(没--dev)—— 它会进require,线上必报错 -
--dev不能缩写成-d(旧版 Composer 会静默忽略,结果还是进require) - 如果已误加,手动删
composer.json里require下对应行,再跑composer update phpunit/phpunit清除残留
验证是否生效:composer show | grep phpunit 应只输出一行,且不显示 “required by your project”(说明没被 require 引用)。
require-dev 包在线上意外执行的三个典型场景
即使你严格用了 --no-dev,某些代码写法仍会让 dev 包“活过来”:
- 在
config/app.php或bootstrap/app.php里写了class_exists('PhpCsFixer\Fixer\...')—— 这会触发 autoload,而该类不在生产 autoloader 中,直接报错 - 把
symfony/var-dumper放进require-dev,却在中间件或服务提供者里无条件调用dump()—— 生产环境找不到函数 - 用
autoload.files引入了tests/helpers.php这类文件,而它又 require 了 dev 包里的类
规避方式:
- 改用
function_exists('dump')或interface_exists('SomeDevInterface')做判断(它们不触发 autoload) - 把所有 dev 工具相关逻辑包裹在
if (app()->environment('local')) { ... }或if (getenv('APP_ENV') === 'local') { ... } - 确保
autoload-dev的 PSR-4 路径只映射tests/、stubs/、fixtures/这类纯开发目录
想真正在不同环境装不同包?别硬刚 composer.json
如果你需要「dev 环境装 xdebug,prod 环境装 blackfire」,或者「staging 用 mock SDK,prod 用真实网关客户端」,require-dev 解决不了——它只能控制“装不装”,不能控制“装哪个”。
可行路径只有两条:
- 把环境差异下沉到 PHP 层:定义统一接口(如
PaymentGatewayInterface),require里只放稳定基础依赖,运行时根据APP_ENVnew 不同实现 - 用多份
composer.json+pathrepo:建myapp/prod-deps和myapp/dev-deps两个子项目,主项目通过repositories和环境变量切换依赖源(CI 脚本里用COMPOSER=composer.prod.json composer install --no-dev)
后者维护成本高,但能彻底隔离;前者更轻量,也更符合 Composer 的设计哲学——它管装包,不管分支逻辑。











