require-dev不是环境开关,而是安装范围声明;Composer不感知APP_ENV,仅凭--no-dev参数决定是否安装require-dev中的包,漏加会导致线上混入phpunit等工具并引发错误。

require-dev 不是环境开关,而是安装范围声明
很多人误以为把包放进 require-dev 就等于“只在开发环境生效”,其实它只是告诉 Composer:这个包不参与运行时逻辑,只供本地开发、测试或构建使用。Composer 本身没有环境感知能力——APP_ENV=prod 或服务器 hostname 完全不影响它是否安装 require-dev 中的包。
真正起作用的是安装命令是否带 --no-dev。不加就全装,加了就跳过 require-dev 所有条目。漏掉这参数,线上就会多出 phpunit、larastan 甚至 symfony/debug,不仅体积翻倍,还可能因类名冲突或自动加载泄露引发 fatal error。
-
composer install→ 装require+require-dev -
composer install --no-dev→ 只装require,require-dev条目被忽略 -
COMPOSER_DEV_MODE=0 composer install→ 效果等同--no-dev,但优先级更低,不推荐替代参数
生产部署必须显式加 --no-dev,且不能依赖 CI 缓存
CI/CD 流水线里最常见翻车点:缓存 vendor/ 但没把 --no-dev 纳入缓存键。结果上次跑测试时缓存了带 phpunit 的 vendor/,这次部署生产却复用了它,Class 'PHPUnit\Framework\TestCase' not found 直接炸在上线后。
解决办法不是禁用缓存,而是让缓存策略明确区分场景:
- 测试阶段:用
composer install(不加--no-dev),缓存键包含composer.lock+ “dev” 标识 - 生产构建:用
composer install --no-dev --optimize-autoloader --no-interaction,缓存键含 “prod” 标识 - Dockerfile 中禁止出现裸
composer install,必须写全参数
–no-dev 只控制安装,不清理 vendor 里的危险残留
--no-dev 确实跳过了 require-dev 包的下载和 autoload 注册,但它不会删掉 vendor/ 里已存在的冗余内容。比如:
围绕关键发现、作用机制、临床相关性及研究局限性展开讨论。适用于撰写或优化任何生物医学论文的“讨论(Discussion)”部分——包括结果解读、与既往文献关联、阐释意外发现、界定研究局限性,以及撰写结论。当用户输入以下任一指令时也会自动触发该功能: - “write my discussion” - “help me discuss my findings” - “how do I compare to prior studies” - “write the limitations par
-
vendor/bin/phpunit这类二进制文件仍存在,若 web 目录权限配置不当,可能被直接访问执行 -
vendor/guzzlehttp/guzzle/tests/这类测试目录没被删,可能暴露内部结构或注释中的敏感信息 -
vendor/composer/installed.json记录了所有已装包的完整版本与哈希,攻击者可据此快速定位已知漏洞组件
推荐在 composer install --no-dev --optimize-autoloader 后追加清理步骤:
find vendor -path '*/tests' -o -path '*/.git' -o -name 'phpunit.xml' -o -name 'psalm.xml' -delete rm -rf vendor/bin/ vendor/**/docs/ vendor/**/examples/ vendor/composer/installed.json
autoload-dev 不受 --no-dev 影响,但类文件不存在就加载失败
autoload-dev 是 composer.json 里独立的自动加载配置段,它的存在与否、是否生效,和 --no-dev 无关。只要你在 composer dump-autoload 时没加 --no-dev,它就会被写进 vendor/composer/autoload_*.php 里。
但关键点在于:即使 autoloader 规则写了 tests/ 目录,只要 --no-dev 没装对应包,那个目录根本不存在,class_exists() 或 new 实例化都会直接报错——这不是 autoloader 的问题,是文件缺失。
真正要警惕的是项目代码里硬编码引用了 dev 包的类,比如:
// 错误示例:把 PHPUnit 断言混进控制器逻辑里 use PHPUnit\Framework\Assert; Assert::assertTrue($user->isActive()); // --no-dev 下直接 fatal error
这种写法会让 --no-dev 失效,因为错误发生在运行时而非安装时。
--no-dev,而是后续有没有清理 vendor/ 里那些“没被装、但曾经存在过”的痕迹——特别是 installed.json 和 bin/ 目录,它们不参与运行,却最可能成为攻击入口。

















