composer install 不是原子操作:它直接修改 vendor/ 目录,边下载边解压边写入,无临时目录、无完整性校验、无原子重命名,失败后易致半损坏状态,且 autoload.php 等文件被反复覆盖,并发执行还会引发内容错乱。

composer install 为什么不是原子操作?
默认情况下 composer install 不是原子操作:它会直接修改 vendor/ 目录,边下载边解压边写入。一旦中途失败(如网络中断、磁盘满、权限错误),vendor/ 很可能处于半损坏状态,后续 composer install 或 autoload 都可能报错。
根本原因在于 Composer 没有内置“先准备再切换”的机制——它不创建临时目录、不校验完整性、也不做原子重命名。
- 没有事务回滚能力,失败后需手动
rm -rf vendor/ && composer install -
autoload.php和vendor/composer/autoload_*.php文件在安装过程中被反复覆盖,期间若被 PHP 进程加载,可能触发 fatal error - 并发执行多个
composer install(如部署脚本并行跑)会导致vendor/内容错乱
如何模拟原子部署?用 --no-scripts + 临时目录 + mv
最常用且可靠的做法是把安装过程隔离到临时目录,验证通过后再整体替换。关键点不是“能不能”,而是“怎么控制时机和边界”。
典型流程如下(Linux/macOS):
mkdir -p /tmp/composer-tmp-$$
cd /tmp/composer-tmp-$$
composer create-project --no-install $PROJECT_REPO . --stability=stable --no-scripts
cp /path/to/composer.lock .
composer install --no-dev --optimize-autoloader --no-scripts
# 验证:确保 autoload 可加载、核心类存在
php -r "require 'vendor/autoload.php'; echo class_exists('Monolog\Logger') ? 'OK' : 'FAIL';"
# 验证通过后原子切换
mv /path/to/vendor /path/to/vendor.old && mv /tmp/composer-tmp-$$/vendor /path/to/vendor
rm -rf /path/to/vendor.old /tmp/composer-tmp-$$
-
--no-scripts必须加,否则post-install-cmd等钩子可能访问旧vendor/或写入非临时路径 - 临时目录不能在
/tmp下无限制创建,建议用$$(当前 shell PID)防冲突 -
mv是 POSIX 原子操作,只要源和目标在同文件系统,切换就是瞬时且不可见中间态的
composer update 能否原子化?基本不能,慎用于生产
composer update 天然不适合原子化:它要读写 composer.lock、生成新哈希、触发大量钩子,并且依赖解析结果不可预测。即使你在临时目录运行,也无法保证 lock 更新后线上行为一致。
- 生产环境应禁止直接运行
composer update;正确流程是本地update → commit lock → deploy → install - CI 中若必须跑
update(如依赖审计),请确保工作目录完全隔离,且不复用任何已有vendor/或缓存 -
composer update --dry-run只是文本模拟,不校验包可用性或 autoloader 兼容性,不能替代真实测试
cache-dir 和 COMPOSER_HOME 对原子性的影响
Composer 缓存本身不影响 vendor/ 原子性,但配置不当会干扰隔离效果:
- 若多个项目共用同一
cache-dir(默认~/.composer/cache),一个项目的install可能污染另一个项目的缓存包,导致 hash 不匹配或解压失败 - 部署时若未重置
COMPOSER_HOME,全局插件或认证凭据可能意外介入,尤其在容器或 CI 环境中 - 建议部署脚本显式设置:
COMPOSER_HOME=/tmp/composer-home-$$ composer install ...
真正影响原子性的,永远是 vendor/ 目录的写入方式和生命周期——缓存只是加速手段,不是安全边界。











