在PHP开发中,文件操作是一个非常基础但又非常重要的技能。无论是读取配置文件、写入日志、上传文件、生成缓存,还是处理用户上传的文件,都需要用到文件操作。

PHP提供了丰富的文件操作函数,可以完成文件的创建、打开、读取、写入、删除、复制、移动、重命名等各种操作。同时,PHP也提供了目录操作函数,可以创建、删除、遍历目录。

今天分享PHP文件操作的详解,从基础到进阶,帮你掌握PHP文件操作的各种技巧。

文件的基本操作

1. 判断文件是否存在

$file = 'test.txt';
if (file_exists($file)) {
    echo "文件存在";
} else {
    echo "文件不存在";
}

fileexists()函数可以判断文件或目录是否存在。如果只判断文件,可以用isfile();如果只判断目录,可以用is_dir()

if (is_file($file)) { echo "是文件"; }
if (is_dir($file)) { echo "是目录"; }

2. 打开文件

$file = 'test.txt';
$handle = fopen($file, 'r'); // 只读方式打开

fopen()函数用于打开文件,第二个参数是打开模式:

  • r:只读,文件指针在开头
  • r+:读写,文件指针在开头
  • w:只写,文件指针在开头,清空文件内容,文件不存在则创建
  • w+:读写,文件指针在开头,清空文件内容,文件不存在则创建
  • a:只写,文件指针在末尾,追加写入,文件不存在则创建
  • a+:读写,文件指针在末尾,追加写入,文件不存在则创建
  • x:只写,文件不存在则创建,存在则报错
  • x+:读写,文件不存在则创建,存在则报错

注意:打开文件后,用完要记得用fclose()关闭文件。

3. 读取文件

方式一:fread读取指定长度

$handle = fopen('test.txt', 'r');
$content = fread($handle, filesize('test.txt')); // 读取整个文件
fclose($handle);

方式二:fgets逐行读取

$handle = fopen('test.txt', 'r');
while (!feof($handle)) {
    $line = fgets($handle); // 读取一行
    echo $line;
}
fclose($handle);

方式三:fgetc逐字符读取

$handle = fopen('test.txt', 'r');
while (!feof($handle)) {
    $char = fgetc($handle); // 读取一个字符
    echo $char;
}
fclose($handle);

方式四:filegetcontents读取整个文件(推荐)

$content = file_get_contents('test.txt'); // 读取整个文件到字符串

filegetcontents()是最简单的读取文件方式,不需要手动打开和关闭文件。它也可以读取远程文件(URL)。

方式五:file读取文件到数组

$lines = file('test.txt'); // 读取文件到数组,每个元素是一行
foreach ($lines as $line) {
    echo $line;
}

4. 写入文件

方式一:fwrite写入

$handle = fopen('test.txt', 'w');
fwrite($handle, 'Hello World'); // 写入内容
fclose($handle);

方式二:fileputcontents写入(推荐)

file_put_contents('test.txt', 'Hello World'); // 写入文件

fileputcontents()是最简单的写入方式,不需要手动打开和关闭文件。如果文件不存在,会自动创建;如果文件存在,会覆盖原有内容。

追加写入:

file_put_contents('test.txt', 'Hello World', FILE_APPEND); // 追加写入

同时加锁:

file_put_contents('test.txt', 'Hello World', FILE_APPEND | LOCK_EX); // 追加写入并加锁

5. 删除文件

$file = 'test.txt';
if (unlink($file)) {
    echo "文件删除成功";
} else {
    echo "文件删除失败";
}

6. 复制文件

$source = 'test.txt';
$dest = 'test_copy.txt';
if (copy($source, $dest)) {
    echo "文件复制成功";
} else {
    echo "文件复制失败";
}

7. 重命名/移动文件

$oldName = 'test.txt';
$newName = 'new_test.txt';
if (rename($oldName, $newName)) {
    echo "文件重命名成功";
} else {
    echo "文件重命名失败";
}

rename()也可以用来移动文件,把文件移动到另一个目录。

文件属性操作

1. 获取文件大小

$file = 'test.txt';
$size = filesize($file); // 文件大小,单位字节
echo "文件大小:" . $size . "字节";

// 格式化显示
function formatSize($size) {
    $units = ['B', 'KB', 'MB', 'GB', 'TB'];
    $i = 0;
    while ($size >= 1024 && $i < 4) {
        $size /= 1024;
        $i++;
    }
    return round($size, 2) . $units[$i];
}
echo formatSize(filesize($file));

2. 获取文件修改时间

$file = 'test.txt';
$mtime = filemtime($file); // 文件修改时间,时间戳
echo "修改时间:" . date('Y-m-d H:i:s', $mtime);

$atime = fileatime($file); // 文件访问时间
$ctime = filectime($file); // 文件创建时间(Windows)/ inode修改时间(Linux)

3. 获取文件权限

$file = 'test.txt';
$perms = fileperms($file); // 文件权限
echo "权限:" . decoct($perms & 0777); // 八进制显示

4. 判断文件类型

$file = 'test.txt';
echo filetype($file); // 文件类型:file、dir、link等

is_file($file);   // 是否是文件
is_dir($file);    // 是否是目录
is_link($file);   // 是否是符号链接
is_readable($file); // 是否可读
is_writable($file); // 是否可写
is_executable($file); // // 是否可执行

5. 获取文件信息

$file = 'test.txt';
$info = stat($file); // 获取文件详细信息
print_r($info);

$pathinfo = pathinfo($file); // 获取路径信息
echo $pathinfo['dirname'];   // 目录名
echo $pathinfo['basename'];  // 文件名(含扩展名)
echo $pathinfo['extension']; // 扩展名
echo $pathinfo['filename'];  // 文件名(不含扩展名)

目录操作

1. 创建目录

$dir = 'new_dir';
if (mkdir($dir)) {
    echo "目录创建成功";
} else {
    echo "目录创建失败";
}

// 递归创建多级目录
mkdir('a/b/c', 0777, true); // 递归创建a/b/c目录

2. 删除目录

$dir = 'new_dir';
if (rmdir($dir)) {
    echo "目录删除成功";
} else {
    echo "目录删除失败";
}

注意:rmdir()只能删除空目录,如果目录中有文件或子目录,需要先删除里面的内容。

3. 遍历目录

方式一:opendir/readdir

$dir = 'uploads';
if (is_dir($dir)) {
    if ($handle = opendir($dir)) {
        while (($file = readdir($handle)) !== false) {
            if ($file != '.' && $file != '..') {
                echo $file . "\n";
            }
        }
        closedir($handle);
    }
}

方式二:scandir(推荐)

$dir = 'uploads';
$files = scandir($dir); // 读取目录内容到数组
foreach ($files as $file) {
    if ($file != '.' && $file != '..') {
        echo $file . "\n";
    }
}

方式三:glob(按模式匹配)

$files = glob('uploads/*.jpg'); // 匹配uploads目录下所有jpg文件
foreach ($files as $file) {
    echo $file . "\n";
}

$files = glob('uploads/*'); // 匹配uploads目录下所有文件和目录

方式四:RecursiveDirectoryIterator(递归遍历)

$dir = new RecursiveDirectoryIterator('uploads');
$iterator = new RecursiveIteratorIterator($dir);
foreach ($iterator as $file) {
    echo $file->getPathname() . "\n";
}

4. 递归删除目录

function deleteDir($dir) {
    if (!is_dir($dir)) return false;
    $files = scandir($dir);
    foreach ($files as $file) {
        if ($file != '.' && $file != '..') {
            $path = $dir . '/' . $file;
            if (is_dir($path)) {
                deleteDir($path); // 递归删除子目录
            } else {
                unlink($path); // 删除文件
            }
        }
    }
    return rmdir($dir); // 删除空目录
}

deleteDir('uploads');

5. 递归复制目录

function copyDir($source, $dest) {
    if (!is_dir($source)) return false;
    if (!is_dir($dest)) mkdir($dest, 0777, true);
    $files = scandir($source);
    foreach ($files as $file) {
        if ($file != '.' && $file != '..') {
            $srcPath = $source . '/' . $file;
            $destPath = $dest . '/' . $file;
            if (is_dir($srcPath)) {
                copyDir($srcPath, $destPath); // 递归复制子目录
            } else {
                copy($srcPath, $destPath); // 复制文件
            }
        }
    }
    return true;
}

copyDir('source_dir', 'dest_dir');

文件上传

文件上传是PHP开发中常见的需求,比如用户头像上传、图片上传、文件分享等。

1. HTML表单

<form action="upload.php" method="post" enctype="multipart/form-data">
    <input type="file" name="file">
    <input type="submit" value="上传">
</form>

注意:文件上传表单必须设置enctype="multipart/form-data",method必须是post。

2. PHP处理上传

if ($_FILES['file']['error'] == UPLOAD_ERR_OK) {
    $tmpName = $_FILES['file']['tmp_name']; // 临时文件名
    $name = $_FILES['file']['name']; // 原始文件名
    $size = $_FILES['file']['size']; // 文件大小
    $type = $_FILES['file']['type']; // 文件类型

    $uploadDir = 'uploads/';
    $dest = $uploadDir . basename($name);

    if (move_uploaded_file($tmpName, $dest)) {
        echo "文件上传成功";
    } else {
        echo "文件上传失败";
    }
} else {
    echo "上传错误:" . $_FILES['file']['error'];
}

3. 上传安全

文件上传有安全风险,一定要做好安全检查:

$allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
$maxSize = 2 * 1024 * 1024; // 2MB

if ($_FILES['file']['error'] != UPLOAD_ERR_OK) {
    die("上传错误");
}

if ($_FILES['file']['size'] > $maxSize) {
    die("文件太大");
}

if (!in_array($_FILES['file']['type'], $allowedTypes)) {
    die("文件类型不允许");
}

// 检查文件扩展名
$ext = pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION);
$allowedExts = ['jpg', 'jpeg', 'png', 'gif'];
if (!in_array(strtolower($ext), $allowedExts)) {
    die("文件扩展名不允许");
}

// 检查是否是真实图片
if (!getimagesize($_FILES['file']['tmp_name'])) {
    die("不是真实图片");
}

// 生成安全的文件名
$newName = uniqid() . '.' . $ext;
$dest = 'uploads/' . $newName;

if (move_uploaded_file($_FILES['file']['tmp_name'], $dest)) {
    echo "上传成功:" . $newName;
}

文件锁

当多个进程同时读写同一个文件时,可能会出现数据不一致的问题。这时候需要用文件锁。

$file = 'data.txt';
$handle = fopen($file, 'a+');

if (flock($handle, LOCK_EX)) { // 排他锁(写入锁)
    fwrite($handle, "新内容\n");
    fflush($handle); // 刷新输出
    flock($handle, LOCK_UN); // 释放锁
} else {
    echo "无法获取锁";
}

fclose($handle);

锁的类型:

  • LOCK_SH:共享锁(读取锁),多个进程可以同时持有
  • LOCK_EX:排他锁(写入锁),只能有一个进程持有
  • LOCK_UN:释放锁
  • LOCK_NB:非阻塞锁,获取不到锁时不等待

fileputcontents()时,可以用LOCK_EX参数加锁:

file_put_contents('data.txt', '内容', LOCK_EX);

常用技巧和最佳实践

  1. 优先使用filegetcontents和fileputcontents:这两个函数简单易用,不需要手动打开和关闭文件,适合大多数场景。
  2. 大文件用fopen/fread/fwrite:处理大文件时,不要一次性读取整个文件,用fopen/fread逐块读取,避免内存溢出。
  3. 检查文件是否存在:操作文件前,先检查文件是否存在,避免报错。
  4. 处理文件路径:用DIRECTORY_SEPARATOR处理跨平台路径,或者统一用/(Windows也支持)。
  5. 文件上传安全:文件上传一定要做安全检查,验证文件类型、大小、扩展名,生成安全的文件名,上传到web根目录外或禁止执行。
  6. 文件权限:注意文件和目录的权限设置,不要设置过高的权限(如0777),避免安全风险。
  7. 错误处理:文件操作可能失败,要做好错误处理,检查返回值。
  8. 关闭文件:用fopen打开的文件,用完一定要fclose关闭,避免资源泄漏。
  9. 使用文件锁:多进程同时写文件时,要用文件锁,避免数据冲突。
  10. 清理临时文件:上传的临时文件、生成的临时文件,用完要及时清理,避免占用磁盘空间。

常见问题

1. 文件权限不足

报错:Permission denied 解决:检查文件和目录的权限,确保PHP有读写权限。Linux下用chmod修改权限,Windows下检查文件夹权限。

2. 文件路径错误

报错:No such file or directory 解决:检查文件路径是否正确,用绝对路径或正确的相对路径。可以用DIR获取当前目录,拼接路径。

3. 大文件内存溢出

报错:Allowed memory size exhausted 解决:不要一次性读取大文件,用fopen/fread逐块读取,或者用生成器(Generator)处理。

4. 文件上传失败

常见原因:

  • 表单没有设置enctype="multipart/form-data"
  • 文件大小超过uploadmaxfilesizepostmaxsize
  • 上传目录没有写入权限
  • 临时目录没有写入权限

解决:检查php.ini配置,检查目录权限,检查表单设置。

5. 中文文件名乱码

解决:设置文件系统编码,Windows下用iconv()转换编码,或者用英文/数字文件名。

总结

PHP文件操作是PHP开发的基础技能,掌握它能让你处理各种文件相关的需求。

核心要点:

  1. 基本操作:file_exists判断、fopen打开、fread/fgets读取、fwrite写入、fclose关闭、unlink删除、copy复制、rename重命名
  2. 简便函数:filegetcontents读取、fileputcontents写入、scandir遍历目录、glob模式匹配
  3. 文件属性:filesize大小、filemtime修改时间、fileperms权限、pathinfo路径信息、stat详细信息
  4. 目录操作:mkdir创建、rmdir删除、opendir/readdir遍历、递归删除/复制
  5. 文件上传:HTML表单、$FILES处理、moveuploaded_file、安全检查
  6. 文件锁:flock加锁,避免多进程冲突
  7. 最佳实践:用简便函数、大文件逐块读、检查存在、处理路径、上传安全、权限设置、错误处理

希望这篇文章能帮你掌握PHP文件操作,让你的开发更加高效。