生产环境必须用构建阶段预生成vendor目录并原子化切换,禁用生产机composer install。因边装边切会导致autoload.php重写冲突、classmap生成中断、opcache失效等真实500错误和slowlog卡顿。

生产环境不能“边装边切”,必须用构建阶段预生成完整 vendor/ 目录 + 原子化切换,否则必然出现 502、类找不到、白屏等不可控状态。
为什么直接在生产机上运行 composer install 不可能平滑
它不是“安装完再上线”,而是边写文件边被 PHP-FPM 读取——vendor/autoload.php 文件正在重写时被请求加载,autoload_classmap.php 正在生成一半就被 include,vendor/composer/installed.json 写到一半就触发 opcache 缓存失效。这些都不是理论风险,是 Nginx access log 里真实出现的 500 和 slowlog 里卡在 flock() 的实锤。
常见现象包括:
-
Class 'Monolog\Logger' not found(autoload_real.php 读到半截 classmap) - PHP-FPM worker 持续卡在
stat()系统调用(因 autoload 未优化,反复扫描路径) - 部署中途
post-install-cmd清空缓存或重写.env,导致正在处理的请求读到空配置
composer install 必须在构建环境执行,且参数一个都不能少
这条命令不是“建议加参数”,是构建产出合法 vendor/ 的唯一入口:
-
--no-dev:跳过require-dev及其所有传递依赖(如phpunit/phpunit→sebastian/exporter),不清理它们,classmap就会混入测试类,体积膨胀且 fallback 路径仍存在 -
--optimize-autoloader(或-o):生成vendor/composer/autoload_classmap.php,把 PSR-4 映射固化为数组,避免运行时file_exists()扫描 -
--classmap-authoritative:让 autoloader 完全信任 classmap,彻底禁用foreach ($prefixesPsr4 as $prefix => $paths)分支——这是真正关闭 I/O 的开关 -
--no-interaction(或-n):防止插件弹出认证提示卡死(如私有源 token 请求) -
--prefer-dist:强制走 zip 包,避免因缺失git或网络波动导致 clone 失败 -
--no-scripts:关掉所有post-install-cmd,线上不需要生成密钥、清缓存、跑迁移
示例命令:
composer install --no-dev --optimize-autoloader --classmap-authoritative --no-interaction --prefer-dist --no-scripts
注意:composer dump-autoload -o 在 Composer 2+ 中已失效,它不重建 classmap,只改 autoload_real.php 的标记位,极易导致 Class not found。
原子化切换 vendor/ 是平滑的核心动作
不能 rsync -av vendor/ ./ 覆盖,也不能 rm -rf vendor && cp -r build/vendor ./ —— 这些操作都有时间窗口,PHP-FPM 会在中间某刻读到残缺目录。
正确做法是用符号链接(symlink)实现零停机切换:
- 构建完成后,将完整
vendor/打包为带时间戳的目录,如vendor-202611031930 - 用
ln -snf vendor-202611031930 vendor原子替换软链(ln -sf是原子操作) - 旧
vendor/目录由内核延迟回收,正在运行的 PHP-FPM worker 仍可安全访问其文件
验证是否生效:检查 vendor/autoload.php 是否包含 'classmap-authoritative' => true,并确认 vendor/composer/autoload_real.php 中的 findFile() 方法只剩 if ($classMap = $this->classMap) 分支。
部署后必须重置 opcache 并校验权限
即使 vendor/ 切换完成,PHP-FPM 仍可能缓存旧的 autoload_real.php 或 opcache.file_cache,导致新 classmap 不生效。
- 执行
opcache_reset()(需在 PHP-FPM pool 配置中允许该函数,或通过 CLI 调用) - 或更稳妥地重启 PHP-FPM:
sudo systemctl reload php8.1-fpm(注意用reload而非restart,保持连接不中断) - 检查
www-data(或对应 pool user)对新vendor/目录是否有r-x权限(chmod -R a+rX vendor-202611031930) - 禁止在生产机保留
composer二进制,防止误执行;若必须保留,应chmod 000 /usr/local/bin/composer
最易被忽略的是:composer.lock 文件必须和代码分支严格一致,CI 构建前要 git checkout $TAG,否则 install 会退化为 update,引入未测试的版本漂移。











