文件处理
概述
PHP提供了强大的文件处理功能,允许您读取、写入、创建和操作文件和目录。本章涵盖文件I/O操作、目录管理、文件上传、权限处理以及文件系统安全等重要主题。
基本文件操作
文件读取
php
<?php
// 方法1:读取整个文件内容
$content = file_get_contents('data.txt');
if ($content !== false) {
echo $content;
} else {
echo "无法读取文件";
}
// 方法2:使用fopen和fread
$file = fopen('data.txt', 'r');
if ($file) {
$content = fread($file, filesize('data.txt'));
fclose($file);
echo $content;
} else {
echo "无法打开文件";
}
// 方法3:逐行读取
$file = fopen('data.txt', 'r');
if ($file) {
while (($line = fgets($file)) !== false) {
echo "行: " . $line;
}
fclose($file);
}
// 方法4:读取为数组
$lines = file('data.txt', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $lineNumber => $line) {
echo "第" . ($lineNumber + 1) . "行: $line\n";
}
?>文件写入
php
<?php
// 方法1:写入整个内容(覆盖)
file_put_contents('output.txt', '这是新的文件内容');
// 方法2:追加内容
file_put_contents('output.txt', '\n追加的内容', FILE_APPEND | LOCK_EX);
// 方法3:使用fopen和fwrite
$file = fopen('output.txt', 'w');
if ($file) {
fwrite($file, '使用fwrite写入的内容');
fclose($file);
}
// 方法4:写入CSV数据
$data = [
['姓名', '年龄', '城市'],
['张三', 25, '北京'],
['李四', 30, '上海'],
['王五', 28, '广州']
];
$file = fopen('users.csv', 'w');
foreach ($data as $row) {
fputcsv($file, $row);
}
fclose($file);
// 方法5:安全写入(原子操作)
function safeFileWrite($filename, $content) {
$tempFile = $filename . '.tmp';
if (file_put_contents($tempFile, $content, LOCK_EX) !== false) {
return rename($tempFile, $filename);
}
return false;
}
safeFileWrite('important.txt', '重要数据');
?>文件信息和检查
php
<?php
// 检查文件是否存在
if (file_exists('data.txt')) {
echo "文件存在";
}
// 获取文件信息
$filename = 'data.txt';
if (file_exists($filename)) {
echo "文件大小: " . filesize($filename) . " 字节\n";
echo "最后修改时间: " . date('Y-m-d H:i:s', filemtime($filename)) . "\n";
echo "最后访问时间: " . date('Y-m-d H:i:s', fileatime($filename)) . "\n";
echo "是否可读: " . (is_readable($filename) ? '是' : '否') . "\n";
echo "是否可写: " . (is_writable($filename) ? '是' : '否') . "\n";
echo "是否为文件: " . (is_file($filename) ? '是' : '否') . "\n";
echo "是否为目录: " . (is_dir($filename) ? '是' : '否') . "\n";
}
// 获取文件详细信息
$stat = stat('data.txt');
print_r($stat);
// 获取文件扩展名和路径信息
$path = '/path/to/document.pdf';
echo "目录名: " . dirname($path) . "\n";
echo "文件名: " . basename($path) . "\n";
echo "扩展名: " . pathinfo($path, PATHINFO_EXTENSION) . "\n";
echo "不含扩展名的文件名: " . pathinfo($path, PATHINFO_FILENAME) . "\n";
?>目录操作
创建和删除目录
php
<?php
// 创建目录
if (!is_dir('uploads')) {
mkdir('uploads', 0755, true); // 递归创建,设置权限
echo "目录创建成功";
}
// 创建多级目录
mkdir('project/src/controllers', 0755, true);
// 删除目录(必须为空)
if (is_dir('temp') && rmdir('temp')) {
echo "目录删除成功";
}
// 递归删除目录和所有内容
function deleteDirectory($dir) {
if (!is_dir($dir)) {
return false;
}
$files = array_diff(scandir($dir), ['.', '..']);
foreach ($files as $file) {
$path = $dir . DIRECTORY_SEPARATOR . $file;
is_dir($path) ? deleteDirectory($path) : unlink($path);
}
return rmdir($dir);
}
deleteDirectory('old_project');
?>目录遍历
php
<?php
// 方法1:使用scandir
$files = scandir('.');
foreach ($files as $file) {
if ($file != '.' && $file != '..') {
echo $file . "\n";
}
}
// 方法2:使用glob
$phpFiles = glob('*.php');
foreach ($phpFiles as $file) {
echo "PHP文件: $file\n";
}
// 查找所有子目录中的PHP文件
$allPhpFiles = glob('**/*.php', GLOB_BRACE);
// 方法3:使用DirectoryIterator
$iterator = new DirectoryIterator('.');
foreach ($iterator as $fileInfo) {
if (!$fileInfo->isDot()) {
echo $fileInfo->getFilename() . " (" .
($fileInfo->isDir() ? '目录' : '文件') . ")\n";
}
}
// 方法4:递归遍历
$recursiveIterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator('.'),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($recursiveIterator as $file) {
echo str_repeat(' ', $recursiveIterator->getDepth()) .
$file->getFilename() . "\n";
}
?>文件上传处理
基本文件上传
php
<?php
// HTML表单示例
/*
<form action="upload.php" method="post" enctype="multipart/form-data">
<input type="file" name="uploadFile" required>
<input type="submit" value="上传文件">
</form>
*/
// 处理文件上传
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['uploadFile'])) {
$uploadDir = 'uploads/';
$uploadFile = $uploadDir . basename($_FILES['uploadFile']['name']);
// 基本验证
if ($_FILES['uploadFile']['error'] === UPLOAD_ERR_OK) {
if (move_uploaded_file($_FILES['uploadFile']['tmp_name'], $uploadFile)) {
echo "文件上传成功: " . $uploadFile;
} else {
echo "文件上传失败";
}
} else {
echo "上传错误代码: " . $_FILES['uploadFile']['error'];
}
}
?>高级文件上传处理
php
<?php
class FileUploader {
private $uploadDir;
private $maxFileSize;
private $allowedTypes;
private $allowedExtensions;
public function __construct($uploadDir = 'uploads/', $maxFileSize = 2097152) { // 2MB
$this->uploadDir = rtrim($uploadDir, '/') . '/';
$this->maxFileSize = $maxFileSize;
$this->allowedTypes = [
'image/jpeg',
'image/png',
'image/gif',
'application/pdf',
'text/plain'
];
$this->allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'txt'];
// 确保上传目录存在
if (!is_dir($this->uploadDir)) {
mkdir($this->uploadDir, 0755, true);
}
}
public function upload($fileInput) {
if (!isset($_FILES[$fileInput])) {
return ['success' => false, 'message' => '未选择文件'];
}
$file = $_FILES[$fileInput];
// 检查上传错误
if ($file['error'] !== UPLOAD_ERR_OK) {
return ['success' => false, 'message' => $this->getUploadErrorMessage($file['error'])];
}
// 验证文件大小
if ($file['size'] > $this->maxFileSize) {
return ['success' => false, 'message' => '文件大小超过限制'];
}
// 验证文件类型
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
if (!in_array($mimeType, $this->allowedTypes)) {
return ['success' => false, 'message' => '不支持的文件类型'];
}
// 验证文件扩展名
$extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($extension, $this->allowedExtensions)) {
return ['success' => false, 'message' => '不支持的文件扩展名'];
}
// 生成安全的文件名
$filename = $this->generateSafeFilename($file['name']);
$filepath = $this->uploadDir . $filename;
// 移动文件
if (move_uploaded_file($file['tmp_name'], $filepath)) {
return [
'success' => true,
'message' => '文件上传成功',
'filename' => $filename,
'filepath' => $filepath,
'size' => $file['size'],
'type' => $mimeType
];
} else {
return ['success' => false, 'message' => '文件移动失败'];
}
}
private function generateSafeFilename($originalName) {
$extension = pathinfo($originalName, PATHINFO_EXTENSION);
$basename = pathinfo($originalName, PATHINFO_FILENAME);
// 清理文件名
$basename = preg_replace('/[^a-zA-Z0-9_-]/', '_', $basename);
$basename = substr($basename, 0, 50); // 限制长度
// 添加时间戳避免重名
$timestamp = time();
return $basename . '_' . $timestamp . '.' . $extension;
}
private function getUploadErrorMessage($errorCode) {
switch ($errorCode) {
case UPLOAD_ERR_INI_SIZE:
return '文件大小超过php.ini中的upload_max_filesize设置';
case UPLOAD_ERR_FORM_SIZE:
return '文件大小超过表单中MAX_FILE_SIZE的设置';
case UPLOAD_ERR_PARTIAL:
return '文件只上传了一部分';
case UPLOAD_ERR_NO_FILE:
return '没有文件被上传';
case UPLOAD_ERR_NO_TMP_DIR:
return '缺少临时目录';
case UPLOAD_ERR_CANT_WRITE:
return '文件写入失败';
case UPLOAD_ERR_EXTENSION:
return '上传被PHP扩展阻止';
default:
return '未知上传错误';
}
}
}
// 使用示例
$uploader = new FileUploader('uploads/', 5242880); // 5MB
$result = $uploader->upload('uploadFile');
if ($result['success']) {
echo "上传成功: " . $result['filename'];
} else {
echo "上传失败: " . $result['message'];
}
?>文件权限和安全
文件权限管理
php
<?php
// 检查和设置文件权限
$file = 'config.txt';
// 获取当前权限
$perms = fileperms($file);
echo "当前权限: " . decoct($perms & 0777) . "\n";
// 设置权限
chmod($file, 0644); // 所有者读写,组和其他用户只读
chmod('script.sh', 0755); // 所有者读写执行,组和其他用户读执行
// 更改文件所有者(需要适当权限)
// chown($file, 'www-data');
// chgrp($file, 'www-data');
// 权限检查函数
function checkFilePermissions($file) {
if (!file_exists($file)) {
return "文件不存在";
}
$perms = fileperms($file);
$info = '';
// 文件类型
if (($perms & 0xC000) == 0xC000) {
$info = 's'; // Socket
} elseif (($perms & 0xA000) == 0xA000) {
$info = 'l'; // 符号链接
} elseif (($perms & 0x8000) == 0x8000) {
$info = '-'; // 普通文件
} elseif (($perms & 0x6000) == 0x6000) {
$info = 'b'; // 块设备
} elseif (($perms & 0x4000) == 0x4000) {
$info = 'd'; // 目录
} elseif (($perms & 0x2000) == 0x2000) {
$info = 'c'; // 字符设备
} elseif (($perms & 0x1000) == 0x1000) {
$info = 'p'; // FIFO管道
} else {
$info = 'u'; // 未知
}
// 权限
$info .= (($perms & 0x0100) ? 'r' : '-');
$info .= (($perms & 0x0080) ? 'w' : '-');
$info .= (($perms & 0x0040) ?
(($perms & 0x0800) ? 's' : 'x') :
(($perms & 0x0800) ? 'S' : '-'));
$info .= (($perms & 0x0020) ? 'r' : '-');
$info .= (($perms & 0x0010) ? 'w' : '-');
$info .= (($perms & 0x0008) ?
(($perms & 0x0400) ? 's' : 'x') :
(($perms & 0x0400) ? 'S' : '-'));
$info .= (($perms & 0x0004) ? 'r' : '-');
$info .= (($perms & 0x0002) ? 'w' : '-');
$info .= (($perms & 0x0001) ?
(($perms & 0x0200) ? 't' : 'x') :
(($perms & 0x0200) ? 'T' : '-'));
return $info;
}
echo checkFilePermissions('data.txt');
?>安全文件操作
php
<?php
// 安全路径验证
function isValidPath($path, $basePath = '.') {
$realPath = realpath($path);
$realBasePath = realpath($basePath);
// 检查路径是否在基础路径内
return $realPath !== false &&
$realBasePath !== false &&
strpos($realPath, $realBasePath) === 0;
}
// 安全文件名验证
function sanitizeFilename($filename) {
// 移除危险字符
$filename = preg_replace('/[^a-zA-Z0-9._-]/', '', $filename);
// 防止目录遍历攻击
$filename = str_replace(['../', '..\\', './'], '', $filename);
return $filename;
}
// 安全文件读取类
class SecureFileReader {
private $basePath;
private $allowedExtensions;
public function __construct($basePath, $allowedExtensions = ['txt', 'json', 'csv']) {
$this->basePath = realpath($basePath);
$this->allowedExtensions = $allowedExtensions;
}
public function readFile($filename) {
// 验证文件名
$filename = sanitizeFilename($filename);
// 构建完整路径
$fullPath = $this->basePath . DIRECTORY_SEPARATOR . $filename;
// 验证路径安全性
if (!isValidPath($fullPath, $this->basePath)) {
throw new Exception('无效的文件路径');
}
// 验证文件扩展名
$extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
if (!in_array($extension, $this->allowedExtensions)) {
throw new Exception('不允许的文件类型');
}
// 检查文件是否存在和可读
if (!file_exists($fullPath) || !is_readable($fullPath)) {
throw new Exception('文件不存在或不可读');
}
return file_get_contents($fullPath);
}
}
// 使用示例
try {
$reader = new SecureFileReader('/safe/data/directory');
$content = $reader->readFile('user_data.txt');
echo $content;
} catch (Exception $e) {
echo "错误: " . $e->getMessage();
}
?>文件锁定和并发控制
文件锁定基础
php
<?php
// 排他锁(写锁)
$file = fopen('counter.txt', 'r+');
if (flock($file, LOCK_EX)) { // 独占锁
$count = (int)fread($file, filesize('counter.txt'));
$count++;
ftruncate($file, 0);
rewind($file);
fwrite($file, $count);
flock($file, LOCK_UN); // 释放锁
} else {
echo "无法获取文件锁";
}
fclose($file);
// 共享锁(读锁)
$file = fopen('log.txt', 'r');
if (flock($file, LOCK_SH)) { // 共享锁
$content = fread($file, filesize('log.txt'));
echo $content;
flock($file, LOCK_UN);
}
fclose($file);
// 非阻塞锁
$file = fopen('data.txt', 'w');
if (flock($file, LOCK_EX | LOCK_NB)) { // 非阻塞独占锁
fwrite($file, '写入数据');
flock($file, LOCK_UN);
echo "成功获取锁并写入数据";
} else {
echo "文件正被其他进程使用";
}
fclose($file);
?>最佳实践和安全建议
文件操作最佳实践
- 始终检查文件操作的返回值
- 使用适当的文件锁定机制
- 验证文件路径和文件名
- 处理大文件时使用流式读取
- 设置适当的文件权限
- 使用异常处理机制
安全注意事项
- 验证用户输入的文件名和路径
- 限制文件上传的类型和大小
- 使用白名单而不是黑名单进行文件类型验证
- 将上传的文件存储在Web根目录之外
- 定期清理临时文件
性能优化
- 对于大文件使用流式处理
- 合理使用文件缓存
- 避免频繁的文件开关操作
- 使用适当的缓冲区大小
总结
本章介绍了PHP中的文件处理技术,包括:
- 基本文件读写操作
- 目录管理和遍历
- 安全的文件上传处理
- 文件权限和安全控制
- 文件锁定和并发处理
- 大文件的流式处理
掌握这些技能将使您能够构建安全、高效的文件处理应用程序。在下一章中,我们将学习PHP中的继承和Trait特性。 if (($perms & 0xC000) == 0xC000) { $info = 's'; // Socket } elseif (($perms & 0xA000) == 0xA000) { $info = 'l'; // 符号链接 } elseif (($perms & 0x8000) == 0x8000) { $info = '-'; // 普通文件 } elseif (($perms & 0x6000) == 0x6000) { $info = 'b'; // 块设备 } elseif (($perms & 0x4000) == 0x4000) { $info = 'd'; // 目录 } elseif (($perms & 0x2000) == 0x2000) { $info = 'c'; // 字符设备 } elseif (($perms & 0x1000) == 0x1000) { $info = 'p'; // FIFO管道 } else { $info = 'u'; // 未知 }
// 权限
$info .= (($perms & 0x0100) ? 'r' : '-');
$info .= (($perms & 0x0080) ? 'w' : '-');
$info .= (($perms & 0x0040) ?
(($perms & 0x0800) ? 's' : 'x') :
(($perms & 0x0800) ? 'S' : '-'));
$info .= (($perms & 0x0020) ? 'r' : '-');
$info .= (($perms & 0x0010) ? 'w' : '-');
$info .= (($perms & 0x0008) ?
(($perms & 0x0400) ? 's' : 'x') :
(($perms & 0x0400) ? 'S' : '-'));
$info .= (($perms & 0x0004) ? 'r' : '-');
$info .= (($perms & 0x0002) ? 'w' : '-');
$info .= (($perms & 0x0001) ?
(($perms & 0x0200) ? 't' : 'x') :
(($perms & 0x0200) ? 'T' : '-'));
return $info;
}
echo checkFilePermissions('data.txt'); ?>
### 安全文件操作
```php
<?php
// 安全路径验证
function isValidPath($path, $basePath = '.') {
$realPath = realpath($path);
$realBasePath = realpath($basePath);
// 检查路径是否在基础路径内
return $realPath !== false &&
$realBasePath !== false &&
strpos($realPath, $realBasePath) === 0;
}
// 安全文件名验证
function sanitizeFilename($filename) {
// 移除危险字符
$filename = preg_replace('/[^a-zA-Z0-9._-]/', '', $filename);
// 防止目录遍历攻击
$filename = str_replace(['../', '..\\', './'], '', $filename);
return $filename;
}
// 安全文件读取类
class SecureFileReader {
private $basePath;
private $allowedExtensions;
public function __construct($basePath, $allowedExtensions = ['txt', 'json', 'csv']) {
$this->basePath = realpath($basePath);
$this->allowedExtensions = $allowedExtensions;
}
public function readFile($filename) {
// 验证文件名
$filename = sanitizeFilename($filename);
// 构建完整路径
$fullPath = $this->basePath . DIRECTORY_SEPARATOR . $filename;
// 验证路径安全性
if (!isValidPath($fullPath, $this->basePath)) {
throw new Exception('无效的文件路径');
}
// 验证文件扩展名
$extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
if (!in_array($extension, $this->allowedExtensions)) {
throw new Exception('不允许的文件类型');
}
// 检查文件是否存在和可读
if (!file_exists($fullPath) || !is_readable($fullPath)) {
throw new Exception('文件不存在或不可读');
}
return file_get_contents($fullPath);
}
}
// 使用示例
try {
$reader = new SecureFileReader('/safe/data/directory');
$content = $reader->readFile('user_data.txt');
echo $content;
} catch (Exception $e) {
echo "错误: " . $e->getMessage();
}
?>php
<?php
$filename = 'test.txt';
// 检查文件是否存在
if (file_exists($filename)) {
echo "文件存在\n";
// 获取文件信息
echo "文件大小: " . filesize($filename) . " 字节\n";
echo "最后修改时间: " . date('Y-m-d H:i:s', filemtime($filename)) . "\n";
echo "最后访问时间: " . date('Y-m-d H:i:s', fileatime($filename)) . "\n";
echo "创建时间: " . date('Y-m-d H:i:s', filectime($filename)) . "\n";
// 检查文件类型
if (is_file($filename)) {
echo "这是一个普通文件\n";
}
if (is_dir($filename)) {
echo "这是一个目录\n";
}
if (is_link($filename)) {
echo "这是一个符号链接\n";
}
// 检查权限
if (is_readable($filename)) {
echo "文件可读\n";
}
if (is_writable($filename)) {
echo "文件可写\n";
}
if (is_executable($filename)) {
echo "文件可执行\n";
}
} else {
echo "文件不存在\n";
}
// 获取文件的详细信息
$fileInfo = stat($filename);
print_r($fileInfo);
// 获取文件的MIME类型
if (function_exists('mime_content_type')) {
echo "MIME类型: " . mime_content_type($filename) . "\n";
}
// 使用pathinfo获取文件路径信息
$pathInfo = pathinfo('/path/to/file.txt');
echo "目录: " . $pathInfo['dirname'] . "\n";
echo "文件名: " . $pathInfo['basename'] . "\n";
echo "扩展名: " . $pathInfo['extension'] . "\n";
echo "不含扩展名的文件名: " . $pathInfo['filename'] . "\n";
?>目录操作
创建和删除目录
php
<?php
// 创建目录
mkdir('new_directory', 0755, true);
// 删除目录
rmdir('old_directory');
// 列出目录内容
$files = scandir('.');
foreach ($files as $file) {
if ($file != '.' && $file != '..') {
echo $file . "\n";
}
}
?>文件上传处理
HTML表单
html
<form action="upload.php" method="post" enctype="multipart/form-data">
<input type="file" name="uploadfile">
<input type="submit" value="上传文件">
</form>PHP处理上传
php
<?php
if ($_FILES['uploadfile']['error'] == UPLOAD_ERR_OK) {
$uploadDir = 'uploads/';
$uploadFile = $uploadDir . basename($_FILES['uploadfile']['name']);
if (move_uploaded_file($_FILES['uploadfile']['tmp_name'], $uploadFile)) {
echo '文件上传成功';
} else {
echo '文件上传失败';
}
}
?>继续学习:下一章 - 继承和Trait