第9章:文件操作
文件操作是PHP编程中的重要组成部分,它允许我们读取、写入、修改和管理服务器上的文件。通过文件操作,你可以创建动态内容管理系统、文件上传功能、日志记录系统,以及各种数据处理应用。
学习目标
完成本章学习后,你将能够:
- 理解PHP文件系统的基本概念
- 掌握文件的读取、写入和修改操作
- 学会文件上传和下载的实现方法
- 了解目录管理和文件权限控制
- 掌握文件处理的安全最佳实践
- 能够构建完整的文件管理系统
文件系统基础
什么是文件系统?
文件系统是操作系统用于管理存储设备上数据的机制。在PHP中,我们可以通过内置函数与文件系统进行交互,执行各种文件和目录操作。
路径的概念
在文件操作中,路径是指定文件位置的字符串:
<?php
// 绝对路径 - 从根目录开始的完整路径
$absolutePath = '/var/www/html/index.php';
$windowsAbsolutePath = 'C:\xampp\htdocs\index.php';
// 相对路径 - 相对于当前脚本所在目录的路径
$relativePath = '../config/database.php';
$relativePath2 = 'includes/functions.php';
// 当前工作目录
echo "当前工作目录:" . getcwd() . "<br>";
// 获取脚本的绝对路径
echo "脚本路径:" . __FILE__ . "<br>";
echo "脚本目录:" . __DIR__ . "<br>";
?>
路径分隔符
不同操作系统的路径分隔符不同:
<?php
// 使用DIRECTORY_SEPARATOR获取系统分隔符
echo "路径分隔符:" . DIRECTORY_SEPARATOR . "<br>";
// 跨平台路径拼接
$path = 'uploads' . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR . 'photo.jpg';
echo "跨平台路径:" . $path . "<br>";
// 使用函数处理路径
$fullPath = 'uploads/images/photo.jpg';
$dirname = dirname($fullPath); // 获取目录名
$basename = basename($fullPath); // 获取文件名
$filename = pathinfo($fullPath, PATHINFO_FILENAME); // 获取不含扩展名的文件名
$extension = pathinfo($fullPath, PATHINFO_EXTENSION); // 获取扩展名
echo "目录名:" . $dirname . "<br>";
echo "文件名:" . $basename . "<br>";
echo "文件名(无扩展名):" . $filename . "<br>";
echo "扩展名:" . $extension . "<br>";
?>
文件读取操作
1. 检查文件是否存在
<?php
// 检查文件或目录是否存在
$filename = 'example.txt';
if (file_exists($filename)) {
echo "文件存在<br>";
} else {
echo "文件不存在<br>";
}
// 检查是否为文件
if (is_file($filename)) {
echo "这是一个文件<br>";
}
// 检查是否为目录
if (is_dir('uploads')) {
echo "这是一个目录<br>";
}
// 检查文件是否可读/可写/可执行
if (is_readable($filename)) {
echo "文件可读<br>";
}
if (is_writable($filename)) {
echo "文件可写<br>";
}
if (is_executable($filename)) {
echo "文件可执行<br>";
}
?>
2. 获取文件信息
<?php
// 文件信息示例类
class FileInfo {
private $filename;
public function __construct($filename) {
$this->filename = $filename;
}
// 获取文件大小
public function getSize() {
if (file_exists($this->filename)) {
$bytes = filesize($this->filename);
return $this->formatBytes($bytes);
}
return "文件不存在";
}
// 获取文件修改时间
public function getModifiedTime() {
if (file_exists($this->filename)) {
return date('Y-m-d H:i:s', filemtime($this->filename));
}
return "文件不存在";
}
// 获取文件创建时间
public function getCreatedTime() {
if (file_exists($this->filename)) {
return date('Y-m-d H:i:s', filectime($this->filename));
}
return "文件不存在";
}
// 获取文件最后访问时间
public function getAccessedTime() {
if (file_exists($this->filename)) {
return date('Y-m-d H:i:s', fileatime($this->filename));
}
return "文件不存在";
}
// 获取文件类型
public function getType() {
if (file_exists($this->filename)) {
return filetype($this->filename);
}
return "文件不存在";
}
// 获取文件权限
public function getPermissions() {
if (file_exists($this->filename)) {
return substr(sprintf('%o', fileperms($this->filename)), -4);
}
return "文件不存在";
}
// 获取文件MIME类型
public function getMimeType() {
if (file_exists($this->filename)) {
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $this->filename);
finfo_close($finfo);
return $mimeType;
}
return "文件不存在";
}
// 格式化字节大小
private function formatBytes($bytes, $precision = 2) {
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
for ($i = 0; $bytes > 1024 && $i < count($units) - 1; $i++) {
$bytes /= 1024;
}
return round($bytes, $precision) . ' ' . $units[$i];
}
// 显示所有文件信息
public function displayAllInfo() {
echo "<h3>文件信息:{$this->filename}</h3>";
echo "<table border='1'>";
echo "<tr><td>文件大小</td><td>" . $this->getSize() . "</td></tr>";
echo "<tr><td>修改时间</td><td>" . $this->getModifiedTime() . "</td></tr>";
echo "<tr><td>创建时间</td><td>" . $this->getCreatedTime() . "</td></tr>";
echo "<tr><td>访问时间</td><td>" . $this->getAccessedTime() . "</td></tr>";
echo "<tr><td>文件类型</td><td>" . $this->getType() . "</td></tr>";
echo "<tr><td>权限</td><td>" . $this->getPermissions() . "</td></tr>";
echo "<tr><td>MIME类型</td><td>" . $this->getMimeType() . "</td></tr>";
echo "</table>";
}
}
// 使用示例
$fileInfo = new FileInfo('example.txt');
$fileInfo->displayAllInfo();
?>
3. 读取文件内容
<?php
// 文件读取示例类
class FileReader {
private $filename;
public function __construct($filename) {
$this->filename = $filename;
}
// 检查文件是否可读
private function isReadable() {
return file_exists($this->filename) && is_readable($this->filename);
}
// 使用file_get_contents读取整个文件
public function readAll() {
if (!$this->isReadable()) {
throw new Exception("文件不可读或不存在");
}
$content = file_get_contents($this->filename);
if ($content === false) {
throw new Exception("读取文件失败");
}
return $content;
}
// 使用file()函数逐行读取
public function readLines() {
if (!$this->isReadable()) {
throw new Exception("文件不可读或不存在");
}
$lines = file($this->filename, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
if ($lines === false) {
throw new Exception("读取文件失败");
}
return $lines;
}
// 使用fopen和fgets逐行读取(适用于大文件)
public function readLineByLine() {
if (!$this->isReadable()) {
throw new Exception("文件不可读或不存在");
}
$handle = fopen($this->filename, 'r');
if (!$handle) {
throw new Exception("无法打开文件");
}
$lines = [];
try {
while (($line = fgets($handle)) !== false) {
$lines[] = trim($line);
}
} finally {
fclose($handle);
}
return $lines;
}
// 按字符读取文件
public function readByCharacter($maxChars = 100) {
if (!$this->isReadable()) {
throw new Exception("文件不可读或不存在");
}
$handle = fopen($this->filename, 'r');
if (!$handle) {
throw new Exception("无法打开文件");
}
$content = '';
$charCount = 0;
try {
while (($char = fgetc($handle)) !== false && $charCount < $maxChars) {
$content .= $char;
$charCount++;
}
} finally {
fclose($handle);
}
return $content;
}
// 读取特定范围的行
public function readLinesRange($start, $end) {
$lines = $this->readLines();
return array_slice($lines, $start - 1, $end - $start + 1);
}
// 搜索文件内容
public function searchContent($searchTerm) {
$lines = $this->readLines();
$results = [];
foreach ($lines as $lineNumber => $line) {
if (stripos($line, $searchTerm) !== false) {
$results[] = [
'line_number' => $lineNumber + 1,
'content' => $line
];
}
}
return $results;
}
// 显示文件内容(带行号)
public function displayWithLineNumbers() {
$lines = $this->readLines();
echo "<h3>文件内容:{$this->filename}</h3>";
echo "<pre style='background-color: #f5f5f5; padding: 10px; font-family: monospace;'>";
foreach ($lines as $lineNumber => $line) {
$lineNum = str_pad($lineNumber + 1, 4, ' ', STR_PAD_LEFT);
echo "{$lineNum}: " . htmlspecialchars($line) . "\n";
}
echo "</pre>";
}
// 获取文件统计信息
public function getStats() {
if (!$this->isReadable()) {
return null;
}
$lines = $this->readLines();
$content = file_get_contents($this->filename);
return [
'total_lines' => count($lines),
'total_characters' => strlen($content),
'total_words' => str_word_count($content),
'file_size' => filesize($this->filename)
];
}
}
// 使用示例
try {
$reader = new FileReader('example.txt');
// 读取所有内容
echo "<h3>读取所有内容:</h3>";
echo "<pre>" . htmlspecialchars($reader->readAll()) . "</pre>";
// 按行读取
echo "<h3>按行读取:</h3>";
$lines = $reader->readLines();
echo "<ol>";
foreach ($lines as $line) {
echo "<li>" . htmlspecialchars($line) . "</li>";
}
echo "</ol>";
// 显示统计信息
echo "<h3>文件统计:</h3>";
$stats = $reader->getStats();
echo "<ul>";
echo "<li>总行数:" . $stats['total_lines'] . "</li>";
echo "<li>总字符数:" . $stats['total_characters'] . "</li>";
echo "<li>总词数:" . $stats['total_words'] . "</li>";
echo "<li>文件大小:" . $stats['file_size'] . " 字节</li>";
echo "</ul>";
// 搜索内容
echo "<h3>搜索示例:</h3>";
$results = $reader->searchContent('function');
if (!empty($results)) {
foreach ($results as $result) {
echo "第{$result['line_number']}行:" . htmlspecialchars($result['content']) . "<br>";
}
}
} catch (Exception $e) {
echo "错误:" . $e->getMessage();
}
?>
4. 配置文件读取
<?php
// 配置文件读取类
class ConfigReader {
private $configFile;
private $config = [];
public function __construct($configFile) {
$this->configFile = $configFile;
$this->loadConfig();
}
// 加载配置文件
private function loadConfig() {
if (!file_exists($this->configFile)) {
throw new Exception("配置文件不存在:{$this->configFile}");
}
$extension = pathinfo($this->configFile, PATHINFO_EXTENSION);
switch ($extension) {
case 'json':
$this->loadJsonConfig();
break;
case 'ini':
$this->loadIniConfig();
break;
case 'php':
$this->loadPhpConfig();
break;
default:
throw new Exception("不支持的配置文件格式:{$extension}");
}
}
// 加载JSON配置文件
private function loadJsonConfig() {
$content = file_get_contents($this->configFile);
$this->config = json_decode($content, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new Exception("JSON解析错误:" . json_last_error_msg());
}
}
// 加载INI配置文件
private function loadIniConfig() {
$this->config = parse_ini_file($this->configFile, true);
}
// 加载PHP配置文件
private function loadPhpConfig() {
$this->config = include $this->configFile;
}
// 获取配置值
public function get($key, $default = null) {
$keys = explode('.', $key);
$value = $this->config;
foreach ($keys as $k) {
if (!isset($value[$k])) {
return $default;
}
$value = $value[$k];
}
return $value;
}
// 设置配置值
public function set($key, $value) {
$keys = explode('.', $key);
$config = &$this->config;
foreach ($keys as $k) {
if (!isset($config[$k])) {
$config[$k] = [];
}
$config = &$config[$k];
}
$config = $value;
}
// 获取所有配置
public function getAll() {
return $this->config;
}
// 保存配置
public function save() {
$extension = pathinfo($this->configFile, PATHINFO_EXTENSION);
switch ($extension) {
case 'json':
return $this->saveJsonConfig();
case 'ini':
return $this->saveIniConfig();
case 'php':
return $this->savePhpConfig();
default:
throw new Exception("不支持的配置文件格式:{$extension}");
}
}
// 保存JSON配置
private function saveJsonConfig() {
$content = json_encode($this->config, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
return file_put_contents($this->configFile, $content) !== false;
}
// 保存INI配置
private function saveIniConfig() {
$content = $this->arrayToIni($this->config);
return file_put_contents($this->configFile, $content) !== false;
}
// 保存PHP配置
private function savePhpConfig() {
$content = "<?php\nreturn " . var_export($this->config, true) . ";\n";
return file_put_contents($this->configFile, $content) !== false;
}
// 数组转INI格式
private function arrayToIni($array, $sections = []) {
$content = '';
foreach ($array as $key => $value) {
if (is_array($value)) {
if (!empty($sections)) {
$content .= "\n[{$key}]\n";
}
$content .= $this->arrayToIni($value, array_merge($sections, [$key]));
} else {
$content .= "{$key} = " . (is_bool($value) ? ($value ? 'true' : 'false') : "\"{$value}\"") . "\n";
}
}
return $content;
}
}
// 使用示例
try {
// 读取JSON配置
$configReader = new ConfigReader('config.json');
echo "<h3>配置值示例:</h3>";
echo "数据库主机:" . $configReader->get('database.host', 'localhost') . "<br>";
echo "数据库端口:" . $configReader->get('database.port', 3306) . "<br>";
echo "应用名称:" . $configReader->get('app.name', 'MyApp') . "<br>";
// 修改配置
$configReader->set('app.debug', true);
$configReader->set('app.version', '1.2.3');
// 保存配置
if ($configReader->save()) {
echo "配置已保存<br>";
}
} catch (Exception $e) {
echo "配置错误:" . $e->getMessage();
}
?>
文件写入操作
1. 基本文件写入
<?php
// 文件写入示例类
class FileWriter {
private $filename;
private $backup = true;
public function __construct($filename, $backup = true) {
$this->filename = $filename;
$this->backup = $backup;
}
// 检查目录是否存在,不存在则创建
private function ensureDirectory() {
$directory = dirname($this->filename);
if (!is_dir($directory)) {
if (!mkdir($directory, 0755, true)) {
throw new Exception("无法创建目录:{$directory}");
}
}
}
// 创建备份
private function createBackup() {
if ($this->backup && file_exists($this->filename)) {
$backupFile = $this->filename . '.backup.' . date('YmdHis');
if (!copy($this->filename, $backupFile)) {
throw new Exception("创建备份失败");
}
}
}
// 写入整个文件
public function write($content, $mode = 'w') {
$this->ensureDirectory();
$this->createBackup();
$result = file_put_contents($this->filename, $content, $mode === 'a' ? FILE_APPEND : 0);
if ($result === false) {
throw new Exception("写入文件失败");
}
return $result;
}
// 追加内容
public function append($content) {
return $this->write($content, 'a');
}
// 写入单行
public function writeLine($line, $mode = 'w') {
return $this->write($line . PHP_EOL, $mode);
}
// 追加单行
public function appendLine($line) {
return $this->writeLine($line, 'a');
}
// 写入数组(每行一个元素)
public function writeLines(array $lines, $mode = 'w') {
$content = implode(PHP_EOL, $lines) . PHP_EOL;
return $this->write($content, $mode);
}
// 写入JSON数据
public function writeJson($data, $pretty = true) {
$options = JSON_UNESCAPED_UNICODE;
if ($pretty) {
$options |= JSON_PRETTY_PRINT;
}
$content = json_encode($data, $options);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new Exception("JSON编码错误:" . json_last_error_msg());
}
return $this->write($content);
}
// 写入CSV数据
public function writeCsv(array $data, $headers = null) {
$this->ensureDirectory();
$this->createBackup();
$handle = fopen($this->filename, 'w');
if (!$handle) {
throw new Exception("无法打开文件写入");
}
try {
// 写入表头
if ($headers !== null) {
fputcsv($handle, $headers);
}
// 写入数据
foreach ($data as $row) {
if (!is_array($row)) {
$row = [$row];
}
fputcsv($handle, $row);
}
} finally {
fclose($handle);
}
return true;
}
// 清空文件
public function clear() {
$this->ensureDirectory();
return $this->write('');
}
// 获取写入的字节数
public function getBytesWritten() {
return filesize($this->filename);
}
}
// 使用示例
try {
$writer = new FileWriter('output/test.txt');
// 写入文本内容
$writer->write('Hello, World!');
$writer->appendLine('This is a new line.');
$writer->appendLine('Another line of text.');
// 写入数组
$lines = [
'Line 1: First line',
'Line 2: Second line',
'Line 3: Third line'
];
$writer->writeLines($lines);
// 写入JSON数据
$data = [
'name' => 'John Doe',
'age' => 30,
'email' => 'john@example.com',
'hobbies' => ['reading', 'swimming', 'coding']
];
$jsonWriter = new FileWriter('output/data.json');
$jsonWriter->writeJson($data);
// 写入CSV数据
$csvData = [
['John', 'Doe', 30, 'john@example.com'],
['Jane', 'Smith', 25, 'jane@example.com'],
['Bob', 'Johnson', 35, 'bob@example.com']
];
$csvHeaders = ['First Name', 'Last Name', 'Age', 'Email'];
$csvWriter = new FileWriter('output/users.csv');
$csvWriter->writeCsv($csvData, $csvHeaders);
echo "所有文件写入完成!<br>";
} catch (Exception $e) {
echo "文件写入错误:" . $e->getMessage();
}
?>
2. 文件锁定机制
<?php
// 文件锁定示例类
class FileLock {
private $filename;
private $handle;
private $lockType;
public function __construct($filename) {
$this->filename = $filename;
}
// 打开文件并获取锁
public function openWithLock($mode, $lockType = LOCK_EX) {
$this->handle = fopen($this->filename, $mode);
if (!$this->handle) {
throw new Exception("无法打开文件");
}
// 尝试获取锁
if (flock($this->handle, $lockType)) {
$this->lockType = $lockType;
return true;
} else {
fclose($this->handle);
throw new Exception("无法获取文件锁");
}
}
// 非阻塞锁获取
public function openWithLockNonBlocking($mode, $lockType = LOCK_EX | LOCK_NB) {
$this->handle = fopen($this->filename, $mode);
if (!$this->handle) {
throw new Exception("无法打开文件");
}
if (flock($this->handle, $lockType)) {
$this->lockType = $lockType;
return true;
} else {
fclose($this->handle);
$this->handle = null;
return false;
}
}
// 写入数据
public function write($data) {
if (!$this->handle) {
throw new Exception("文件未打开或未获取锁");
}
return fwrite($this->handle, $data);
}
// 读取数据
public function read($length = 1024) {
if (!$this->handle) {
throw new Exception("文件未打开或未获取锁");
}
return fread($this->handle, $length);
}
// 读取所有内容
public function readAll() {
if (!$this->handle) {
throw new Exception("文件未打开或未获取锁");
}
return stream_get_contents($this->handle);
}
// 释放锁并关闭文件
public function close() {
if ($this->handle) {
flock($this->handle, LOCK_UN); // 释放锁
fclose($this->handle);
$this->handle = null;
$this->lockType = null;
}
}
// 析构函数,确保锁被释放
public function __destruct() {
$this->close();
}
}
// 多进程文件写入示例
function multiProcessWriteExample() {
$filename = 'output/multi_process.txt';
// 模拟多个进程同时写入
for ($i = 1; $i <= 5; $i++) {
$pid = pcntl_fork(); // 需要pcntl扩展
if ($pid == -1) {
// Fork失败,使用单进程模拟
singleProcessWrite($filename, $i);
} elseif ($pid == 0) {
// 子进程
singleProcessWrite($filename, $i);
exit(0);
}
// 父进程继续
}
}
// 单进程写入函数
function singleProcessWrite($filename, $processId) {
$fileLock = new FileLock($filename);
try {
$fileLock->openWithLock('a');
$timestamp = date('Y-m-d H:i:s');
$message = "进程 {$processId} 在 {$timestamp} 写入了数据\n";
// 模拟一些处理时间
usleep(rand(100000, 500000)); // 0.1-0.5秒
$fileLock->write($message);
$fileLock->close();
echo "进程 {$processId} 写入完成<br>";
} catch (Exception $e) {
echo "进程 {$processId} 错误:" . $e->getMessage() . "<br>";
}
}
// 访问计数器示例
class AccessCounter {
private $filename;
private $fileLock;
public function __construct($filename) {
$this->filename = $filename;
$this->fileLock = new FileLock($filename);
// 初始化计数器文件
if (!file_exists($filename)) {
file_put_contents($filename, '0');
}
}
// 增加访问次数
public function increment() {
try {
// 打开文件并获取独占锁
$this->fileLock->openWithLock('r+');
// 读取当前计数
$content = $this->fileLock->readAll();
$count = intval(trim($content));
// 增加计数
$count++;
// 回到文件开头
rewind($this->fileLock->handle);
// 写入新计数
$this->fileLock->write($count);
// 释放锁
$this->fileLock->close();
return $count;
} catch (Exception $e) {
throw new Exception("更新计数器失败:" . $e->getMessage());
}
}
// 获取当前计数
public function getCount() {
$content = file_get_contents($this->filename);
return intval(trim($content));
}
// 重置计数器
public function reset() {
try {
$this->fileLock->openWithLock('w');
$this->fileLock->write('0');
$this->fileLock->close();
} catch (Exception $e) {
throw new Exception("重置计数器失败:" . $e->getMessage());
}
}
}
// 使用示例
try {
$counter = new AccessCounter('output/access_counter.txt');
// 增加访问次数
for ($i = 0; $i < 10; $i++) {
$count = $counter->increment();
echo "访问次数:{$count}<br>";
usleep(100000); // 0.1秒延迟
}
echo "最终访问次数:" . $counter->getCount() . "<br>";
} catch (Exception $e) {
echo "计数器错误:" . $e->getMessage();
}
?>
目录操作
1. 目录创建和管理
<?php
// 目录管理类
class DirectoryManager {
private $basePath;
public function __construct($basePath = '.') {
$this->basePath = rtrim($basePath, '/\\');
}
// 创建目录
public function createDirectory($path, $permissions = 0755) {
$fullPath = $this->getFullPath($path);
if (is_dir($fullPath)) {
return true;
}
if (!mkdir($fullPath, $permissions, true)) {
throw new Exception("无法创建目录:{$fullPath}");
}
return true;
}
// 删除目录及其内容
public function deleteDirectory($path, $recursive = false) {
$fullPath = $this->getFullPath($path);
if (!is_dir($fullPath)) {
return false;
}
if ($recursive) {
return $this->deleteRecursive($fullPath);
} else {
return rmdir($fullPath);
}
}
// 递归删除目录
private function deleteRecursive($dir) {
$files = array_diff(scandir($dir), ['.', '..']);
foreach ($files as $file) {
$path = $dir . DIRECTORY_SEPARATOR . $file;
if (is_dir($path)) {
$this->deleteRecursive($path);
} else {
unlink($path);
}
}
return rmdir($dir);
}
// 复制目录
public function copyDirectory($source, $destination) {
$sourcePath = $this->getFullPath($source);
$destPath = $this->getFullPath($destination);
if (!is_dir($sourcePath)) {
throw new Exception("源目录不存在:{$sourcePath}");
}
// 创建目标目录
$this->createDirectory($destPath);
$files = array_diff(scandir($sourcePath), ['.', '..']);
foreach ($files as $file) {
$sourceFile = $sourcePath . DIRECTORY_SEPARATOR . $file;
$destFile = $destPath . DIRECTORY_SEPARATOR . $file;
if (is_dir($sourceFile)) {
$this->copyDirectory($source . DIRECTORY_SEPARATOR . $file,
$destination . DIRECTORY_SEPARATOR . $file);
} else {
copy($sourceFile, $destFile);
}
}
return true;
}
// 移动目录
public function moveDirectory($source, $destination) {
$this->copyDirectory($source, $destination);
$this->deleteDirectory($source, true);
return true;
}
// 列出目录内容
public function listDirectory($path = '.', $recursive = false, $showHidden = false) {
$fullPath = $this->getFullPath($path);
if (!is_dir($fullPath)) {
throw new Exception("目录不存在:{$fullPath}");
}
return $this->scanDirectory($fullPath, $recursive, $showHidden);
}
// 扫描目录
private function scanDirectory($dir, $recursive, $showHidden) {
$items = [];
$files = scandir($dir);
foreach ($files as $file) {
if ($file === '.' || $file === '..') {
continue;
}
if (!$showHidden && strpos($file, '.') === 0) {
continue;
}
$path = $dir . DIRECTORY_SEPARATOR . $file;
$relativePath = str_replace($this->basePath . DIRECTORY_SEPARATOR, '', $path);
$items[] = [
'name' => $file,
'path' => $path,
'relative_path' => $relativePath,
'type' => is_dir($path) ? 'directory' : 'file',
'size' => is_file($path) ? filesize($path) : 0,
'modified' => filemtime($path),
'permissions' => substr(sprintf('%o', fileperms($path)), -4)
];
if ($recursive && is_dir($path)) {
$items = array_merge($items, $this->scanDirectory($path, $recursive, $showHidden));
}
}
return $items;
}
// 获取目录大小
public function getDirectorySize($path) {
$fullPath = $this->getFullPath($path);
if (!is_dir($fullPath)) {
return 0;
}
$size = 0;
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($fullPath, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iterator as $file) {
if ($file->isFile()) {
$size += $file->getSize();
}
}
return $size;
}
// 格式化目录大小
public function formatSize($bytes) {
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
$bytes = max(0, $bytes);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);
$bytes /= pow(1024, $pow);
return round($bytes, 2) . ' ' . $units[$pow];
}
// 检查目录是否为空
public function isEmpty($path) {
$fullPath = $this->getFullPath($path);
if (!is_dir($fullPath)) {
return true;
}
$files = array_diff(scandir($fullPath), ['.', '..']);
return empty($files);
}
// 获取完整路径
private function getFullPath($path) {
if (strpos($path, '/') === 0 || (PHP_OS === 'WINNT' && preg_match('/^[A-Za-z]:/', $path))) {
return $path; // 绝对路径
}
return $this->basePath . DIRECTORY_SEPARATOR . $path;
}
// 显示目录树
public function displayTree($path = '.', $maxDepth = 5) {
$fullPath = $this->getFullPath($path);
if (!is_dir($fullPath)) {
echo "目录不存在:{$fullPath}<br>";
return;
}
echo "<h3>目录树:{$path}</h3>";
echo "<pre>";
$this->displayDirectoryTree($fullPath, '', $maxDepth, 0);
echo "</pre>";
}
// 递归显示目录树
private function displayDirectoryTree($dir, $prefix, $maxDepth, $currentDepth) {
if ($currentDepth >= $maxDepth) {
return;
}
$files = array_diff(scandir($dir), ['.', '..']);
sort($files);
$count = count($files);
$index = 0;
foreach ($files as $file) {
$index++;
$path = $dir . DIRECTORY_SEPARATOR . $file;
$isLast = ($index === $count);
$currentPrefix = $prefix . ($isLast ? '└── ' : '├── ');
echo $currentPrefix . $file;
if (is_dir($path)) {
echo "/<br>";
$nextPrefix = $prefix . ($isLast ? ' ' : '│ ');
$this->displayDirectoryTree($path, $nextPrefix, $maxDepth, $currentDepth + 1);
} else {
$size = filesize($path);
echo " (" . $this->formatSize($size) . ")<br>";
}
}
}
}
// 使用示例
try {
$dirManager = new DirectoryManager('.');
// 创建目录
$dirManager->createDirectory('uploads/images');
$dirManager->createDirectory('uploads/documents');
$dirManager->createDirectory('backup/2024/01');
echo "目录创建完成<br>";
// 列出目录内容
$items = $dirManager->listDirectory('.', false, true);
echo "<h3>当前目录内容:</h3>";
echo "<table border='1'>";
echo "<tr><th>名称</th><th>类型</th><th>大小</th><th>修改时间</th><th>权限</th></tr>";
foreach ($items as $item) {
$modified = date('Y-m-d H:i:s', $item['modified']);
$size = $item['type'] === 'file' ? $dirManager->formatSize($item['size']) : '-';
echo "<tr>";
echo "<td>" . htmlspecialchars($item['name']) . "</td>";
echo "<td>" . $item['type'] . "</td>";
echo "<td>" . $size . "</td>";
echo "<td>" . $modified . "</td>";
echo "<td>" . $item['permissions'] . "</td>";
echo "</tr>";
}
echo "</table>";
// 获取目录大小
$size = $dirManager->getDirectorySize('.');
echo "<p>当前目录总大小:" . $dirManager->formatSize($size) . "</p>";
// 显示目录树
$dirManager->displayTree('.', 3);
} catch (Exception $e) {
echo "目录操作错误:" . $e->getMessage();
}
?>
文件上传处理
1. 基本文件上传
<?php
// 文件上传处理类
class FileUploader {
private $uploadDir;
private $allowedTypes = [];
private $maxSize = 5242880; // 5MB
private $allowedExtensions = [];
private $overwrite = false;
private $randomizeName = true;
public function __construct($uploadDir) {
$this->uploadDir = rtrim($uploadDir, '/\\');
// 确保上传目录存在
if (!is_dir($this->uploadDir)) {
if (!mkdir($this->uploadDir, 0755, true)) {
throw new Exception("无法创建上传目录:{$this->uploadDir}");
}
}
}
// 设置允许的文件类型
public function setAllowedTypes(array $types) {
$this->allowedTypes = $types;
return $this;
}
// 设置允许的扩展名
public function setAllowedExtensions(array $extensions) {
$this->allowedExtensions = $extensions;
return $this;
}
// 设置最大文件大小
public function setMaxSize($size) {
$this->maxSize = $size;
return $this;
}
// 设置是否覆盖已存在文件
public function setOverwrite($overwrite) {
$this->overwrite = $overwrite;
return $this;
}
// 设置是否随机化文件名
public function setRandomizeName($randomize) {
$this->randomizeName = $randomize;
return $this;
}
// 上传单个文件
public function upload($fileInput) {
if (!isset($_FILES[$fileInput])) {
throw new Exception("没有上传文件");
}
$file = $_FILES[$fileInput];
// 验证上传
$this->validateUpload($file);
// 验证文件
$this->validateFile($file);
// 生成目标文件名
$targetFile = $this->generateTargetFilename($file['name']);
// 移动文件
if (!move_uploaded_file($file['tmp_name'], $targetFile)) {
throw new Exception("文件移动失败");
}
return [
'original_name' => $file['name'],
'file_path' => $targetFile,
'file_size' => $file['size'],
'file_type' => $file['type'],
'upload_time' => date('Y-m-d H:i:s')
];
}
// 批量上传
public function uploadMultiple($fileInput) {
if (!isset($_FILES[$fileInput])) {
throw new Exception("没有上传文件");
}
$files = $_FILES[$fileInput];
$results = [];
// 处理多个文件
for ($i = 0; $i < count($files['name']); $i++) {
if ($files['error'][$i] === UPLOAD_ERR_OK) {
$file = [
'name' => $files['name'][$i],
'type' => $files['type'][$i],
'tmp_name' => $files['tmp_name'][$i],
'error' => $files['error'][$i],
'size' => $files['size'][$i]
];
try {
$result = $this->uploadSingle($file);
$results[] = $result;
} catch (Exception $e) {
$results[] = [
'error' => $e->getMessage(),
'original_name' => $files['name'][$i]
];
}
} else {
$results[] = [
'error' => $this->getUploadErrorMessage($files['error'][$i]),
'original_name' => $files['name'][$i]
];
}
}
return $results;
}
// 上传单个文件(内部方法)
private function uploadSingle($file) {
$this->validateUpload($file);
$this->validateFile($file);
$targetFile = $this->generateTargetFilename($file['name']);
if (!move_uploaded_file($file['tmp_name'], $targetFile)) {
throw new Exception("文件移动失败");
}
return [
'original_name' => $file['name'],
'file_path' => $targetFile,
'file_size' => $file['size'],
'file_type' => $file['type'],
'upload_time' => date('Y-m-d H:i:s')
];
}
// 验证上传
private function validateUpload($file) {
if ($file['error'] !== UPLOAD_ERR_OK) {
throw new Exception($this->getUploadErrorMessage($file['error']));
}
if (!is_uploaded_file($file['tmp_name'])) {
throw new Exception("不是有效的上传文件");
}
}
// 验证文件
private function validateFile($file) {
// 检查文件大小
if ($file['size'] > $this->maxSize) {
throw new Exception("文件大小超过限制:" . $this->formatBytes($this->maxSize));
}
// 检查MIME类型
if (!empty($this->allowedTypes) && !in_array($file['type'], $this->allowedTypes)) {
throw new Exception("不支持的文件类型:{$file['type']}");
}
// 检查文件扩展名
$extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!empty($this->allowedExtensions) && !in_array($extension, $this->allowedExtensions)) {
throw new Exception("不支持的文件扩展名:{$extension}");
}
// 验证文件内容(可选)
if ($this->isImageFile($file)) {
$this->validateImage($file['tmp_name']);
}
}
// 验证图片文件
private function validateImage($tmpName) {
$imageInfo = getimagesize($tmpName);
if ($imageInfo === false) {
throw new Exception("不是有效的图片文件");
}
// 检查图片尺寸限制
list($width, $height) = $imageInfo;
if ($width > 4000 || $height > 4000) {
throw new Exception("图片尺寸过大");
}
}
// 判断是否为图片文件
private function isImageFile($file) {
return strpos($file['type'], 'image/') === 0;
}
// 生成目标文件名
private function generateTargetFilename($originalName) {
$extension = pathinfo($originalName, PATHINFO_EXTENSION);
$basename = pathinfo($originalName, PATHINFO_FILENAME);
if ($this->randomizeName) {
$basename = uniqid() . '_' . time();
}
$targetName = $basename . '.' . $extension;
$targetPath = $this->uploadDir . DIRECTORY_SEPARATOR . $targetName;
// 处理文件名冲突
$counter = 1;
while (!$this->overwrite && file_exists($targetPath)) {
$targetName = $basename . '_' . $counter . '.' . $extension;
$targetPath = $this->uploadDir . DIRECTORY_SEPARATOR . $targetName;
$counter++;
}
return $targetPath;
}
// 格式化字节大小
private function formatBytes($bytes) {
$units = ['B', 'KB', 'MB', 'GB'];
$bytes = max(0, $bytes);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);
$bytes /= pow(1024, $pow);
return round($bytes, 2) . ' ' . $units[$pow];
}
// 获取上传错误消息
private function getUploadErrorMessage($errorCode) {
switch ($errorCode) {
case UPLOAD_ERR_INI_SIZE:
return "文件大小超过php.ini中的限制";
case UPLOAD_ERR_FORM_SIZE:
return "文件大小超过HTML表单中的限制";
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 "上传被文件扩展停止";
default:
return "未知上传错误";
}
}
}
// 使用示例
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
$uploader = new FileUploader('uploads');
// 设置上传限制
$uploader->setAllowedExtensions(['jpg', 'jpeg', 'png', 'gif', 'pdf', 'doc', 'docx'])
->setMaxSize(10485760) // 10MB
->setRandomizeName(true)
->setOverwrite(false);
// 上传单个文件
if (isset($_FILES['single_file'])) {
$result = $uploader->upload('single_file');
echo "<h3>上传成功!</h3>";
echo "<p>原文件名:" . $result['original_name'] . "</p>";
echo "<p>保存路径:" . $result['file_path'] . "</p>";
echo "<p>文件大小:" . $uploader->formatBytes($result['file_size']) . "</p>";
echo "<p>上传时间:" . $result['upload_time'] . "</p>";
}
// 批量上传
if (isset($_FILES['multiple_files'])) {
$results = $uploader->uploadMultiple('multiple_files');
echo "<h3>批量上传结果:</h3>";
echo "<ul>";
foreach ($results as $i => $result) {
if (isset($result['error'])) {
echo "<li>错误:" . htmlspecialchars($result['error']) . "</li>";
} else {
echo "<li>成功:" . htmlspecialchars($result['original_name']) . "</li>";
}
}
echo "</ul>";
}
} catch (Exception $e) {
echo "<h3>上传错误</h3>";
echo "<p>" . htmlspecialchars($e->getMessage()) . "</p>";
}
}
?>
<!-- HTML表单 -->
<!DOCTYPE html>
<html>
<head>
<title>文件上传示例</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.upload-form { border: 1px solid #ddd; padding: 20px; margin: 20px 0; }
.form-group { margin: 15px 0; }
label { display: block; margin-bottom: 5px; font-weight: bold; }
input[type="file"] { padding: 5px; }
button { padding: 10px 20px; background-color: #007cba; color: white; border: none; cursor: pointer; }
button:hover { background-color: #005a87; }
.success { color: green; }
.error { color: red; }
</style>
</head>
<body>
<h1>文件上传示例</h1>
<div class="upload-form">
<h3>单个文件上传</h3>
<form method="POST" enctype="multipart/form-data">
<div class="form-group">
<label for="single_file">选择文件:</label>
<input type="file" name="single_file" id="single_file" required>
</div>
<button type="submit">上传文件</button>
</form>
</div>
<div class="upload-form">
<h3>多个文件上传</h3>
<form method="POST" enctype="multipart/form-data">
<div class="form-group">
<label for="multiple_files">选择多个文件:</label>
<input type="file" name="multiple_files[]" id="multiple_files" multiple required>
</div>
<button type="submit">批量上传</button>
</form>
</div>
</body>
</html>
安全注意事项
1. 文件安全检查
<?php
// 文件安全检查类
class FileSecurityChecker {
private $allowedExtensions = [];
private $maxFileSize = 10485760; // 10MB
private $allowedMimeTypes = [];
public function __construct() {
// 设置默认的安全配置
$this->allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'txt', 'doc', 'docx'];
$this->allowedMimeTypes = [
'image/jpeg', 'image/png', 'image/gif',
'application/pdf',
'text/plain',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
];
}
// 验证上传文件
public function validateUploadedFile($file) {
// 检查上传错误
if ($file['error'] !== UPLOAD_ERR_OK) {
throw new Exception("上传错误:" . $this->getUploadErrorMessage($file['error']));
}
// 验证是否为上传文件
if (!is_uploaded_file($file['tmp_name'])) {
throw new Exception("文件不是通过HTTP POST上传的");
}
// 验证文件扩展名
$extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($extension, $this->allowedExtensions)) {
throw new Exception("不允许的文件扩展名:{$extension}");
}
// 验证文件大小
if ($file['size'] > $this->maxFileSize) {
throw new Exception("文件大小超过限制");
}
// 验证MIME类型
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
if (!in_array($mimeType, $this->allowedMimeTypes)) {
throw new Exception("不允许的MIME类型:{$mimeType}");
}
// 检查文件内容
$this->validateFileContent($file['tmp_name'], $mimeType);
return true;
}
// 验证文件内容
private function validateFileContent($filePath, $mimeType) {
// 检查文件是否为图片
if (strpos($mimeType, 'image/') === 0) {
$this->validateImage($filePath);
}
// 检查是否包含恶意代码
$this->checkForMaliciousContent($filePath);
return true;
}
// 验证图片文件
private function validateImage($filePath) {
// 使用getimagesize检查文件头
$imageInfo = @getimagesize($filePath);
if ($imageInfo === false) {
throw new Exception("文件不是有效的图片");
}
// 检查图片尺寸
list($width, $height) = $imageInfo;
if ($width <= 0 || $height <= 0) {
throw new Exception("无效的图片尺寸");
}
// 检查图片是否过大
if ($width > 4000 || $height > 4000) {
throw new Exception("图片尺寸过大");
}
// 重新保存图片以去除潜在的恶意代码
$this->sanitizeImage($filePath, $imageInfo[2]);
}
// 清理图片文件
private function sanitizeImage($filePath, $imageType) {
switch ($imageType) {
case IMAGETYPE_JPEG:
$image = imagecreatefromjpeg($filePath);
imagejpeg($image, $filePath, 90);
break;
case IMAGETYPE_PNG:
$image = imagecreatefrompng($filePath);
imagepng($image, $filePath, 9);
break;
case IMAGETYPE_GIF:
$image = imagecreatefromgif($filePath);
imagegif($image, $filePath);
break;
}
if (isset($image)) {
imagedestroy($image);
}
}
// 检查恶意内容
private function checkForMaliciousContent($filePath) {
// 读取文件的前1KB内容检查
$handle = fopen($filePath, 'r');
$content = fread($handle, 1024);
fclose($handle);
// 检查常见的Web脚本标签
$dangerousPatterns = [
'/<\?php/i',
'/<\?=/i',
'/<script/i',
'/javascript:/i',
'/vbscript:/i',
'/onload=/i',
'/onerror=/i'
];
foreach ($dangerousPatterns as $pattern) {
if (preg_match($pattern, $content)) {
throw new Exception("文件包含危险内容");
}
}
return true;
}
// 获取上传错误消息
private function getUploadErrorMessage($errorCode) {
switch ($errorCode) {
case UPLOAD_ERR_INI_SIZE:
return "文件大小超过服务器限制";
case UPLOAD_ERR_FORM_SIZE:
return "文件大小超过表单限制";
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 "上传被扩展阻止";
default:
return "未知上传错误";
}
}
// 生成安全的文件名
public function generateSecureFilename($originalName) {
// 获取文件扩展名
$extension = pathinfo($originalName, PATHINFO_EXTENSION);
$basename = pathinfo($originalName, PATHINFO_FILENAME);
// 清理文件名
$basename = preg_replace('/[^a-zA-Z0-9_-]/', '', $basename);
$basename = substr($basename, 0, 50); // 限制长度
// 生成随机前缀
$prefix = bin2hex(random_bytes(8));
$timestamp = time();
return $prefix . '_' . $timestamp . '_' . $basename . '.' . $extension;
}
// 创建安全的上传目录结构
public function createSecureUploadDir($baseDir) {
// 按年月创建目录
$year = date('Y');
$month = date('m');
$day = date('d');
$dirPath = $baseDir . DIRECTORY_SEPARATOR . $year .
DIRECTORY_SEPARATOR . $month .
DIRECTORY_SEPARATOR . $day;
if (!is_dir($dirPath)) {
if (!mkdir($dirPath, 0755, true)) {
throw new Exception("无法创建上传目录");
}
// 创建.htaccess文件防止直接访问
$htaccessContent = "Options -Indexes\n";
$htaccessContent .= "deny from all\n";
file_put_contents($dirPath . DIRECTORY_SEPARATOR . '.htaccess', $htaccessContent);
}
return $dirPath;
}
}
// 使用示例
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['secure_upload'])) {
try {
$securityChecker = new FileSecurityChecker();
// 验证上传文件
$file = $_FILES['secure_upload'];
$securityChecker->validateUploadedFile($file);
// 生成安全文件名
$secureFilename = $securityChecker->generateSecureFilename($file['name']);
// 创建安全上传目录
$uploadDir = $securityChecker->createSecureUploadDir('secure_uploads');
$targetPath = $uploadDir . DIRECTORY_SEPARATOR . $secureFilename;
// 移动文件
if (move_uploaded_file($file['tmp_name'], $targetPath)) {
echo "<div class='success'>";
echo "<h3>安全上传成功!</h3>";
echo "<p>安全文件名:" . htmlspecialchars($secureFilename) . "</p>";
echo "<p>存储路径:" . htmlspecialchars($targetPath) . "</p>";
echo "</div>";
} else {
throw new Exception("文件移动失败");
}
} catch (Exception $e) {
echo "<div class='error'>";
echo "<h3>安全检查失败</h3>";
echo "<p>" . htmlspecialchars($e->getMessage()) . "</p>";
echo "</div>";
}
}
?>
<!-- 安全上传表单 -->
<div class="upload-form">
<h3>安全文件上传</h3>
<form method="POST" enctype="multipart/form-data">
<div class="form-group">
<label for="secure_upload">选择文件(安全上传):</label>
<input type="file" name="secure_upload" id="secure_upload" required>
<small>允许的文件类型:JPG, PNG, GIF, PDF, DOC, DOCX(最大10MB)</small>
</div>
<button type="submit">安全上传</button>
</form>
</div>
总结
文件操作是PHP开发中的核心技能,掌握文件操作对于构建功能完整的Web应用至关重要。
关键要点:
- 路径处理:正确处理相对路径和绝对路径,使用跨平台的路径函数
- 文件权限:理解文件权限,确保PHP进程有足够的权限
- 错误处理:始终检查文件操作函数的返回值
- 安全考虑:验证上传文件,防止路径遍历攻击
- 性能优化:对大文件使用流式处理,避免内存溢出
最佳实践:
- 使用
file_exists()检查文件是否存在 - 对上传文件进行严格的验证和安全检查
- 使用文件锁定机制防止并发写入问题
- 为上传的文件生成安全的文件名
- 及时关闭文件句柄释放资源
- 使用异常处理机制管理文件操作错误
通过学习本章内容,你现在应该能够熟练处理PHP中的各种文件操作,为构建复杂的Web应用打下坚实基础。