require和require-dev不是环境开关,而是声明是否参与生产构建:require包必须进vendor,require-dev包默认不进,除非漏加--no-dev;其自动加载取决于autoload配置而非字段本身。

require 和 require-dev 不是“开发环境 vs 生产环境”的开关,而是明确声明「这个包是否参与生产构建」——require 里的包必须出现在生产 vendor/ 目录中,require-dev 里的包默认不进生产环境,除非你漏了 --no-dev。
require-dev 的包到底会不会被自动加载?
会,但只在安装了它的前提下。Composer 的 autoloader 生成逻辑不区分 require 和 require-dev,它只看 autoload 和 autoload-dev 配置是否覆盖对应路径。如果某个 dev 包(比如 phpunit/phpunit)的类路径没被 autoload-dev 声明,又没被主项目的 autoload 覆盖,那即使本地装了,new PHPUnit\Framework\TestCase() 也会报 Class not found。
常见错误现象:
- 本地跑测试正常,CI 报
Class 'PHPUnit\Framework\TestCase' not found—— 很可能是 CI 没运行composer install,或用了--no-dev但又试图执行测试 - 线上开了 debug 模式,
dump()报错 —— 因为symfony/var-dumper被误放进了require-dev,而它实际被 runtime 函数调用
部署时漏掉 --no-dev 会出什么问题?
不是“多装几个包而已”,而是直接破坏生产环境稳定性:
-
vendor/体积膨胀 30%~200%,镜像拉取和启动变慢 - dev 包可能带监听端口、文件写入、调试钩子等行为,上线后意外触发(如
symfony/debug-bundle在 prod 环境暴露路由) - 某些包存在运行时冲突:比如
mockery/mockery和真实 HTTP 客户端共存时,class_alias或函数重定义引发 fatal error - Docker 构建缓存失效更频繁 —— 因为
composer.lock里 dev 包版本变动也会触发全量重装
怎么确认一个包该放 require 还是 require-dev?
问自己三个问题,答案全为「否」才能放进 require-dev:
- 这个包的类或函数是否在 PHP 请求生命周期内被
new、use、call_user_func或function_exists检查? - 这个包是否被框架容器在启动时注册(如 Symfony Bundle、Laravel Service Provider)?
- 这个包是否提供运行时必需的功能(比如日志、缓存、数据库连接、API 客户端)?
反例:doctrine/doctrine-bundle 必须放 require,哪怕只在 dev 环境启用;laravel/sail 可以放 require-dev,因为它只提供 sail CLI 命令,不参与请求处理。
误把包装进 require 怎么快速修正?
别改 composer.json 后手动删 vendor/,容易残留 autoload 映射:
- 先执行
composer remove vendor/package-name(它会自动从require移除) - 再执行
composer require --dev vendor/package-name - 验证:运行
composer show | grep package-name,输出应只有一行,且不含required by字样 - 额外检查:
grep -r "package-name" vendor/composer/autoload_*.php,确认没出现在生产 autoload 文件里
真正难排查的从来不是“装错位置”,而是 autoload-dev 路径写错、或 dev 包本身没声明自己的 autoload 规则 —— 这时候即使装对了,也照样 Class not found。











