PHP文件操作入门:读写文件的实用技巧
Orion K Lv6

PHP文件操作入门:读写文件的实用技巧

文件操作是Web开发中的常见需求,无论是日志记录、配置文件读取,还是文件上传下载,都离不开文件操作。作为一名PHP开发者,我想分享一些关于PHP文件操作的实用知识和经验。

文件基本操作

1. 检查文件和目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?php
$filename = "test.txt";
$dirname = "uploads";

// 检查文件是否存在
if (file_exists($filename)) {
echo "文件 $filename 存在\n";
} else {
echo "文件 $filename 不存在\n";
}

// 检查是否为文件
if (is_file($filename)) {
echo "$filename 是一个文件\n";
}

// 检查是否为目录
if (is_dir($dirname)) {
echo "$dirname 是一个目录\n";
} else {
echo "$dirname 不是目录\n";
}

// 检查文件权限
if (is_readable($filename)) {
echo "$filename 可读\n";
}

if (is_writable($filename)) {
echo "$filename 可写\n";
}

if (is_executable($filename)) {
echo "$filename 可执行\n";
}

// 获取文件信息
if (file_exists($filename)) {
$filesize = filesize($filename);
$filetime = filemtime($filename);
$filetype = filetype($filename);

echo "文件大小:$filesize 字节\n";
echo "修改时间:" . date('Y-m-d H:i:s', $filetime) . "\n";
echo "文件类型:$filetype\n";
}
?>

2. 文件路径操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?php
$filepath = "/var/www/html/uploads/image.jpg";

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

// 分别获取各部分
echo "目录:" . dirname($filepath) . "\n";
echo "文件名:" . basename($filepath) . "\n";
echo "扩展名:" . pathinfo($filepath, PATHINFO_EXTENSION) . "\n";
echo "不含扩展名的文件名:" . pathinfo($filepath, PATHINFO_FILENAME) . "\n";

// 构建路径
$dir = "/var/www/html";
$filename = "config.php";
$fullpath = $dir . DIRECTORY_SEPARATOR . $filename;
echo "完整路径:$fullpath\n";

// 获取绝对路径
$relativePath = "../config/database.php";
$absolutePath = realpath($relativePath);
if ($absolutePath) {
echo "绝对路径:$absolutePath\n";
} else {
echo "路径不存在\n";
}

// 规范化路径
function normalizePath($path) {
$path = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $path);
$parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen');
$absolutes = [];

foreach ($parts as $part) {
if ('.' == $part) continue;
if ('..' == $part) {
array_pop($absolutes);
} else {
$absolutes[] = $part;
}
}

return implode(DIRECTORY_SEPARATOR, $absolutes);
}

echo "规范化路径:" . normalizePath("/var/www/../html/./uploads") . "\n";
?>

文件读取操作

1. 一次性读取文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php
$filename = "sample.txt";

// 创建示例文件
file_put_contents($filename, "第一行内容\n第二行内容\n第三行内容\n");

// 方法1:file_get_contents(推荐用于小文件)
$content = file_get_contents($filename);
if ($content !== false) {
echo "文件内容:\n$content\n";
} else {
echo "读取文件失败\n";
}

// 方法2:file(读取为数组,每行一个元素)
$lines = file($filename);
if ($lines !== false) {
echo "按行读取:\n";
foreach ($lines as $lineNumber => $line) {
echo "第" . ($lineNumber + 1) . "行:" . trim($line) . "\n";
}
}

// 方法3:file(去除换行符)
$lines = file($filename, FILE_IGNORE_NEW_LINES);
print_r($lines);

// 读取远程文件
$url = "https://httpbin.org/json";
$context = stream_context_create([
'http' => [
'timeout' => 10,
'user_agent' => 'PHP Script'
]
]);

$remoteContent = file_get_contents($url, false, $context);
if ($remoteContent !== false) {
echo "远程内容:\n$remoteContent\n";
}
?>

2. 逐行读取大文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<?php
// 创建大文件示例
$largeFile = "large_file.txt";
$handle = fopen($largeFile, 'w');
for ($i = 1; $i <= 10000; $i++) {
fwrite($handle, "这是第 $i 行内容\n");
}
fclose($handle);

// 逐行读取大文件(内存友好)
function readLargeFile($filename) {
$handle = fopen($filename, 'r');
if (!$handle) {
echo "无法打开文件\n";
return;
}

$lineNumber = 1;
while (($line = fgets($handle)) !== false) {
// 处理每一行
if ($lineNumber <= 5 || $lineNumber > 9995) {
echo "行 $lineNumber: " . trim($line) . "\n";
} elseif ($lineNumber == 6) {
echo "... (省略中间行) ...\n";
}
$lineNumber++;
}

fclose($handle);
echo "总共读取了 " . ($lineNumber - 1) . " 行\n";
}

readLargeFile($largeFile);

// 使用SplFileObject读取文件(面向对象方式)
function readWithSplFileObject($filename) {
try {
$file = new SplFileObject($filename);
$file->setFlags(SplFileObject::READ_AHEAD | SplFileObject::SKIP_EMPTY | SplFileObject::DROP_NEW_LINE);

$lineNumber = 1;
foreach ($file as $line) {
if ($lineNumber <= 3) {
echo "SplFileObject 行 $lineNumber: $line\n";
}
$lineNumber++;
if ($lineNumber > 3) break;
}
} catch (Exception $e) {
echo "读取文件出错:" . $e->getMessage() . "\n";
}
}

readWithSplFileObject($largeFile);

// 清理示例文件
unlink($largeFile);
?>

3. 二进制文件读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?php
// 创建二进制文件示例
$binaryFile = "binary_data.bin";
$data = pack("N*", 1234567890, 987654321, 555666777);
file_put_contents($binaryFile, $data);

// 读取二进制文件
$handle = fopen($binaryFile, 'rb');
if ($handle) {
// 读取固定字节数
$chunk = fread($handle, 4); // 读取4字节
$number = unpack("N", $chunk)[1];
echo "读取的数字:$number\n";

// 读取剩余内容
$remaining = fread($handle, filesize($binaryFile) - 4);
$numbers = unpack("N*", $remaining);
echo "剩余数字:";
print_r($numbers);

fclose($handle);
}

// 读取文件的十六进制表示
function hexDump($filename, $length = 16) {
$handle = fopen($filename, 'rb');
if (!$handle) return;

$offset = 0;
while (($data = fread($handle, $length)) !== false && strlen($data) > 0) {
$hex = bin2hex($data);
$hex = chunk_split($hex, 2, ' ');
$ascii = preg_replace('/[^\x20-\x7E]/', '.', $data);

printf("%08X: %-48s %s\n", $offset, $hex, $ascii);
$offset += strlen($data);
}

fclose($handle);
}

echo "二进制文件十六进制表示:\n";
hexDump($binaryFile);

unlink($binaryFile);
?>

文件写入操作

1. 简单文件写入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php
$filename = "output.txt";
$content = "这是要写入的内容\n";

// 方法1:file_put_contents(推荐)
$result = file_put_contents($filename, $content);
if ($result !== false) {
echo "写入成功,写入了 $result 字节\n";
} else {
echo "写入失败\n";
}

// 追加内容
$additionalContent = "这是追加的内容\n";
$result = file_put_contents($filename, $additionalContent, FILE_APPEND);
if ($result !== false) {
echo "追加成功,追加了 $result 字节\n";
}

// 加锁写入(防止并发问题)
$lockedContent = "这是加锁写入的内容\n";
$result = file_put_contents($filename, $lockedContent, FILE_APPEND | LOCK_EX);
if ($result !== false) {
echo "加锁写入成功\n";
}

// 读取并显示文件内容
echo "文件内容:\n";
echo file_get_contents($filename);
?>

2. 使用文件句柄写入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?php
$filename = "detailed_output.txt";

// 打开文件进行写入
$handle = fopen($filename, 'w');
if (!$handle) {
die("无法打开文件进行写入\n");
}

// 写入不同类型的数据
fwrite($handle, "标题:文件操作示例\n");
fwrite($handle, str_repeat("=", 30) . "\n");

$data = [
"姓名" => "张三",
"年龄" => 25,
"城市" => "北京"
];

foreach ($data as $key => $value) {
fwrite($handle, "$key: $value\n");
}

// 写入当前时间
fwrite($handle, "创建时间:" . date('Y-m-d H:i:s') . "\n");

// 关闭文件
fclose($handle);

echo "文件写入完成\n";

// 使用不同模式打开文件
$modes = [
'r' => '只读模式',
'w' => '写入模式(清空文件)',
'a' => '追加模式',
'r+' => '读写模式',
'w+' => '读写模式(清空文件)',
'a+' => '读写追加模式'
];

echo "\n文件打开模式说明:\n";
foreach ($modes as $mode => $description) {
echo "$mode: $description\n";
}
?>

3. CSV文件操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?php
$csvFile = "users.csv";

// 写入CSV文件
$users = [
['姓名', '年龄', '城市', '邮箱'],
['张三', 25, '北京', 'zhangsan@example.com'],
['李四', 30, '上海', 'lisi@example.com'],
['王五', 28, '广州', 'wangwu@example.com']
];

$handle = fopen($csvFile, 'w');
if ($handle) {
foreach ($users as $user) {
fputcsv($handle, $user);
}
fclose($handle);
echo "CSV文件写入完成\n";
}

// 读取CSV文件
echo "\n读取CSV文件:\n";
$handle = fopen($csvFile, 'r');
if ($handle) {
$isHeader = true;
while (($data = fgetcsv($handle)) !== false) {
if ($isHeader) {
echo "表头:" . implode(' | ', $data) . "\n";
echo str_repeat('-', 50) . "\n";
$isHeader = false;
} else {
echo implode(' | ', $data) . "\n";
}
}
fclose($handle);
}

// 使用SplFileObject处理CSV
echo "\n使用SplFileObject读取CSV:\n";
try {
$file = new SplFileObject($csvFile);
$file->setFlags(SplFileObject::READ_CSV);

foreach ($file as $row) {
if ($row[0] !== null) { // 跳过空行
echo "用户:" . implode(', ', $row) . "\n";
}
}
} catch (Exception $e) {
echo "读取CSV出错:" . $e->getMessage() . "\n";
}
?>

目录操作

1. 创建和删除目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
<?php
$dirName = "test_directory";
$subDir = $dirName . "/subdirectory";

// 创建目录
if (!is_dir($dirName)) {
if (mkdir($dirName, 0755)) {
echo "目录 $dirName 创建成功\n";
} else {
echo "目录 $dirName 创建失败\n";
}
}

// 递归创建目录
if (!is_dir($subDir)) {
if (mkdir($subDir, 0755, true)) { // true表示递归创建
echo "子目录 $subDir 创建成功\n";
}
}

// 在目录中创建文件
$testFile = $subDir . "/test.txt";
file_put_contents($testFile, "测试文件内容");

// 删除文件
if (file_exists($testFile)) {
if (unlink($testFile)) {
echo "文件 $testFile 删除成功\n";
}
}

// 删除目录(必须为空)
if (is_dir($subDir)) {
if (rmdir($subDir)) {
echo "子目录 $subDir 删除成功\n";
}
}

if (is_dir($dirName)) {
if (rmdir($dirName)) {
echo "目录 $dirName 删除成功\n";
}
}

// 递归删除目录的函数
function removeDirectory($dir) {
if (!is_dir($dir)) {
return false;
}

$files = array_diff(scandir($dir), ['.', '..']);

foreach ($files as $file) {
$path = $dir . DIRECTORY_SEPARATOR . $file;
if (is_dir($path)) {
removeDirectory($path);
} else {
unlink($path);
}
}

return rmdir($dir);
}

// 示例使用
$testDir = "recursive_test";
mkdir($testDir);
mkdir($testDir . "/sub1");
mkdir($testDir . "/sub2");
file_put_contents($testDir . "/file1.txt", "内容1");
file_put_contents($testDir . "/sub1/file2.txt", "内容2");

echo "递归删除目录:" . (removeDirectory($testDir) ? "成功" : "失败") . "\n";
?>

2. 遍历目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
<?php
// 创建测试目录结构
$baseDir = "directory_test";
mkdir($baseDir);
mkdir($baseDir . "/images");
mkdir($baseDir . "/documents");
mkdir($baseDir . "/images/thumbnails");

// 创建测试文件
$testFiles = [
$baseDir . "/readme.txt" => "说明文件",
$baseDir . "/config.php" => "<?php // 配置文件",
$baseDir . "/images/photo1.jpg" => "假装是图片内容",
$baseDir . "/images/photo2.png" => "假装是图片内容",
$baseDir . "/images/thumbnails/thumb1.jpg" => "缩略图",
$baseDir . "/documents/report.pdf" => "报告文件",
$baseDir . "/documents/manual.doc" => "手册文件"
];

foreach ($testFiles as $file => $content) {
file_put_contents($file, $content);
}

// 方法1:使用scandir
echo "使用scandir遍历目录:\n";
function listDirectory($dir, $level = 0) {
$indent = str_repeat(" ", $level);
$files = scandir($dir);

foreach ($files as $file) {
if ($file === '.' || $file === '..') {
continue;
}

$path = $dir . DIRECTORY_SEPARATOR . $file;

if (is_dir($path)) {
echo $indent . "[目录] $file/\n";
listDirectory($path, $level + 1);
} else {
$size = filesize($path);
echo $indent . "[文件] $file ($size 字节)\n";
}
}
}

listDirectory($baseDir);

// 方法2:使用DirectoryIterator
echo "\n使用DirectoryIterator遍历:\n";
function listWithDirectoryIterator($dir) {
try {
$iterator = new DirectoryIterator($dir);

foreach ($iterator as $fileInfo) {
if ($fileInfo->isDot()) {
continue;
}

$type = $fileInfo->isDir() ? "目录" : "文件";
$size = $fileInfo->isFile() ? " (" . $fileInfo->getSize() . " 字节)" : "";

echo "$type: " . $fileInfo->getFilename() . $size . "\n";
}
} catch (Exception $e) {
echo "遍历出错:" . $e->getMessage() . "\n";
}
}

listWithDirectoryIterator($baseDir);

// 方法3:使用RecursiveDirectoryIterator(递归遍历)
echo "\n递归遍历所有文件:\n";
function recursiveList($dir) {
try {
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir),
RecursiveIteratorIterator::SELF_FIRST
);

foreach ($iterator as $fileInfo) {
if ($fileInfo->isDot()) {
continue;
}

$level = $iterator->getDepth();
$indent = str_repeat(" ", $level);
$type = $fileInfo->isDir() ? "目录" : "文件";

echo $indent . "$type: " . $fileInfo->getFilename() . "\n";
}
} catch (Exception $e) {
echo "递归遍历出错:" . $e->getMessage() . "\n";
}
}

recursiveList($baseDir);

// 查找特定类型的文件
echo "\n查找图片文件:\n";
function findImageFiles($dir) {
$imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp'];
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir)
);

foreach ($iterator as $fileInfo) {
if ($fileInfo->isFile()) {
$extension = strtolower($fileInfo->getExtension());
if (in_array($extension, $imageExtensions)) {
echo "找到图片:" . $fileInfo->getPathname() . "\n";
}
}
}
}

findImageFiles($baseDir);

// 清理测试目录
removeDirectory($baseDir);
?>

文件上传处理

1. 基本文件上传

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
<?php
// 注意:这部分代码需要在Web环境中运行,并且需要HTML表单

// HTML表单示例(需要保存为单独的HTML文件)
$htmlForm = '
<!DOCTYPE html>
<html>
<head>
<title>文件上传示例</title>
</head>
<body>
<form action="upload.php" method="post" enctype="multipart/form-data">
<label for="file">选择文件:</label>
<input type="file" name="uploaded_file" id="file">
<input type="submit" value="上传文件" name="submit">
</form>
</body>
</html>
';

// 文件上传处理代码
function handleFileUpload() {
if (!isset($_POST['submit'])) {
return;
}

// 检查是否有文件上传
if (!isset($_FILES['uploaded_file']) || $_FILES['uploaded_file']['error'] !== UPLOAD_ERR_OK) {
echo "文件上传失败\n";
return;
}

$file = $_FILES['uploaded_file'];

// 获取文件信息
$fileName = $file['name'];
$fileSize = $file['size'];
$fileTmpName = $file['tmp_name'];
$fileType = $file['type'];
$fileError = $file['error'];

echo "文件信息:\n";
echo "文件名:$fileName\n";
echo "文件大小:$fileSize 字节\n";
echo "文件类型:$fileType\n";

// 验证文件
$maxSize = 5 * 1024 * 1024; // 5MB
$allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'text/plain'];

if ($fileSize > $maxSize) {
echo "文件太大,最大允许 5MB\n";
return;
}

if (!in_array($fileType, $allowedTypes)) {
echo "不允许的文件类型\n";
return;
}

// 生成安全的文件名
$fileExtension = pathinfo($fileName, PATHINFO_EXTENSION);
$safeFileName = uniqid() . '.' . $fileExtension;

// 设置上传目录
$uploadDir = 'uploads/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}

$uploadPath = $uploadDir . $safeFileName;

// 移动上传的文件
if (move_uploaded_file($fileTmpName, $uploadPath)) {
echo "文件上传成功:$uploadPath\n";
} else {
echo "文件移动失败\n";
}
}

// 文件上传错误处理
function getUploadErrorMessage($errorCode) {
$errors = [
UPLOAD_ERR_OK => '上传成功',
UPLOAD_ERR_INI_SIZE => '文件大小超过php.ini中upload_max_filesize的值',
UPLOAD_ERR_FORM_SIZE => '文件大小超过表单中MAX_FILE_SIZE的值',
UPLOAD_ERR_PARTIAL => '文件只有部分被上传',
UPLOAD_ERR_NO_FILE => '没有文件被上传',
UPLOAD_ERR_NO_TMP_DIR => '找不到临时文件夹',
UPLOAD_ERR_CANT_WRITE => '文件写入失败',
UPLOAD_ERR_EXTENSION => '文件上传被扩展程序阻止'
];

return $errors[$errorCode] ?? '未知错误';
}

// 多文件上传处理
function handleMultipleFileUpload() {
if (!isset($_FILES['multiple_files'])) {
return;
}

$files = $_FILES['multiple_files'];
$fileCount = count($files['name']);

for ($i = 0; $i < $fileCount; $i++) {
if ($files['error'][$i] === UPLOAD_ERR_OK) {
$fileName = $files['name'][$i];
$fileTmpName = $files['tmp_name'][$i];

// 处理每个文件...
echo "处理文件:$fileName\n";
}
}
}
?>

日志文件操作

1. 简单日志记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
<?php
class SimpleLogger {
private $logFile;
private $dateFormat;

public function __construct($logFile = 'app.log', $dateFormat = 'Y-m-d H:i:s') {
$this->logFile = $logFile;
$this->dateFormat = $dateFormat;
}

public function log($level, $message) {
$timestamp = date($this->dateFormat);
$logEntry = "[$timestamp] [$level] $message" . PHP_EOL;

// 使用文件锁防止并发写入问题
file_put_contents($this->logFile, $logEntry, FILE_APPEND | LOCK_EX);
}

public function info($message) {
$this->log('INFO', $message);
}

public function warning($message) {
$this->log('WARNING', $message);
}

public function error($message) {
$this->log('ERROR', $message);
}

public function debug($message) {
$this->log('DEBUG', $message);
}

public function readLogs($lines = 50) {
if (!file_exists($this->logFile)) {
return [];
}

$file = new SplFileObject($this->logFile);
$file->seek(PHP_INT_MAX);
$totalLines = $file->key();

$startLine = max(0, $totalLines - $lines);
$logs = [];

$file->seek($startLine);
while (!$file->eof()) {
$line = trim($file->current());
if (!empty($line)) {
$logs[] = $line;
}
$file->next();
}

return $logs;
}

public function clearLogs() {
if (file_exists($this->logFile)) {
file_put_contents($this->logFile, '');
}
}

public function rotateLogs($maxSize = 10485760) { // 10MB
if (!file_exists($this->logFile)) {
return;
}

if (filesize($this->logFile) > $maxSize) {
$backupFile = $this->logFile . '.' . date('Y-m-d-H-i-s');
rename($this->logFile, $backupFile);

// 压缩旧日志文件(如果支持gzip)
if (function_exists('gzencode')) {
$content = file_get_contents($backupFile);
file_put_contents($backupFile . '.gz', gzencode($content));
unlink($backupFile);
}
}
}
}

// 使用示例
$logger = new SimpleLogger('application.log');

$logger->info('应用程序启动');
$logger->debug('调试信息:用户ID = 123');
$logger->warning('警告:内存使用率较高');
$logger->error('错误:数据库连接失败');

// 读取最近的日志
echo "最近的日志记录:\n";
$recentLogs = $logger->readLogs(10);
foreach ($recentLogs as $log) {
echo $log . "\n";
}

// 日志轮转
$logger->rotateLogs(1024); // 1KB用于测试
?>

配置文件操作

1. INI配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
<?php
// 创建INI配置文件
$iniContent = '
; 数据库配置
[database]
host = localhost
port = 3306
username = root
password = secret
database = myapp

; 应用配置
[app]
name = "My Application"
version = "1.0.0"
debug = true
timezone = "Asia/Shanghai"

; 缓存配置
[cache]
driver = redis
host = 127.0.0.1
port = 6379
ttl = 3600
';

$configFile = 'config.ini';
file_put_contents($configFile, $iniContent);

// 读取INI配置文件
$config = parse_ini_file($configFile, true); // true表示处理节
print_r($config);

// 访问配置值
echo "数据库主机:" . $config['database']['host'] . "\n";
echo "应用名称:" . $config['app']['name'] . "\n";
echo "调试模式:" . ($config['app']['debug'] ? '开启' : '关闭') . "\n";

// 配置文件管理类
class ConfigManager {
private $configFile;
private $config;

public function __construct($configFile) {
$this->configFile = $configFile;
$this->loadConfig();
}

private function loadConfig() {
if (file_exists($this->configFile)) {
$this->config = parse_ini_file($this->configFile, true);
} else {
$this->config = [];
}
}

public function get($section, $key = null) {
if ($key === null) {
return $this->config[$section] ?? null;
}
return $this->config[$section][$key] ?? null;
}

public function set($section, $key, $value) {
$this->config[$section][$key] = $value;
}

public function save() {
$content = '';
foreach ($this->config as $section => $values) {
$content .= "[$section]\n";
foreach ($values as $key => $value) {
if (is_bool($value)) {
$value = $value ? 'true' : 'false';
} elseif (is_string($value) && strpos($value, ' ') !== false) {
$value = '"' . $value . '"';
}
$content .= "$key = $value\n";
}
$content .= "\n";
}

return file_put_contents($this->configFile, $content) !== false;
}
}

// 使用配置管理器
$configManager = new ConfigManager($configFile);
echo "应用版本:" . $configManager->get('app', 'version') . "\n";

$configManager->set('app', 'version', '1.0.1');
$configManager->save();

unlink($configFile);
?>

2. JSON配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php
// JSON配置文件操作
$jsonConfig = [
'database' => [
'host' => 'localhost',
'port' => 3306,
'username' => 'root',
'password' => 'secret'
],
'app' => [
'name' => 'My App',
'debug' => true,
'features' => ['auth', 'cache', 'logging']
]
];

$jsonFile = 'config.json';

// 写入JSON配置
$jsonContent = json_encode($jsonConfig, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
file_put_contents($jsonFile, $jsonContent);

// 读取JSON配置
$loadedConfig = json_decode(file_get_contents($jsonFile), true);
if (json_last_error() === JSON_ERROR_NONE) {
echo "JSON配置加载成功\n";
print_r($loadedConfig);
} else {
echo "JSON解析错误:" . json_last_error_msg() . "\n";
}

unlink($jsonFile);
?>

文件安全和最佳实践

1. 文件权限和安全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
<?php
// 安全的文件操作函数
class SecureFileHandler {
private $allowedExtensions;
private $maxFileSize;
private $uploadDir;

public function __construct($uploadDir = 'uploads/', $maxFileSize = 5242880) {
$this->uploadDir = rtrim($uploadDir, '/') . '/';
$this->maxFileSize = $maxFileSize; // 5MB
$this->allowedExtensions = ['txt', 'jpg', 'jpeg', 'png', 'gif', 'pdf'];

// 确保上传目录存在
if (!is_dir($this->uploadDir)) {
mkdir($this->uploadDir, 0755, true);
}
}

public function validateFile($filename, $filesize = null, $content = null) {
// 检查文件名
if (empty($filename) || strpos($filename, '..') !== false) {
throw new InvalidArgumentException('无效的文件名');
}

// 检查扩展名
$extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
if (!in_array($extension, $this->allowedExtensions)) {
throw new InvalidArgumentException('不允许的文件类型');
}

// 检查文件大小
if ($filesize !== null && $filesize > $this->maxFileSize) {
throw new InvalidArgumentException('文件太大');
}

// 检查文件内容(简单的恶意代码检测)
if ($content !== null) {
$dangerousPatterns = [
'/<\?php/i',
'/<script/i',
'/eval\s*\(/i',
'/exec\s*\(/i',
'/system\s*\(/i'
];

foreach ($dangerousPatterns as $pattern) {
if (preg_match($pattern, $content)) {
throw new InvalidArgumentException('文件包含危险内容');
}
}
}

return true;
}

public function secureWrite($filename, $content) {
try {
$this->validateFile($filename, strlen($content), $content);

// 生成安全的文件名
$safeFilename = $this->generateSafeFilename($filename);
$fullPath = $this->uploadDir . $safeFilename;

// 写入文件
$result = file_put_contents($fullPath, $content, LOCK_EX);

if ($result !== false) {
// 设置适当的文件权限
chmod($fullPath, 0644);
return $safeFilename;
}

throw new RuntimeException('文件写入失败');

} catch (Exception $e) {
throw new RuntimeException('安全写入失败:' . $e->getMessage());
}
}

public function secureRead($filename) {
$fullPath = $this->uploadDir . $filename;

// 检查文件是否在允许的目录内
$realPath = realpath($fullPath);
$realUploadDir = realpath($this->uploadDir);

if (!$realPath || strpos($realPath, $realUploadDir) !== 0) {
throw new InvalidArgumentException('不允许访问该文件');
}

if (!file_exists($fullPath)) {
throw new InvalidArgumentException('文件不存在');
}

return file_get_contents($fullPath);
}

private function generateSafeFilename($filename) {
$extension = pathinfo($filename, PATHINFO_EXTENSION);
$basename = pathinfo($filename, PATHINFO_FILENAME);

// 清理文件名
$basename = preg_replace('/[^a-zA-Z0-9_-]/', '_', $basename);
$basename = substr($basename, 0, 50); // 限制长度

// 添加时间戳避免冲突
return $basename . '_' . time() . '.' . $extension;
}
}

// 使用示例
try {
$fileHandler = new SecureFileHandler();

$testContent = "这是安全的文件内容\n测试数据";
$savedFile = $fileHandler->secureWrite('test.txt', $testContent);
echo "文件安全保存为:$savedFile\n";

$readContent = $fileHandler->secureRead($savedFile);
echo "读取的内容:\n$readContent\n";

// 清理测试文件
unlink('uploads/' . $savedFile);
rmdir('uploads');

} catch (Exception $e) {
echo "错误:" . $e->getMessage() . "\n";
}
?>

2. 文件锁定和并发处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
<?php
// 文件锁定示例
class FileLocker {
private $lockFile;
private $handle;

public function __construct($filename) {
$this->lockFile = $filename . '.lock';
}

public function lock($timeout = 10) {
$this->handle = fopen($this->lockFile, 'w');
if (!$this->handle) {
throw new RuntimeException('无法创建锁文件');
}

$startTime = time();
while (!flock($this->handle, LOCK_EX | LOCK_NB)) {
if (time() - $startTime > $timeout) {
fclose($this->handle);
throw new RuntimeException('获取文件锁超时');
}
usleep(100000); // 等待100ms
}

return true;
}

public function unlock() {
if ($this->handle) {
flock($this->handle, LOCK_UN);
fclose($this->handle);
unlink($this->lockFile);
$this->handle = null;
}
}

public function __destruct() {
$this->unlock();
}
}

// 安全的计数器示例
class SafeCounter {
private $counterFile;
private $locker;

public function __construct($counterFile = 'counter.txt') {
$this->counterFile = $counterFile;
$this->locker = new FileLocker($counterFile);

if (!file_exists($counterFile)) {
file_put_contents($counterFile, '0');
}
}

public function increment() {
try {
$this->locker->lock();

$current = (int)file_get_contents($this->counterFile);
$new = $current + 1;
file_put_contents($this->counterFile, $new);

$this->locker->unlock();

return $new;
} catch (Exception $e) {
$this->locker->unlock();
throw $e;
}
}

public function get() {
return (int)file_get_contents($this->counterFile);
}
}

// 测试并发安全
$counter = new SafeCounter();

echo "当前计数:" . $counter->get() . "\n";
echo "递增后:" . $counter->increment() . "\n";
echo "再次递增:" . $counter->increment() . "\n";

// 清理
unlink('counter.txt');
?>

常见错误和注意事项

1. 文件操作错误处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<?php
// 常见错误和解决方案
function demonstrateCommonErrors() {
echo "=== 常见文件操作错误 ===\n";

// 错误1:不检查文件是否存在
$filename = 'nonexistent.txt';

// 错误的做法
// $content = file_get_contents($filename); // 会产生警告

// 正确的做法
if (file_exists($filename)) {
$content = file_get_contents($filename);
} else {
echo "错误:文件 $filename 不存在\n";
}

// 错误2:不检查目录权限
$restrictedDir = '/root/test.txt'; // 假设没有权限

// 正确的做法:检查权限
$dir = dirname($restrictedDir);
if (!is_writable($dir)) {
echo "错误:目录 $dir 不可写\n";
}

// 错误3:忘记关闭文件句柄
$testFile = 'test_handle.txt';
file_put_contents($testFile, 'test content');

// 错误的做法
// $handle = fopen($testFile, 'r');
// $content = fread($handle, filesize($testFile));
// // 忘记 fclose($handle);

// 正确的做法
$handle = fopen($testFile, 'r');
if ($handle) {
$content = fread($handle, filesize($testFile));
fclose($handle); // 记得关闭
echo "正确读取文件内容\n";
}

// 错误4:不处理文件操作返回值
$result = file_put_contents('readonly.txt', 'content');
if ($result === false) {
echo "文件写入失败\n";
} else {
echo "成功写入 $result 字节\n";
}

// 清理
unlink($testFile);
if (file_exists('readonly.txt')) {
unlink('readonly.txt');
}
}

demonstrateCommonErrors();
?>

2. 内存使用优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<?php
// 处理大文件时的内存优化
function processLargeFileEfficiently($filename) {
if (!file_exists($filename)) {
// 创建大文件用于测试
$handle = fopen($filename, 'w');
for ($i = 0; $i < 100000; $i++) {
fwrite($handle, "这是第 $i 行数据,包含一些测试内容\n");
}
fclose($handle);
}

echo "文件大小:" . number_format(filesize($filename)) . " 字节\n";

// 错误的做法:一次性读取整个文件
// $content = file_get_contents($filename); // 可能导致内存不足

// 正确的做法:分块处理
$handle = fopen($filename, 'r');
if (!$handle) {
echo "无法打开文件\n";
return;
}

$chunkSize = 8192; // 8KB
$lineCount = 0;
$buffer = '';

while (!feof($handle)) {
$chunk = fread($handle, $chunkSize);
$buffer .= $chunk;

// 处理完整的行
while (($pos = strpos($buffer, "\n")) !== false) {
$line = substr($buffer, 0, $pos);
$buffer = substr($buffer, $pos + 1);

// 处理这一行
$lineCount++;

// 每1000行显示一次进度
if ($lineCount % 10000 === 0) {
echo "已处理 $lineCount 行\n";
}
}
}

// 处理最后的不完整行
if (!empty($buffer)) {
$lineCount++;
}

fclose($handle);
echo "总共处理了 $lineCount 行\n";

// 清理测试文件
unlink($filename);
}

processLargeFileEfficiently('large_test_file.txt');
?>

总结

PHP文件操作的关键要点:

  1. 基本操作

    • 熟练使用file_exists、is_file、is_dir等检查函数
    • 掌握文件路径操作函数
    • 理解文件权限和安全性
  2. 文件读写

    • 小文件使用file_get_contents/file_put_contents
    • 大文件使用文件句柄逐行处理
    • 合理选择读写模式
  3. 目录操作

    • 掌握目录创建、删除和遍历
    • 使用适当的迭代器处理目录结构
    • 实现递归目录操作
  4. 安全考虑

    • 验证文件名和路径
    • 检查文件类型和大小
    • 防止目录遍历攻击
    • 使用文件锁处理并发
  5. 性能优化

    • 分块处理大文件
    • 避免一次性加载大量数据
    • 合理使用缓冲区
  6. 错误处理

    • 检查函数返回值
    • 使用try-catch处理异常
    • 提供有意义的错误信息
  7. 最佳实践

    • 始终关闭文件句柄
    • 使用适当的文件权限
    • 实现日志轮转和配置管理
    • 编写可重用的文件操作类

文件操作是Web开发的基础技能,正确掌握这些知识对于构建稳定、安全的应用程序至关重要。记住,好的文件操作代码应该是安全的、高效的、易于维护的。

希望这篇文章能帮助你更好地掌握PHP文件操作技巧!在实际开发中,要根据具体需求选择合适的方法,并始终考虑安全性和性能。

本站由 提供部署服务