生产环境必须使用 composer install --no-dev 确保不安装 require-dev 依赖,避免 autoload 冲突、镜像膨胀和 class not found 错误;同时需配合 composer dump-autoload --no-dev 清除测试路径映射。

install-dev 选项控制依赖安装范围
Composer 默认只装 require 里的包,require-dev 里的包要显式加 --dev(或不加任何选项)才装。但上线时你通常不想装测试、代码检查这些开发用的包——这时候就得关掉它。
关键不是“怎么装开发依赖”,而是“怎么确保生产环境不装”。最稳妥的做法是部署时加 --no-dev:
composer install --no-dev
这个参数会跳过 require-dev 下所有包,连带它们的自动加载规则也不会生成。顺带一提:composer update 默认也会装 dev 包,线上执行前必须加 --no-dev,否则可能意外升级 dev-only 的包,引发 autoload 冲突。
-
--no-dev不影响autoload-dev配置的加载逻辑,只是不装包 - 如果项目用了
autoload-dev里的测试类路径,又没装对应包,运行时会报Class not found - Docker 构建中常漏掉这个参数,导致镜像体积变大、启动变慢,还可能暴露 dev 工具入口
require-dev 里的包真能“只在开发用”吗
不能完全指望配置字段本身做隔离。有些包表面在 require-dev,实际被主逻辑间接引用了——比如一个日志工具同时被 phpunit 和你的异常处理器调用,一旦移除就会崩。
判断一个包是否真属于“纯开发依赖”,看三点:
- 它的类/函数是否出现在
src/或app/的任何use或new中 - 它是否只在
tests/、phpstan/phpstan、php-cs-fixer这类命令行工具里被 require - 运行
composer show --dev后,对每个包执行grep -r "vendor/name" src/ tests/,确认无主业务引用
常见误判:把 symfony/var-dumper 放进 require-dev 很安全;但若你在控制器里写了 dump($data),它就成了运行时依赖——哪怕只在 debug 模式下执行。
composer.json 的 autoload-dev 不等于 require-dev
autoload-dev 是自动加载规则,和包安装无关;require-dev 是包声明,和是否启用 autoload 无关。两者常被混为一谈。
典型错误场景:本地开发时一切正常,上线后报 Class 'TestsFooTest' not found,但你明明加了 --no-dev。原因可能是:
-
autoload-dev里注册了"Tests\": "tests/",而某处代码(比如 CI 脚本)硬编码加载了测试类 -
composer dump-autoload没加--no-dev,导致生成的vendor/autoload.php仍包含测试路径映射 - 某些框架(如 Laravel)会在
AppServiceProvider中条件加载测试辅助函数,但没做 class_exists 判断
解决方法很简单:上线前跑一次
composer dump-autoload --no-dev
这样生成的自动加载文件就不会包含 autoload-dev 的映射,避免“没装包却留着加载路径”的陷阱。
CI/CD 中区分环境比本地命令更关键
本地敲 composer install --no-dev 很容易记住,但 CI 脚本里往往写死 composer install,尤其用现成的 GitHub Action 模板时。
检查点就三个:
- GitHub Actions 的
composer/installer-action是否传了args: --no-dev - GitLab CI 的
script里有没有composer install --no-dev --optimize-autoloader - 阿里云效或 Jenkins 的构建命令是否在
composer install后补了&& composer dump-autoload --no-dev
最容易被忽略的是:有些部署流程先 composer install,再删 vendor 重装——但第二次忘了加 --no-dev,结果白忙活。真正可靠的方案,是把 --no-dev 写进 composer.json 的 config 段:
"config": { "platform": { "php": "8.1" }, "preferred-install": "dist", "sort-packages": true, "allow-plugins": { "php-http/discovery": true } }, "scripts": { "post-install-cmd": ["@php -r "file_put_contents('runtime/composer-no-dev', '1');""] }
但这只是辅助,核心还得靠部署命令本身带参数——因为 config 里的设置不控制安装行为,只影响插件和平台版本模拟。
dev 包的边界从来不是配置字段划出来的,而是运行时调用链决定的。多一次 grep,少一次线上 Class not found。










