应先用 realpath() 标准化路径再 file_exists();防重复创建须用 fopen('x')、touch() 或 file_put_contents(file_excl);判断存在性用 file_exists(),而非 is_file() 或 !is_dir()。

用 file_exists() 判断前先确认路径是否合法
很多开发者直接对用户传入的文件名调用 file_exists(),结果在路径含 ../、空格、中文或未转义斜杠时返回 false,误判为“文件不存在”。这不是函数失效,而是 PHP 在解析路径时已出错或被安全机制拦截。
实操建议:
- 用
realpath()先标准化路径,再判断。它会解析符号链接、折叠../和./,失败时返回false,可提前捕获非法路径 - 检查
dirname()对应目录是否存在且可写(is_dir()+is_writable()),避免文件路径合法但父目录不可写导致创建失败 - Windows 下注意大小写不敏感,Linux 下严格区分,跨平台项目别依赖大小写判断“重复”
fopen() 的 x 模式是原子级防重创建的关键
靠先 file_exists() 再 fopen(..., 'w') 是典型竞态漏洞:两次调用之间可能被其他进程/线程抢先创建同名文件,导致覆盖或逻辑错乱。
正确做法是跳过判断,直接用独占创建模式:
立即学习“PHP免费学习笔记(深入)”;
$fp = fopen('/path/to/file.txt', 'x');
if ($fp === false) {
// 文件已存在,或目录不可写,或磁盘满
$error = error_get_last()['message'] ?? 'unknown';
if (strpos($error, 'File exists') !== false) {
// 真正的“重复”场景
}
}
'x' 模式只在文件**完全不存在**时成功打开并返回句柄,否则立即失败,无中间状态。它底层调用的是系统级的 O_EXCL | O_CREAT,是 POSIX 标准保证的原子操作。
创建前用 touch() 或 file_put_contents() 配合 FILE_EXCL 更简洁
如果只需创建空文件,touch() 最简;若要写内容,file_put_contents() 的 FILE_EXCL 标志效果等同于 fopen('x'),且自动处理关闭。
示例:
// 创建空文件,失败说明已存在
$result = touch('/path/to/file.log');
// 写入内容,仅当文件不存在时成功
$result = file_put_contents('/path/to/data.json', '{"a":1}', LOCK_EX | FILE_EXCL);
if ($result === false) {
// 可能是文件已存在,也可能是权限/磁盘问题
// 建议再用 file_exists() + is_writable() 细分原因
}
注意:FILE_EXCL 仅在目标文件**完全不存在**时生效;若文件存在但为空,仍会失败——这正是你想要的“防重复”行为。
慎用 is_file() 替代 file_exists() 判断“是否存在”
file_exists() 返回 true 对于文件和目录都成立;而 is_file() 只对普通文件返回 true,遇到目录、符号链接、设备文件等均返回 false。如果你本意是“防止同名文件被覆盖”,却用了 is_file(),那同名目录存在时就会误判为“可创建”,最终 fopen('x') 失败或 file_put_contents() 报错 Is a directory。
所以:
- 判断“能否创建同名文件” → 用
file_exists()或直接上'x'模式 - 判断“某路径是否为普通文件” → 才用
is_file() - 永远不要用
is_dir()的反向逻辑代替file_exists()
真正容易被忽略的点是:PHP 的文件系统函数多数不抛异常,失败只返回 false,且错误信息藏在 error_get_last() 里——不主动查,就永远不知道是“已存在”还是“没权限”或“路径越界”。











