PHP文件处理

全面学习PHP文件读写、上传、目录操作和文件系统管理

文件处理概述

PHP提供了丰富的文件处理函数,可以读取、写入、修改和管理文件。文件处理是Web开发中的重要组成部分,常用于配置文件读取、数据存储、日志记录、文件上传等场景。

文件处理的重要性

  • 数据持久化: 将数据保存到文件中,实现长期存储
  • 配置管理: 读取和修改应用程序配置文件
  • 日志记录: 记录应用程序运行状态和错误信息
  • 文件上传: 处理用户上传的文件
  • 数据交换: 导入和导出数据文件
  • 缓存机制: 使用文件作为缓存存储介质

PHP文件处理发展历程

PHP的文件处理功能随着版本更新不断改进:

  • PHP 4: 提供了基本的文件读写函数,如fopen、fread、fwrite等
  • PHP 5: 引入了更便捷的文件操作函数,如file_get_contents、file_put_contents
  • PHP 7: 改进了文件处理性能和安全性
  • PHP 8: 进一步优化了文件系统函数和错误处理

文件读写基础

PHP提供了多种文件读写方法,从基本的逐字节读取到便捷的一次性读取。

打开和关闭文件

使用fopen()函数打开文件,使用fclose()函数关闭文件。正确关闭文件是良好的编程习惯。

打开和关闭文件
<?php
// 打开文件 - 基本用法
$file = fopen("example.txt", "r");

// 检查文件是否成功打开
if ($file) {
    // 文件操作...
    
    // 关闭文件
    fclose($file);
} else {
    echo "无法打开文件!";
}

// 使用绝对路径
$file = fopen("/var/www/html/data/example.txt", "r");

// 使用相对路径
$file = fopen("./data/example.txt", "r");

// 使用URL(需要allow_url_fopen开启)
$file = fopen("http://example.com/data.txt", "r");
?>

文件打开模式

模式 描述 文件指针位置
"r" 只读方式打开,文件必须存在 文件开头
"r+" 读写方式打开,文件必须存在 文件开头
"w" 只写方式打开,如果文件不存在则创建,如果存在则清空内容 文件开头
"w+" 读写方式打开,如果文件不存在则创建,如果存在则清空内容 文件开头
"a" 追加方式打开,如果文件不存在则创建 文件末尾
"a+" 读写方式打开,如果文件不存在则创建 文件末尾
"x" 创建并以写入方式打开,如果文件已存在则返回FALSE 文件开头
"x+" 创建并以读写方式打开,如果文件已存在则返回FALSE 文件开头
"c" 只写方式打开,如果文件不存在则创建,如果存在不清空内容 文件开头
"c+" 读写方式打开,如果文件不存在则创建,如果存在不清空内容 文件开头
提示:

在Windows系统上,文本文件和二进制文件的处理方式不同。如果需要处理二进制文件(如图片、视频等),应该在模式后添加'b',如"rb"、"wb+"等。

读取文件

PHP提供了多种读取文件内容的方法,适用于不同的使用场景。

逐行读取文件

逐行读取文件
<?php
// 使用fgets逐行读取
$file = fopen("example.txt", "r");

if ($file) {
    while (!feof($file)) {
        $line = fgets($file);
        echo $line . "<br>";
    }
    fclose($file);
}

// 使用fgetss过滤HTML标签
$file = fopen("example.html", "r");
if ($file) {
    while (!feof($file)) {
        $line = fgetss($file); // 过滤HTML标签
        echo $line;
    }
    fclose($file);
}

// 使用fgetcsv读取CSV文件
$file = fopen("data.csv", "r");
if ($file) {
    // 读取CSV标题行
    $headers = fgetcsv($file);
    print_r($headers);
    
    // 读取数据行
    while (($data = fgetcsv($file)) !== FALSE) {
        print_r($data);
    }
    fclose($file);
}
?>

读取整个文件

读取整个文件
<?php
// 使用 file_get_contents() 读取整个文件
$content = file_get_contents("example.txt");
echo $content;

// 读取远程文件(需要allow_url_fopen开启)
$web_content = file_get_contents("http://example.com");
echo $web_content;

// 使用 file() 将文件读入数组
$lines = file("example.txt");
foreach ($lines as $line_num => $line) {
    echo "行 #<b>{$line_num}</b> : " . htmlspecialchars($line) . "<br>\n";
}

// 使用 file() 读取远程文件到数组
$web_lines = file("http://example.com");
print_r($web_lines);

// 使用 readfile() 直接输出文件内容
$bytes = readfile("example.txt");
echo "<br>读取了 $bytes 字节";

// 使用 fread() 读取指定字节数
$file = fopen("example.txt", "r");
$content = fread($file, 100); // 读取前100字节
fclose($file);
echo $content;
?>

二进制文件读取

二进制文件读取
<?php
// 读取图片文件
$image_data = file_get_contents("image.jpg");

// 在浏览器中显示图片
header('Content-Type: image/jpeg');
echo $image_data;

// 使用fread读取二进制文件
$file = fopen("binary.dat", "rb"); // 注意使用二进制模式
$data = fread($file, filesize("binary.dat"));
fclose($file);

// 处理二进制数据
$binary_string = unpack("C*", $data); // 将二进制数据转换为字节数组
print_r($binary_string);
?>

写入文件

PHP提供了多种写入文件的方法,从基本的逐字节写入到便捷的一次性写入。

写入文件

写入文件
<?php
// 使用 fwrite() 写入文件
$file = fopen("example.txt", "w");
fwrite($file, "这是第一行\n");
fwrite($file, "这是第二行\n");
fclose($file);

// 使用 file_put_contents() 写入文件
$data = "这是要写入文件的内容\n";
file_put_contents("example.txt", $data);

// 使用 file_put_contents() 追加内容
$data = "这是追加的内容\n";
file_put_contents("example.txt", $data, FILE_APPEND);

// 使用 file_put_contents() 锁定文件
$data = "这是带锁定的内容\n";
file_put_contents("example.txt", $data, LOCK_EX);

// 使用 fputs() 写入文件(fputs是fwrite的别名)
$file = fopen("example.txt", "w");
fputs($file, "使用fputs写入的内容\n");
fclose($file);
?>

追加内容到文件

追加内容
<?php
// 使用追加模式打开文件
$file = fopen("example.txt", "a");
fwrite($file, "这是追加的内容\n");
fclose($file);

// 使用file_put_contents追加
file_put_contents("example.txt", "这也是追加的内容\n", FILE_APPEND);

// 创建日志记录函数
function log_message($message, $log_file = "app.log") {
    $timestamp = date("Y-m-d H:i:s");
    $log_entry = "[$timestamp] $message\n";
    file_put_contents($log_file, $log_entry, FILE_APPEND | LOCK_EX);
}

// 使用日志函数
log_message("用户登录成功");
log_message("数据库查询执行", "db.log");
?>

二进制文件写入

二进制文件写入
<?php
// 创建二进制数据
$binary_data = pack("C*", 65, 66, 67, 68); // ABCD的ASCII码

// 写入二进制文件
$file = fopen("binary.dat", "wb");
fwrite($file, $binary_data);
fclose($file);

// 复制图片文件
$source_image = file_get_contents("source.jpg");
file_put_contents("copy.jpg", $source_image);

// 生成简单的图片文件
$image = imagecreate(100, 100);
$background = imagecolorallocate($image, 255, 255, 255);
$text_color = imagecolorallocate($image, 0, 0, 0);
imagestring($image, 5, 20, 40, "Hello", $text_color);
imagepng($image, "generated.png");
imagedestroy($image);
?>

文件上传

PHP可以处理通过表单上传的文件,这是Web应用中常见的功能。

文件上传表单

文件上传示例

文件上传处理

文件上传处理
<?php
// 检查是否有文件上传
if (isset($_POST["submit"])) {
    $target_dir = "uploads/";
    $target_file = $target_dir . basename($_FILES["fileToUpload"]["name"]);
    $uploadOk = 1;
    $imageFileType = strtolower(pathinfo($target_file, PATHINFO_EXTENSION));
    
    // 检查文件是否为真实图片
    $check = getimagesize($_FILES["fileToUpload"]["tmp_name"]);
    if($check !== false) {
        echo "文件是一个 - " . $check["mime"] . ".";
        $uploadOk = 1;
    } else {
        echo "文件不是图片。";
        $uploadOk = 0;
    }
    
    // 检查文件是否已存在
    if (file_exists($target_file)) {
        echo "抱歉,文件已存在。";
        $uploadOk = 0;
    }
    
    // 检查文件大小
    if ($_FILES["fileToUpload"]["size"] > 500000) {
        echo "抱歉,文件太大。";
        $uploadOk = 0;
    }
    
    // 允许特定文件格式
    if($imageFileType != "jpg" && $imageFileType != "png" && $imageFileType != "jpeg"
    && $imageFileType != "gif" ) {
        echo "抱歉,只允许 JPG, JPEG, PNG & GIF 文件。";
        $uploadOk = 0;
    }
    
    // 检查 $uploadOk 是否为 0
    if ($uploadOk == 0) {
        echo "抱歉,您的文件未被上传。";
    // 如果一切正常,尝试上传文件
    } else {
        if (move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $target_file)) {
            echo "文件 " . htmlspecialchars( basename( $_FILES["fileToUpload"]["name"])) . " 已上传。";
        } else {
            echo "抱歉,上传文件时出现错误。";
        }
    }
}

// 处理多个文件上传
if (isset($_POST["submit"]) && !empty($_FILES["fileToUpload"]["name"][0])) {
    $total_files = count($_FILES["fileToUpload"]["name"]);
    
    for($i = 0; $i < $total_files; $i++) {
        $file_name = $_FILES["fileToUpload"]["name"][$i];
        $file_tmp = $_FILES["fileToUpload"]["tmp_name"][$i];
        $file_size = $_FILES["fileToUpload"]["size"][$i];
        $file_type = $_FILES["fileToUpload"]["type"][$i];
        
        $target_file = "uploads/" . basename($file_name);
        
        if (move_uploaded_file($file_tmp, $target_file)) {
            echo "文件 $file_name 上传成功!<br>";
        } else {
            echo "文件 $file_name 上传失败。<br>";
        }
    }
}
?>

文件上传安全考虑

安全警告:

文件上传功能存在安全风险,必须采取适当的安全措施:

  • 验证文件类型和内容,不要仅依赖文件扩展名
  • 限制上传文件的大小
  • 将上传文件存储在Web根目录之外,或限制对上传目录的访问
  • 对上传的文件进行病毒扫描
  • 重命名上传的文件,避免文件名冲突和路径遍历攻击
  • 限制允许上传的文件类型
安全的文件上传处理
<?php
function safe_file_upload($file_input_name, $upload_dir = "uploads/") {
    // 检查是否有文件上传
    if (!isset($_FILES[$file_input_name]) || $_FILES[$file_input_name]['error'] != UPLOAD_ERR_OK) {
        return ['success' => false, 'message' => '文件上传失败'];
    }
    
    $file = $_FILES[$file_input_name];
    
    // 检查文件大小(限制为5MB)
    $max_size = 5 * 1024 * 1024;
    if ($file['size'] > $max_size) {
        return ['success' => false, 'message' => '文件太大,最大允许5MB'];
    }
    
    // 检查文件类型
    $allowed_types = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf'];
    $finfo = finfo_open(FILEINFO_MIME_TYPE);
    $mime_type = finfo_file($finfo, $file['tmp_name']);
    finfo_close($finfo);
    
    if (!in_array($mime_type, $allowed_types)) {
        return ['success' => false, 'message' => '不允许的文件类型'];
    }
    
    // 生成安全的文件名
    $file_extension = pathinfo($file['name'], PATHINFO_EXTENSION);
    $safe_filename = uniqid() . '_' . md5($file['name']) . '.' . $file_extension;
    $target_path = $upload_dir . $safe_filename;
    
    // 确保上传目录存在
    if (!is_dir($upload_dir)) {
        mkdir($upload_dir, 0755, true);
    }
    
    // 移动文件
    if (move_uploaded_file($file['tmp_name'], $target_path)) {
        return [
            'success' => true,
            'message' => '文件上传成功',
            'filename' => $safe_filename,
            'original_name' => $file['name'],
            'file_path' => $target_path
        ];
    } else {
        return ['success' => false, 'message' => '文件移动失败'];
    }
}

// 使用安全文件上传函数
$result = safe_file_upload('fileToUpload');
if ($result['success']) {
    echo "文件上传成功: " . $result['filename'];
} else {
    echo "上传失败: " . $result['message'];
}
?>

文件和目录操作

PHP提供了丰富的文件和目录操作函数,用于管理文件系统。

检查文件是否存在

检查文件
<?php
// 检查文件是否存在
if (file_exists("example.txt")) {
    echo "文件存在。";
} else {
    echo "文件不存在。";
}

// 获取文件大小
$file_size = filesize("example.txt");
echo "文件大小: " . $file_size . " 字节";

// 获取文件最后修改时间
$last_modified = filemtime("example.txt");
echo "最后修改时间: " . date("Y-m-d H:i:s", $last_modified);

// 获取文件最后访问时间
$last_accessed = fileatime("example.txt");
echo "最后访问时间: " . date("Y-m-d H:i:s", $last_accessed);

// 检查文件类型
if (is_file("example.txt")) {
    echo "这是一个文件";
}

if (is_dir("uploads")) {
    echo "这是一个目录";
}

// 检查文件是否可读/可写
if (is_readable("example.txt")) {
    echo "文件可读";
}

if (is_writable("example.txt")) {
    echo "文件可写";
}

// 获取文件权限
$permissions = fileperms("example.txt");
echo "文件权限: " . substr(sprintf('%o', $permissions), -4);
?>

目录操作

目录操作
<?php
// 创建目录
if (!file_exists("new_directory")) {
    mkdir("new_directory", 0755);
    echo "目录已创建。";
}

// 创建多级目录
if (!file_exists("parent/child/grandchild")) {
    mkdir("parent/child/grandchild", 0755, true);
    echo "多级目录已创建。";
}

// 读取目录内容
$dir = "uploads/";
if (is_dir($dir)) {
    if ($dh = opendir($dir)) {
        while (($file = readdir($dh)) !== false) {
            echo "文件名: " . $file . "<br>";
        }
        closedir($dh);
    }
}

// 使用scandir获取目录内容
$files = scandir("uploads/");
print_r($files);

// 使用glob模式匹配文件
$images = glob("uploads/*.{jpg,jpeg,png,gif}", GLOB_BRACE);
print_r($images);

// 删除文件
if (file_exists("old_file.txt")) {
    unlink("old_file.txt");
    echo "文件已删除。";
}

// 删除目录
if (is_dir("empty_dir")) {
    rmdir("empty_dir");
    echo "目录已删除。";
}

// 递归删除目录
function delete_directory($dir) {
    if (!file_exists($dir)) {
        return true;
    }
    
    if (!is_dir($dir)) {
        return unlink($dir);
    }
    
    foreach (scandir($dir) as $item) {
        if ($item == '.' || $item == '..') {
            continue;
        }
        
        if (!delete_directory($dir . DIRECTORY_SEPARATOR . $item)) {
            return false;
        }
    }
    
    return rmdir($dir);
}

// 使用递归删除函数
delete_directory("old_directory");
?>

文件路径操作

文件路径操作
<?php
$path = "/var/www/html/images/photo.jpg";

// 获取文件名
$filename = basename($path);
echo "文件名: $filename\n";

// 获取目录名
$dirname = dirname($path);
echo "目录名: $dirname\n";

// 获取路径信息
$pathinfo = pathinfo($path);
print_r($pathinfo);

// 获取文件扩展名
$extension = pathinfo($path, PATHINFO_EXTENSION);
echo "文件扩展名: $extension\n";

// 获取不包含扩展名的文件名
$filename_no_ext = pathinfo($path, PATHINFO_FILENAME);
echo "文件名(无扩展名): $filename_no_ext\n";

// 构建路径
$new_path = dirname($path) . DIRECTORY_SEPARATOR . "new_" . basename($path);
echo "新路径: $new_path\n";

// 获取绝对路径
$real_path = realpath("example.txt");
echo "绝对路径: $real_path\n";

// 检查路径是否为绝对路径
if (strpos($path, '/') === 0) {
    echo "这是绝对路径\n";
} else {
    echo "这是相对路径\n";
}
?>

高级文件操作

PHP还提供了一些高级文件操作功能,用于更复杂的文件处理场景。

文件锁定

文件锁定
<?php
// 使用文件锁定防止并发写入
$file = fopen("counter.txt", "c+"); // 创建或打开文件用于读写

// 尝试获取独占锁
if (flock($file, LOCK_EX)) {
    // 读取当前计数器值
    $counter = (int)fread($file, filesize("counter.txt"));
    
    // 增加计数器
    $counter++;
    
    // 回到文件开头
    ftruncate($file, 0); // 清空文件
    rewind($file); // 回到文件开头
    
    // 写入新值
    fwrite($file, $counter);
    
    // 释放锁
    flock($file, LOCK_UN);
} else {
    echo "无法获取文件锁!";
}

fclose($file);

// 使用共享锁读取文件
$file = fopen("data.txt", "r");
if (flock($file, LOCK_SH)) {
    $content = fread($file, filesize("data.txt"));
    flock($file, LOCK_UN);
}
fclose($file);
?>

文件指针操作

文件指针操作
<?php
$file = fopen("example.txt", "r+");

// 获取当前指针位置
$position = ftell($file);
echo "当前指针位置: $position\n";

// 移动到文件开头
rewind($file);

// 移动到第10个字节
fseek($file, 10);

// 从当前位置向后移动5个字节
fseek($file, 5, SEEK_CUR);

// 从文件末尾向前移动10个字节
fseek($file, -10, SEEK_END);

// 读取从当前位置到文件末尾的内容
$remaining = fread($file, filesize("example.txt") - ftell($file));

fclose($file);
?>

临时文件

临时文件操作
<?php
// 创建临时文件
$temp_file = tmpfile();
fwrite($temp_file, "这是临时文件内容\n");
rewind($temp_file);
echo fread($temp_file, 1024);
// 文件会在fclose时或脚本结束时自动删除
fclose($temp_file);

// 获取临时文件名
$temp_filename = tempnam("/tmp", "PHP_");
echo "临时文件名: $temp_filename\n";

// 使用临时文件
file_put_contents($temp_filename, "临时数据");
$data = file_get_contents($temp_filename);
echo "临时文件内容: $data\n";

// 删除临时文件
unlink($temp_filename);

// 获取系统临时目录
$temp_dir = sys_get_temp_dir();
echo "系统临时目录: $temp_dir\n";
?>

文件处理最佳实践

文件处理最佳实践:
  • 始终检查文件操作是否成功,处理可能的错误
  • 使用适当的文件打开模式,避免意外覆盖重要文件
  • 及时关闭文件句柄,释放系统资源
  • 对大文件使用流式处理,避免内存不足
  • 对用户上传的文件进行严格的安全检查
  • 使用文件锁定防止并发访问导致的数据损坏
  • 定期备份重要文件
  • 使用适当的文件权限,保护敏感数据

性能优化建议

  • 对于小文件,使用file_get_contents()file_put_contents()更简洁高效
  • 对于大文件,使用流式处理(fopen/fread/fwrite)避免内存问题
  • 使用缓存减少文件I/O操作
  • 批量处理文件操作,减少系统调用
  • 使用SSD存储提高文件读写性能