composer install和update默认非原子操作,因采用就地覆盖写入vendor,中途失败会导致半旧半新状态;真正的原子切换需构建新目录后原子替换软链,并重置opcache。

Composer install 和 update 为什么不是原子操作
默认情况下,composer install 和 composer update 都是「就地覆盖」式写入:vendor 目录被逐步删改、文件逐个替换、autoload.php 动态重生成。一旦中途失败(如磁盘满、网络中断、权限错误),vendor 就会处于半旧半新、无法运行的状态——这不是原子的。
用 --no-scripts + 符号链接模拟原子切换
真正可行的原子切换,靠的是「构建新目录 → 原子替换软链」两步,绕过 vendor 直接修改。关键点在于不把 vendor 当作工作目录,而是把它当作一个可替换的符号链接:
- 每次部署前,在独立路径下运行
composer install --no-scripts --no-autoloader(跳过可能失败的脚本和 autoload 生成) - 随后手动运行
composer dump-autoload --optimize到该路径,确保 autoloader 可用 - 用
ln -snf /path/to/new/vendor ./vendor一次性切换链接(Linux/macOS)或mklink /J vendor C:\path\to\new\vendor(Windows) - 确保 Web 服务器用户对新 vendor 有读取权限,且旧 vendor 不被进程锁定(PHP-FPM 的 opcache 可能缓存旧路径,需
opcache_reset()或重启)
vendor-dir 配置不能替代原子性
在 composer.json 中设置 "config": { "vendor-dir": "vendor-staging" },只是改了默认路径,不解决「写入过程是否中断安全」的问题。它甚至会让问题更隐蔽——因为 composer install 依然会直接覆写 vendor-staging 目录本身,而非创建新副本。
真正的隔离必须由外部流程控制:每次构建都用唯一时间戳或哈希后缀命名目录(如 vendor-20240521-abc123),再通过软链指向它。否则任何 composer 命令都可能破坏正在服务的依赖状态。
CI/CD 中要注意 opcache 和 realpath 缓存
PHP 的 realpath 缓存和 opcache 会记住 vendor 下文件的绝对路径。即使你切了软链,已加载的类仍可能从旧路径读取。常见表现是:部署后报 Class not found,但 ls -l vendor 显示链接正确。
- 部署脚本末尾必须调用
opcache_reset()(需 PHP 启用opcache.enable_cli=1且 web 用户可执行) - 或更稳妥地,在 Apache/Nginx reload 前清空 opcache(
curl -X GET http://localhost/opcache-reset.php) - PHP 8.1+ 可配合
opcache.preload,但 preload 文件路径需随 vendor 链接动态生成,否则会 preload 错误路径
软链切换本身很快,但 PHP 运行时缓存才是原子性落地的最后一道坎——漏掉它,前面所有步骤都白做。


















