生产环境必须加--no-dev,否则会强制安装PHPUnit、PHPStan等dev包,导致体积膨胀、启动变慢、暴露调试入口甚至fatal error;其生效前提为composer.lock不含dev包记录。

生产环境不加 --no-dev,就等于把 PHPUnit、PHPStan、Xdebug 全塞进线上镜像——不是“可能出问题”,是必然体积膨胀、启动变慢、暴露调试入口、甚至因扩展缺失直接 fatal error。
composer install 默认到底装哪些包?
它只看 composer.lock 文件里记录了什么,然后照单全收。而这个 lock 文件是怎么生成的?取决于你上次运行的是 composer update 还是 composer update --no-dev。
常见错误现象:composer install --no-dev 后 phpunit 仍在 vendor/ 里;或者线上报 Class 'Mockery\MockInterface' not found,但你确定 mockery/mockery 在 require-dev 里。
- 根本原因:lock 文件本身含
require-dev包的记录 →install就会装,--no-dev此时无效 - 正确做法:CI 构建前先跑一次
composer update --no-dev --lock,提交新 lock;后续部署才用composer install --no-dev -
composer install不改 lock,composer update才改 —— 所以部署阶段永远别用update
require-dev 里的包,真能“只在开发用”吗?
不能一概而论。关键看它是否被运行时代码直接引用。
例如:symfony/var-dumper 放进 require-dev 很常见,但如果控制器里写了 dump($data),且该控制器在 debug 模式下被线上请求调用,那它就是运行时依赖 —— 缺了就 500。
- 检查方式:对每个
require-dev包,执行grep -r "vendor/name" src/ tests/,确认src/下无直接use或new -
autoload-dev只影响自动加载路径(如tests/目录),和包是否安装无关;没装require-dev包,这些路径再配置也没用 - 运行
composer dump-autoload --no-dev才会剔除autoload-dev映射;漏掉这步,vendor/autoload.php仍含测试路径,可能引发 autoload 冲突
Docker 和 CI 里怎么写才不出错?
参数必须显式、不可省略、不能靠环境变量“默认生效”。COMPOSER_DEV_MODE=0 有作用,但 --no-dev 优先级更高,且更直白可控。
- Dockerfile 中应写死:
RUN COMPOSER_DEV_MODE=0 composer install --no-dev --optimize-autoloader - GitHub Actions 缓存 key 必须带语义:
composer-${{ hashFiles('**/composer.lock') }}--no-dev,否则缓存复用导致 dev 包漏装或误装 - CI 测试阶段用完整安装:
composer install && ./vendor/bin/phpunit;生产构建阶段必须切回--no-dev,两者命令不能混用 - 禁止在 CI 脚本里出现
composer update(除非是专用“更新依赖”流水线)
最常被忽略的一点:不是所有“开发用”工具都该塞进 require-dev。如果某个包(比如 roave/security-advisories)只是用于本地扫描,从不参与运行或构建,那它甚至不该进 composer.json —— 用临时容器或本地脚本跑更干净。


















