require 声明运行时必需依赖,require-dev 声明仅开发测试阶段需要的工具型依赖;composer 不自动按环境切换,部署必须显式用 --no-dev,否则易致线上缺失类或混入测试包。

require-dev 和 require 的本质区别
它们不是“开发环境”和“生产环境”的开关,而是「依赖作用域」的声明:`require` 是运行时必需的包,`require-dev` 是仅在本地开发、测试、构建阶段需要的工具型依赖。Composer 不会根据 `APP_ENV` 或服务器类型自动切换加载——它只看安装命令是否带 `--no-dev`。
常见错误现象:phpunit 写在 `require-dev` 里,CI 流水线却没加 `--no-dev`,结果生产镜像意外带上测试框架;反过来,把 `monolog/monolog` 放进 `require-dev`,线上直接报 `Class not found`。
- 部署时必须显式执行
composer install --no-dev(Dockerfile、CI 脚本里漏掉这句是高频翻车点) -
require-dev中的包仍会被 `autoload-dev` 加载,但不会出现在生产 autoloader 的 classmap 里(除非你手动跑composer dump-autoload --optimize且没加--no-dev) - 某些包(如 `
laravel/pint`)只提供 CLI 命令,放 `require` 毫无意义,就该进 `require-dev`
多环境配置不能靠 composer.json 硬切
想用 `require-dev` 区分 staging / prod?行不通。Composer 没有环境感知能力,也没有 `require-staging` 这种字段。真要按环境装不同依赖,得靠外部机制驱动。
使用场景:某 SDK 在开发时需 mock 工具(如 `php-mock/php-mock`),上线后换真实网关客户端(如 `guzzlehttp/guzzle`),但两者冲突或体积过大。
- 方案一:用 Composer 的 `
platform` + 环境变量控制安装逻辑(不推荐,复杂且易错) - 方案二:拆成两个独立 `composer.json`(如 `composer.prod.json`),用 `
COMPOSER=composer.prod.json composer install --no-dev` 指定(适合强隔离场景,但维护成本高) - 方案三:更务实的做法——把环境相关逻辑下沉到代码层,用接口+实现分离,让 `
require` 只管稳定基础依赖,`require-dev` 只管纯开发工具
CI/CD 中 require-dev 的典型陷阱
错误现象:GitHub Actions 里 `composer install` 后跑测试,提示 `Class 'PHPUnit\Framework\TestCase' not found`,但本地好好的。
根本原因:CI 默认缓存 `vendor/`,而缓存键没包含 `composer.lock` 变更或 `--no-dev` 标志变化,导致上一次带 `--no-dev` 的缓存被复用,`phpunit` 根本没装进去。
- 确保 CI 步骤中 `composer install` 显式带上 `--no-dev`(生产部署)或明确不带(测试阶段)
- 缓存 key 必须包含 `
composer.lock` 的 hash,例如 GitHub Actions 用 `${{ hashFiles('**/composer.lock') }}` - 不要在 `.gitignore` 里忽略 `
vendor/` 后又试图在 CI 里 `git checkout vendor`——Composer 不认这种“伪 vendor”
require-dev 里的包也能影响运行时行为
很多人以为 `require-dev` 的包“完全不参与运行”,其实不然。只要代码里 `require` 或 `use` 了它的类,且 autoloader 加载了它(比如用了 `autoload-dev`),它就会被执行。
典型问题:`symfony/var-dumper` 放在 `require-dev`,但你在生产代码里写了 `dump($data)`,结果线上报错或暴露敏感信息。
- 检查所有 `
require-dev` 包是否被实际代码引用(用 `grep -r "use.*VarDumper\|dump(" .` 快速扫描) - 禁用开发辅助函数的最简单方式:在生产环境 `php.ini` 中设 `
disable_functions = dump,dd,ray`(如果用了这些) - 某些包(如 `
barryvdh/laravel-debugbar`)自带环境检测,但别依赖它——配置写错或环境变量拼错,debugbar 就悄悄跑在线上
真正麻烦的从来不是怎么写 `composer.json`,而是谁在什么时候、以什么方式加载了哪段代码。环境差异最终都会落到 `autoload` 和 `include` 路径上,盯着 `vendor/autoload.php` 和 `composer dump-autoload -v` 的输出比背规则有用得多。










