应先检查源文件可读、目标目录可写、目标文件可写(如需覆盖),再用临时文件原子替换;file_exists仅判断路径存在,无法识别符号链接、权限等深层问题。
file_exists 判断目标文件是否已存在再决定是否替换直接 rename() 或 copy() 覆盖旧文件,不检查目标路径是否存在,容易误删或静默失败。比如目标是 /var/www/html/config.php,若该路径已被占用(哪怕是个目录),rename() 会返回 false,但不会报错——你得靠 file_exists() 主动确认。
file_exists($target) 返回 true 表示路径存在(无论文件还是目录)is_file($target) 排除目录干扰uploads/),则不该用 file_exists() 判断能否“替换”,而应先确保目录可写:is_writable($dir)
光靠 file_exists() 不够。常见错误是:

rename() 失败且不提示原因。
if (!is_file($source) || !is_readable($source)) { /* 拒绝操作 */ }
$target_dir = dirname($target); if (!is_writable($target_dir)) { /* 报错 */ }
if (file_exists($target) && !is_writable($target)) { /* 提示 chmod 或跳过 */ }
不要直接覆盖关键配置文件。更稳妥的方式是先写入临时文件,再用 rename() 原子替换(Linux/macOS 下 rename() 对同一文件系统是原子的)。
function safe_replace_file($source, $target) {
if (!is_file($source) || !is_readable($source)) {
throw new RuntimeException("源文件不存在或不可读: $source");
}
$target_dir = dirname($target);
if (!is_dir($target_dir) || !is_writable($target_dir)) {
throw new RuntimeException("目标目录不可写: $target_dir");
}
$temp_target = $target . '.tmp.' . uniqid();
if (copy($source, $temp_target) === false) {
throw new RuntimeException("临时文件写入失败: $temp_target");
}
// 确保临时文件已落盘
if (!chmod($temp_target, 0644)) {
unlink($temp_target);
throw new RuntimeException("无法设置临时文件权限");
}
if (rename($temp_target, $target) === false) {
unlink($temp_target);
throw new RuntimeException("原子替换失败,请检查目标路径是否为目录或权限冲突");
}}
为什么 file_exists 有时返回 false 却能成功 rename?
典型场景:目标路径是符号链接,指向一个不存在的位置。此时 file_exists($target) 返回 false,但 rename($source, $target) 仍可能成功——它会创建该符号链接指向的新文件(取决于系统行为)。这不是 bug,而是 PHP 对符号链接的处理逻辑和底层 rename(2) 系统调用一致。
file_exists() 检查的是链接指向的目标,不是链接本身lstat($target) 或 is_link($target)
实际替换逻辑里,file_exists 只是第一道门;真正容易出问题的,是权限、符号链接、挂载点只读、NFS 延迟这些看不见的环节。