应直接写文件而非先调file_exists(),因其冗余且引发竞态条件;正确做法是用file_put_contents()或fopen(...,'x')原子操作尝试创建并捕获失败。

不用先调 file_exists() 再创建文件——这不仅多余,还可能引发竞态条件(race condition),尤其在并发场景下。
为什么先判再创是错的
两次系统调用(file_exists() + fopen() 或 file_put_contents())之间存在时间窗口:文件可能被其他进程/线程创建或删除。结果就是:判完说“不存在”,一写却报“文件已存在”或“权限失败”,逻辑反而更难处理。
-
file_exists()只是检查路径,不保证后续操作能成功(比如权限不足、磁盘满、父目录不存在) - PHP 写文件本身就能判断是否可写+可创建,没必要多一次判断
- 多数情况下,你真正关心的是“能不能写成”,而不是“当前存不存在”
正确做法:直接写,用返回值或异常判断
用 file_put_contents() 最简洁,它默认会创建文件(如果不存在),并返回写入字节数;失败返回 false。
if (file_put_contents('/path/to/file.txt', 'hello') === false) {
error_log('写文件失败:' . error_get_last()['message']);
}
如果需要严格区分“创建新文件”和“覆盖已有文件”,可用 FILE_EXCL 标志:
立即学习“PHP免费学习笔记(深入)”;
$fp = fopen('/path/to/file.txt', 'x'); // 注意是 'x' 模式
if ($fp === false) {
// 失败:文件已存在,或父目录不可写等
$err = error_get_last();
if (strpos($err['message'], 'File exists') !== false) {
// 明确知道是“已存在”
}
}
-
fopen(..., 'x')是原子操作:只在文件**完全不存在**时才成功创建并打开 -
'x'和'c'不同:'c'会打开或创建,但不排斥已存在;'x'才是真正的“create-only” - 注意:
'x'模式不支持追加,也不支持读,仅用于新建
什么时候真要先用 file\_exists?
极少。只在以下明确场景才有意义:
- 你**不打算写文件**,只是想提前告诉用户“这个配置文件已经存在,请确认是否覆盖”(UI 层逻辑)
- 你依赖文件存在作为某个流程的开关,且该文件**绝不会被其他进程修改**(比如部署脚本中检查
.installed标记) - 你想避免反复尝试写一个明显不该存在的路径(比如检查
/etc/passwd是否存在来判断运行环境——但这属于误用,应改用PHP_OS_FAMILY等)
即便如此,也建议配合 is_writable() 一起用,因为存在 ≠ 可写。
常见错误与坑
最典型的是嵌套判断写法:
if (!file_exists($path)) {
if (!file_put_contents($path, $content)) {
die('创建失败');
}
} else {
if (!file_put_contents($path, $content)) {
die('写入失败');
}
}
这段代码既冗余又危险:两次 file_put_contents() 调用,中间没做任何隔离,而且没处理父目录不存在的问题。
- 漏掉
dirname($path)是否存在 → 用is_dir()+mkdir(..., 0755, true)预创建 - 忽略 umask 影响 →
file_put_contents()创建的文件权限受 umask 控制,必要时用chmod()补设 - 把
file_exists()当权限检查 → 它对无读权限的文件也可能返回false(尤其是 Windows 下 ACL 复杂时)
真正关键的不是“文件存不存在”,而是“当前 PHP 进程有没有权限在目标位置完成整个写操作”。这事,一步到位去试,比猜靠谱得多。











