composer install --no-dev是生产部署的硬性底线,不加该参数就不算真正生产部署;它强制跳过require-dev中的包(如phpunit、symfony/debug),防止安全风险、性能损耗与运行时冲突。

composer install --no-dev 是生产部署的硬性底线
不加 --no-dev 就算不上生产部署。它不是“可选优化”,而是防止 phpunit、symfony/debug、laravel-telescope 这类包意外进线上环境的第一道闸门。
常见错误现象:composer install 在 CI 中没带参数,结果镜像里多出 20+ 个 dev-only 包,既增大攻击面,又可能因扩展缺失(如 ext-xdebug)导致启动失败。
实操建议:
- 所有生产 Dockerfile 必须显式写
composer install --no-dev --optimize-autoloader - CI 脚本中禁用
COMPOSER_DEV_MODE=1—— 它不如参数直接,且容易被忽略 - 别信“我只在代码里没调用它就没事”:只要包装上了,它的 autoload 规则、事件监听器、甚至
post-autoload-dump脚本都可能触发
autoload-dev 不是环境开关,而是加载范围开关
autoload-dev 控制的是自动加载器是否注册某段命名空间,不是运行时环境判断依据。它生效与否,只取决于你有没有执行 --no-dev。
常见错误现象:把 App\Config\ProdConfig 放进 autoload-dev,结果生产环境 new ProdConfig() 报 Class not found —— 因为 --no-dev 让这段 PSR-4 映射压根没写进 vendor/autoload.php。
实操建议:
-
autoload下放所有运行时必需的类(含配置工厂、核心服务) -
autoload-dev只放真正仅开发用的东西:测试桩类、Mock 工具、调试命令行指令 - 配置类本身必须能被
autoload加载;环境分支逻辑写在 PHP 里,比如if ($_SERVER['APP_ENV'] === 'prod') { new ProdConfig(); }
require-dev 里混进运行时依赖是静默炸弹
有些包看似是“开发工具”,实则被生产代码间接依赖。比如 symfony/var-dumper 被日志上下文格式化器调用,doctrine/orm 的某些调试代理类被实体监听器引用。
常见错误现象:本地跑得好好的,上线后某个 API 突然 500,日志里只有 Class 'Symfony\Component\VarDumper\Cloner\VarCloner' not found —— 因为这个类被生产配置里的一个 dump() 调用链带进来了。
实操建议:
- 执行
composer depends symfony/var-dumper --tree查看谁在依赖它;如果生产代码路径里出现,就得挪到require - CI 流程中加一步静态扫描:用
php -l+grep -r 'use.*VarDumper\|dump('检查生产代码是否隐式依赖 dev 包 - 别图省事把整套工具链都塞进
require-dev,先问一句:“删掉它,线上功能是否完整?”
composer.lock 必须跨环境严格一致
你本地 composer update 后生成的 composer.lock,就是生产部署的唯一真相。任何绕过它的行为(比如线上 composer update --no-dev)都会导致依赖树漂移。
常见错误现象:开发机上 monolog 是 2.10.0,但线上跑了 composer install 后装成了 2.13.0 —— 因为有人误删了 composer.lock 或用了 update。
实操建议:
-
composer.lock必须提交 Git,且禁止手动编辑 - 生产部署脚本第一行应校验:
[[ $(sha256sum composer.lock | cut -d' ' -f1) == "$(cat .lock-hash)" ]] || exit 1(配合预存哈希) - CI 构建产物中应包含
composer.lock的 diff 输出,便于回溯变更点
composer.json 时,想清楚这个包到底会不会在 APP_ENV=prod 的请求生命周期里被加载或执行——哪怕只有一行。


















