Skip to content

文件处理

概述

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);
?>

最佳实践和安全建议

文件操作最佳实践

  1. 始终检查文件操作的返回值
  2. 使用适当的文件锁定机制
  3. 验证文件路径和文件名
  4. 处理大文件时使用流式读取
  5. 设置适当的文件权限
  6. 使用异常处理机制

安全注意事项

  • 验证用户输入的文件名和路径
  • 限制文件上传的类型和大小
  • 使用白名单而不是黑名单进行文件类型验证
  • 将上传的文件存储在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

本站内容仅供学习和研究使用。