文件处理概述
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存储提高文件读写性能