9.1 文件基础操作

9.1.1 文件信息获取

<?php
// 文件信息获取
echo "=== 文件信息获取 ===\n";

$filename = 'test.txt';

// 创建测试文件
file_put_contents($filename, "这是一个测试文件\n包含多行内容\n用于演示文件操作");

echo "--- 基本文件信息 ---\n";

// 检查文件是否存在
if (file_exists($filename)) {
    echo "文件存在: $filename\n";
    
    // 文件大小
    $size = filesize($filename);
    echo "文件大小: $size 字节\n";
    
    // 文件类型
    $type = filetype($filename);
    echo "文件类型: $type\n";
    
    // 文件权限
    $perms = fileperms($filename);
    echo "文件权限: " . decoct($perms & 0777) . "\n";
    
    // 文件所有者
    $owner = fileowner($filename);
    echo "文件所有者ID: $owner\n";
    
    // 文件组
    $group = filegroup($filename);
    echo "文件组ID: $group\n";
    
    // 最后访问时间
    $atime = fileatime($filename);
    echo "最后访问时间: " . date('Y-m-d H:i:s', $atime) . "\n";
    
    // 最后修改时间
    $mtime = filemtime($filename);
    echo "最后修改时间: " . date('Y-m-d H:i:s', $mtime) . "\n";
    
    // 最后改变时间(包括权限、所有者等变化)
    $ctime = filectime($filename);
    echo "最后改变时间: " . date('Y-m-d H:i:s', $ctime) . "\n";
    
    // 文件inode
    $inode = fileinode($filename);
    echo "文件inode: $inode\n";
    
} else {
    echo "文件不存在: $filename\n";
}

echo "\n--- 文件状态检查 ---\n";

// 各种文件状态检查
$checks = [
    'is_file' => '是否为普通文件',
    'is_dir' => '是否为目录',
    'is_link' => '是否为符号链接',
    'is_readable' => '是否可读',
    'is_writable' => '是否可写',
    'is_executable' => '是否可执行'
];

foreach ($checks as $function => $description) {
    $result = $function($filename) ? '是' : '否';
    echo "$description: $result\n";
}

// 获取详细的文件统计信息
echo "\n--- 详细文件统计 ---\n";
$stat = stat($filename);
if ($stat) {
    $statInfo = [
        'dev' => '设备号',
        'ino' => 'inode号',
        'mode' => '模式',
        'nlink' => '链接数',
        'uid' => '用户ID',
        'gid' => '组ID',
        'rdev' => '设备类型',
        'size' => '大小',
        'atime' => '访问时间',
        'mtime' => '修改时间',
        'ctime' => '改变时间',
        'blksize' => '块大小',
        'blocks' => '块数'
    ];
    
    foreach ($statInfo as $key => $description) {
        if (isset($stat[$key])) {
            $value = $stat[$key];
            if (in_array($key, ['atime', 'mtime', 'ctime'])) {
                $value = date('Y-m-d H:i:s', $value) . " ($value)";
            }
            echo "$description: $value\n";
        }
    }
}

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

9.1.2 文件读取操作

<?php
// 文件读取操作
echo "=== 文件读取操作 ===\n";

// 创建测试文件
$filename = 'read_test.txt';
$content = "第一行内容\n第二行内容\n第三行内容\n包含中文:你好世界\n数字:12345\n特殊字符:@#$%^&*()";
file_put_contents($filename, $content);

echo "--- 一次性读取整个文件 ---\n";

// 1. file_get_contents() - 最常用的方法
$data = file_get_contents($filename);
echo "file_get_contents()读取结果:\n$data\n\n";

// 2. file() - 读取为数组,每行一个元素
$lines = file($filename);
echo "file()读取结果(数组):\n";
foreach ($lines as $lineNum => $line) {
    echo "行 " . ($lineNum + 1) . ": " . trim($line) . "\n";
}
echo "\n";

// 3. file() 去除换行符
$lines = file($filename, FILE_IGNORE_NEW_LINES);
echo "file()读取结果(去除换行符):\n";
foreach ($lines as $lineNum => $line) {
    echo "行 " . ($lineNum + 1) . ": $line\n";
}
echo "\n";

echo "--- 逐行读取文件 ---\n";

// 使用fopen、fgets逐行读取
$handle = fopen($filename, 'r');
if ($handle) {
    echo "使用fgets()逐行读取:\n";
    $lineNum = 1;
    while (($line = fgets($handle)) !== false) {
        echo "行 $lineNum: " . trim($line) . "\n";
        $lineNum++;
    }
    fclose($handle);
} else {
    echo "无法打开文件\n";
}
echo "\n";

echo "--- 按块读取文件 ---\n";

// 使用fread按指定大小读取
$handle = fopen($filename, 'r');
if ($handle) {
    echo "使用fread()按块读取(每次10字节):\n";
    $chunkNum = 1;
    while (!feof($handle)) {
        $chunk = fread($handle, 10);
        if ($chunk !== false && $chunk !== '') {
            echo "块 $chunkNum: " . json_encode($chunk) . "\n";
            $chunkNum++;
        }
    }
    fclose($handle);
}
echo "\n";

echo "--- 文件指针操作 ---\n";

$handle = fopen($filename, 'r');
if ($handle) {
    // 获取当前指针位置
    echo "初始指针位置: " . ftell($handle) . "\n";
    
    // 读取前10个字符
    $data = fread($handle, 10);
    echo "读取前10个字符: " . json_encode($data) . "\n";
    echo "读取后指针位置: " . ftell($handle) . "\n";
    
    // 移动指针到文件开头
    rewind($handle);
    echo "rewind后指针位置: " . ftell($handle) . "\n";
    
    // 使用fseek移动指针
    fseek($handle, 5, SEEK_SET); // 从文件开头偏移5字节
    echo "fseek(5, SEEK_SET)后指针位置: " . ftell($handle) . "\n";
    
    // 从当前位置向前移动
    fseek($handle, 3, SEEK_CUR);
    echo "fseek(3, SEEK_CUR)后指针位置: " . ftell($handle) . "\n";
    
    // 从文件末尾向前移动
    fseek($handle, -5, SEEK_END);
    echo "fseek(-5, SEEK_END)后指针位置: " . ftell($handle) . "\n";
    
    // 读取当前位置的数据
    $data = fread($handle, 5);
    echo "从当前位置读取5个字符: " . json_encode($data) . "\n";
    
    fclose($handle);
}

echo "\n--- 二进制文件读取 ---\n";

// 创建二进制测试文件
$binaryFile = 'binary_test.bin';
$binaryData = pack('C*', 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x00, 0xFF, 0x01, 0x02, 0x03);
file_put_contents($binaryFile, $binaryData);

// 读取二进制文件
$handle = fopen($binaryFile, 'rb'); // 'rb'模式用于二进制读取
if ($handle) {
    echo "二进制文件内容:\n";
    while (!feof($handle)) {
        $byte = fread($handle, 1);
        if ($byte !== false && $byte !== '') {
            $hex = bin2hex($byte);
            $dec = ord($byte);
            $char = ($dec >= 32 && $dec <= 126) ? $byte : '.';
            echo "字节: 0x$hex ($dec) '$char'\n";
        }
    }
    fclose($handle);
}

// 清理测试文件
unlink($filename);
unlink($binaryFile);
?>

9.1.3 文件写入操作

<?php
// 文件写入操作
echo "=== 文件写入操作 ===\n";

echo "--- 一次性写入文件 ---\n";

// 1. file_put_contents() - 最常用的方法
$filename = 'write_test.txt';
$content = "这是通过file_put_contents写入的内容\n";

// 写入文件(覆盖模式)
$bytes = file_put_contents($filename, $content);
echo "写入字节数: $bytes\n";
echo "文件内容: " . file_get_contents($filename) . "\n";

// 追加写入
$appendContent = "这是追加的内容\n";
$bytes = file_put_contents($filename, $appendContent, FILE_APPEND);
echo "追加字节数: $bytes\n";
echo "文件内容: " . file_get_contents($filename) . "\n";

// 使用锁定写入
$lockContent = "这是使用锁定写入的内容\n";
$bytes = file_put_contents($filename, $lockContent, FILE_APPEND | LOCK_EX);
echo "锁定写入字节数: $bytes\n";
echo "文件内容: " . file_get_contents($filename) . "\n";

echo "--- 使用文件句柄写入 ---\n";

$filename2 = 'handle_write_test.txt';

// 打开文件进行写入
$handle = fopen($filename2, 'w'); // 'w'模式:写入,截断文件
if ($handle) {
    // 使用fwrite写入
    $data1 = "第一行数据\n";
    $bytes1 = fwrite($handle, $data1);
    echo "fwrite写入字节数: $bytes1\n";
    
    // 使用fputs写入(fwrite的别名)
    $data2 = "第二行数据\n";
    $bytes2 = fputs($handle, $data2);
    echo "fputs写入字节数: $bytes2\n";
    
    // 使用fprintf格式化写入
    $bytes3 = fprintf($handle, "格式化数据: %s = %d\n", "数量", 100);
    echo "fprintf写入字节数: $bytes3\n";
    
    fclose($handle);
    
    echo "文件内容: " . file_get_contents($filename2) . "\n";
} else {
    echo "无法打开文件进行写入\n";
}

echo "--- 不同写入模式演示 ---\n";

$modeFile = 'mode_test.txt';

// 准备初始内容
file_put_contents($modeFile, "初始内容\n");
echo "初始内容: " . file_get_contents($modeFile);

// 模式说明
$modes = [
    'w' => '写入模式(截断)',
    'a' => '追加模式',
    'r+' => '读写模式(不截断)',
    'w+' => '读写模式(截断)',
    'a+' => '读写追加模式'
];

foreach ($modes as $mode => $description) {
    echo "\n--- $description ($mode) ---\n";
    
    // 重置文件内容
    file_put_contents($modeFile, "初始内容\n");
    
    $handle = fopen($modeFile, $mode);
    if ($handle) {
        // 尝试写入
        fwrite($handle, "新内容\n");
        
        // 如果是读写模式,尝试读取
        if (strpos($mode, '+') !== false || $mode === 'r+') {
            rewind($handle); // 回到文件开头
            $content = fread($handle, 1024);
            echo "读取到的内容: " . json_encode($content) . "\n";
        }
        
        fclose($handle);
        
        echo "最终文件内容: " . file_get_contents($modeFile);
    }
}

echo "\n--- 二进制文件写入 ---\n";

$binaryFile = 'binary_write_test.bin';

// 写入二进制数据
$handle = fopen($binaryFile, 'wb'); // 'wb'模式用于二进制写入
if ($handle) {
    // 写入不同类型的二进制数据
    
    // 写入字节序列
    $bytes = pack('C*', 0x48, 0x65, 0x6C, 0x6C, 0x6F); // "Hello"
    fwrite($handle, $bytes);
    
    // 写入整数(小端序)
    $int32 = pack('V', 0x12345678);
    fwrite($handle, $int32);
    
    // 写入浮点数
    $float = pack('f', 3.14159);
    fwrite($handle, $float);
    
    fclose($handle);
    
    // 读取并显示二进制文件内容
    $binaryContent = file_get_contents($binaryFile);
    echo "二进制文件大小: " . strlen($binaryContent) . " 字节\n";
    echo "十六进制内容: " . bin2hex($binaryContent) . "\n";
}

echo "\n--- 文件写入安全性 ---\n";

// 安全写入函数
function safeFileWrite($filename, $data, $mode = 'w') {
    // 检查目录是否存在
    $dir = dirname($filename);
    if (!is_dir($dir)) {
        if (!mkdir($dir, 0755, true)) {
            throw new Exception("无法创建目录: $dir");
        }
    }
    
    // 检查文件是否可写
    if (file_exists($filename) && !is_writable($filename)) {
        throw new Exception("文件不可写: $filename");
    }
    
    // 使用临时文件进行原子写入
    $tempFile = $filename . '.tmp.' . uniqid();
    
    $handle = fopen($tempFile, $mode);
    if (!$handle) {
        throw new Exception("无法创建临时文件: $tempFile");
    }
    
    try {
        // 获取独占锁
        if (!flock($handle, LOCK_EX)) {
            throw new Exception("无法获取文件锁");
        }
        
        // 写入数据
        $bytes = fwrite($handle, $data);
        if ($bytes === false) {
            throw new Exception("写入失败");
        }
        
        // 强制写入磁盘
        if (!fflush($handle)) {
            throw new Exception("刷新缓冲区失败");
        }
        
        fclose($handle);
        
        // 原子性重命名
        if (!rename($tempFile, $filename)) {
            throw new Exception("重命名临时文件失败");
        }
        
        return $bytes;
        
    } catch (Exception $e) {
        fclose($handle);
        if (file_exists($tempFile)) {
            unlink($tempFile);
        }
        throw $e;
    }
}

// 测试安全写入
try {
    $safeFile = 'safe_test.txt';
    $bytes = safeFileWrite($safeFile, "这是安全写入的内容\n");
    echo "安全写入成功,字节数: $bytes\n";
    echo "文件内容: " . file_get_contents($safeFile);
    unlink($safeFile);
} catch (Exception $e) {
    echo "安全写入失败: " . $e->getMessage() . "\n";
}

// 清理测试文件
$testFiles = [$filename, $filename2, $modeFile, $binaryFile];
foreach ($testFiles as $file) {
    if (file_exists($file)) {
        unlink($file);
    }
}
?>

9.2 目录操作

9.2.1 目录基础操作

<?php
// 目录基础操作
echo "=== 目录基础操作 ===\n";

echo "--- 目录信息获取 ---\n";

// 获取当前工作目录
$currentDir = getcwd();
echo "当前工作目录: $currentDir\n";

// 获取脚本所在目录
$scriptDir = __DIR__;
echo "脚本所在目录: $scriptDir\n";

// 获取目录名和基名
$path = '/path/to/some/file.txt';
echo "路径: $path\n";
echo "目录名: " . dirname($path) . "\n";
echo "基名: " . basename($path) . "\n";
echo "基名(无扩展名): " . basename($path, '.txt') . "\n";

// 路径信息解析
$pathInfo = pathinfo($path);
echo "路径信息:\n";
foreach ($pathInfo as $key => $value) {
    echo "  $key: $value\n";
}

echo "\n--- 目录存在性检查 ---\n";

$testDir = 'test_directory';
echo "检查目录是否存在: $testDir\n";

if (is_dir($testDir)) {
    echo "目录存在\n";
} else {
    echo "目录不存在\n";
}

// 检查是否为目录
$items = ['.', '..', $currentDir, __FILE__];
foreach ($items as $item) {
    $isDir = is_dir($item) ? '是' : '否';
    echo "$item 是目录: $isDir\n";
}

echo "\n--- 创建目录 ---\n";

// 创建单级目录
if (!is_dir($testDir)) {
    if (mkdir($testDir)) {
        echo "成功创建目录: $testDir\n";
    } else {
        echo "创建目录失败: $testDir\n";
    }
}

// 创建多级目录
$nestedDir = 'parent/child/grandchild';
if (!is_dir($nestedDir)) {
    if (mkdir($nestedDir, 0755, true)) { // 第三个参数true表示递归创建
        echo "成功创建多级目录: $nestedDir\n";
    } else {
        echo "创建多级目录失败: $nestedDir\n";
    }
}

// 设置目录权限
if (is_dir($testDir)) {
    if (chmod($testDir, 0755)) {
        echo "成功设置目录权限: $testDir\n";
    }
    
    // 获取目录权限
    $perms = fileperms($testDir);
    echo "目录权限: " . decoct($perms & 0777) . "\n";
}

echo "\n--- 目录遍历 ---\n";

// 使用opendir、readdir遍历目录
echo "使用opendir/readdir遍历当前目录:\n";
$handle = opendir('.');
if ($handle) {
    while (($entry = readdir($handle)) !== false) {
        $type = is_dir($entry) ? '[DIR]' : '[FILE]';
        echo "  $type $entry\n";
    }
    closedir($handle);
}

echo "\n使用scandir遍历目录:\n";
$entries = scandir('.');
if ($entries) {
    foreach ($entries as $entry) {
        if ($entry === '.' || $entry === '..') continue;
        $type = is_dir($entry) ? '[DIR]' : '[FILE]';
        $size = is_file($entry) ? filesize($entry) : 0;
        echo "  $type $entry ($size bytes)\n";
    }
}

echo "\n--- 递归目录遍历 ---\n";

// 递归遍历目录函数
function listDirectory($dir, $prefix = '') {
    if (!is_dir($dir)) {
        return;
    }
    
    $entries = scandir($dir);
    foreach ($entries as $entry) {
        if ($entry === '.' || $entry === '..') continue;
        
        $fullPath = $dir . DIRECTORY_SEPARATOR . $entry;
        $type = is_dir($fullPath) ? '[DIR]' : '[FILE]';
        $size = is_file($fullPath) ? filesize($fullPath) : 0;
        
        echo "$prefix$type $entry";
        if (is_file($fullPath)) {
            echo " ($size bytes)";
        }
        echo "\n";
        
        // 如果是目录,递归遍历
        if (is_dir($fullPath)) {
            listDirectory($fullPath, $prefix . '  ');
        }
    }
}

echo "递归遍历parent目录:\n";
if (is_dir('parent')) {
    listDirectory('parent');
}

echo "\n--- 使用DirectoryIterator ---\n";

// 使用DirectoryIterator遍历目录
echo "使用DirectoryIterator遍历当前目录:\n";
try {
    $iterator = new DirectoryIterator('.');
    foreach ($iterator as $fileInfo) {
        if ($fileInfo->isDot()) continue;
        
        $type = $fileInfo->isDir() ? '[DIR]' : '[FILE]';
        $name = $fileInfo->getFilename();
        $size = $fileInfo->isFile() ? $fileInfo->getSize() : 0;
        $mtime = date('Y-m-d H:i:s', $fileInfo->getMTime());
        
        echo "  $type $name ($size bytes, $mtime)\n";
    }
} catch (Exception $e) {
    echo "DirectoryIterator错误: " . $e->getMessage() . "\n";
}

echo "\n--- 使用RecursiveDirectoryIterator ---\n";

// 递归遍历目录
echo "使用RecursiveDirectoryIterator递归遍历:\n";
try {
    $iterator = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator('.', RecursiveDirectoryIterator::SKIP_DOTS),
        RecursiveIteratorIterator::SELF_FIRST
    );
    
    foreach ($iterator as $fileInfo) {
        $depth = $iterator->getDepth();
        $prefix = str_repeat('  ', $depth);
        $type = $fileInfo->isDir() ? '[DIR]' : '[FILE]';
        $name = $fileInfo->getFilename();
        $relativePath = $iterator->getSubPathName();
        
        echo "$prefix$type $name ($relativePath)\n";
    }
} catch (Exception $e) {
    echo "RecursiveDirectoryIterator错误: " . $e->getMessage() . "\n";
}
?>

9.2.2 目录高级操作

<?php
// 目录高级操作
echo "=== 目录高级操作 ===\n";

echo "--- 目录复制 ---\n";

// 递归复制目录函数
function copyDirectory($source, $destination) {
    if (!is_dir($source)) {
        throw new InvalidArgumentException("源目录不存在: $source");
    }
    
    // 创建目标目录
    if (!is_dir($destination)) {
        if (!mkdir($destination, 0755, true)) {
            throw new RuntimeException("无法创建目标目录: $destination");
        }
    }
    
    $iterator = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS),
        RecursiveIteratorIterator::SELF_FIRST
    );
    
    foreach ($iterator as $item) {
        $targetPath = $destination . DIRECTORY_SEPARATOR . $iterator->getSubPathName();
        
        if ($item->isDir()) {
            if (!is_dir($targetPath) && !mkdir($targetPath, 0755, true)) {
                throw new RuntimeException("无法创建目录: $targetPath");
            }
        } else {
            if (!copy($item->getPathname(), $targetPath)) {
                throw new RuntimeException("无法复制文件: {$item->getPathname()} -> $targetPath");
            }
        }
    }
}

// 创建测试目录结构
$sourceDir = 'source_test';
$destDir = 'dest_test';

// 创建源目录和文件
mkdir($sourceDir, 0755, true);
mkdir($sourceDir . '/subdir', 0755, true);
file_put_contents($sourceDir . '/file1.txt', '文件1内容');
file_put_contents($sourceDir . '/file2.txt', '文件2内容');
file_put_contents($sourceDir . '/subdir/file3.txt', '子目录文件内容');

try {
    copyDirectory($sourceDir, $destDir);
    echo "目录复制成功: $sourceDir -> $destDir\n";
    
    // 验证复制结果
    echo "验证复制结果:\n";
    $iterator = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($destDir, RecursiveDirectoryIterator::SKIP_DOTS)
    );
    
    foreach ($iterator as $file) {
        $relativePath = str_replace($destDir . DIRECTORY_SEPARATOR, '', $file->getPathname());
        $type = $file->isDir() ? '[DIR]' : '[FILE]';
        echo "  $type $relativePath\n";
        
        if ($file->isFile()) {
            echo "    内容: " . file_get_contents($file->getPathname()) . "\n";
        }
    }
} catch (Exception $e) {
    echo "目录复制失败: " . $e->getMessage() . "\n";
}

echo "\n--- 目录移动/重命名 ---\n";

$oldName = 'old_directory';
$newName = 'new_directory';

// 创建测试目录
mkdir($oldName);
file_put_contents($oldName . '/test.txt', '测试内容');

if (rename($oldName, $newName)) {
    echo "目录重命名成功: $oldName -> $newName\n";
    
    // 验证重命名结果
    if (is_dir($newName) && !is_dir($oldName)) {
        echo "重命名验证成功\n";
        echo "新目录内容: " . file_get_contents($newName . '/test.txt') . "\n";
    }
} else {
    echo "目录重命名失败\n";
}

echo "\n--- 目录删除 ---\n";

// 递归删除目录函数
function removeDirectory($dir) {
    if (!is_dir($dir)) {
        return false;
    }
    
    $iterator = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
        RecursiveIteratorIterator::CHILD_FIRST // 先删除子项
    );
    
    foreach ($iterator as $item) {
        if ($item->isDir()) {
            if (!rmdir($item->getPathname())) {
                throw new RuntimeException("无法删除目录: {$item->getPathname()}");
            }
        } else {
            if (!unlink($item->getPathname())) {
                throw new RuntimeException("无法删除文件: {$item->getPathname()}");
            }
        }
    }
    
    return rmdir($dir);
}

// 删除测试目录
$dirsToRemove = [$sourceDir, $destDir, $newName];
foreach ($dirsToRemove as $dir) {
    if (is_dir($dir)) {
        try {
            if (removeDirectory($dir)) {
                echo "成功删除目录: $dir\n";
            } else {
                echo "删除目录失败: $dir\n";
            }
        } catch (Exception $e) {
            echo "删除目录异常: $dir - " . $e->getMessage() . "\n";
        }
    }
}

echo "\n--- 目录权限管理 ---\n";

$permTestDir = 'perm_test';
mkdir($permTestDir);

// 设置不同权限
$permissions = [
    0755 => '所有者读写执行,组和其他用户读执行',
    0644 => '所有者读写,组和其他用户只读',
    0700 => '仅所有者读写执行',
    0777 => '所有用户读写执行'
];

foreach ($permissions as $perm => $description) {
    if (chmod($permTestDir, $perm)) {
        $currentPerm = fileperms($permTestDir);
        $octPerm = decoct($currentPerm & 0777);
        echo "设置权限 $perm ($description): 当前权限 $octPerm\n";
        
        // 检查权限
        $readable = is_readable($permTestDir) ? '是' : '否';
        $writable = is_writable($permTestDir) ? '是' : '否';
        $executable = is_executable($permTestDir) ? '是' : '否';
        echo "  可读: $readable, 可写: $writable, 可执行: $executable\n";
    }
}

// 清理
rmdir($permTestDir);

echo "\n--- 目录监控 ---\n";

// 目录变化监控类
class DirectoryMonitor {
    private $directory;
    private $lastSnapshot;
    
    public function __construct($directory) {
        $this->directory = $directory;
        $this->lastSnapshot = $this->takeSnapshot();
    }
    
    private function takeSnapshot() {
        $snapshot = [];
        
        if (!is_dir($this->directory)) {
            return $snapshot;
        }
        
        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($this->directory, RecursiveDirectoryIterator::SKIP_DOTS)
        );
        
        foreach ($iterator as $file) {
            $relativePath = str_replace($this->directory . DIRECTORY_SEPARATOR, '', $file->getPathname());
            $snapshot[$relativePath] = [
                'type' => $file->isDir() ? 'dir' : 'file',
                'size' => $file->isFile() ? $file->getSize() : 0,
                'mtime' => $file->getMTime()
            ];
        }
        
        return $snapshot;
    }
    
    public function checkChanges() {
        $currentSnapshot = $this->takeSnapshot();
        $changes = [
            'added' => [],
            'modified' => [],
            'deleted' => []
        ];
        
        // 检查新增和修改
        foreach ($currentSnapshot as $path => $info) {
            if (!isset($this->lastSnapshot[$path])) {
                $changes['added'][] = $path;
            } elseif ($this->lastSnapshot[$path]['mtime'] !== $info['mtime'] || 
                     $this->lastSnapshot[$path]['size'] !== $info['size']) {
                $changes['modified'][] = $path;
            }
        }
        
        // 检查删除
        foreach ($this->lastSnapshot as $path => $info) {
            if (!isset($currentSnapshot[$path])) {
                $changes['deleted'][] = $path;
            }
        }
        
        $this->lastSnapshot = $currentSnapshot;
        return $changes;
    }
}

// 测试目录监控
$monitorDir = 'monitor_test';
mkdir($monitorDir);
file_put_contents($monitorDir . '/initial.txt', '初始文件');

$monitor = new DirectoryMonitor($monitorDir);
echo "开始监控目录: $monitorDir\n";

// 模拟一些变化
sleep(1); // 确保时间戳不同
file_put_contents($monitorDir . '/new_file.txt', '新文件');
file_put_contents($monitorDir . '/initial.txt', '修改后的内容');
mkdir($monitorDir . '/new_subdir');

$changes = $monitor->checkChanges();
echo "检测到的变化:\n";
foreach ($changes as $type => $files) {
    if (!empty($files)) {
        echo "  $type: " . implode(', ', $files) . "\n";
    }
}

// 清理
removeDirectory($monitorDir);
?>

9.3 文件上传处理

9.3.1 基础文件上传

<?php
// 文件上传处理
echo "=== 文件上传处理 ===\n";

// 模拟$_FILES数组(实际使用中来自表单上传)
$simulatedFiles = [
    'upload' => [
        'name' => 'test_image.jpg',
        'type' => 'image/jpeg',
        'tmp_name' => '/tmp/php_upload_temp',
        'error' => UPLOAD_ERR_OK,
        'size' => 1024000
    ]
];

echo "--- 文件上传错误处理 ---\n";

// 文件上传错误代码说明
$uploadErrors = [
    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 => '文件上传被扩展程序阻止'
];

foreach ($uploadErrors as $code => $message) {
    echo "错误代码 $code: $message\n";
}

echo "\n--- 文件上传验证函数 ---\n";

// 文件上传验证类
class FileUploadValidator {
    private $allowedTypes = [];
    private $maxSize = 0;
    private $allowedExtensions = [];
    
    public function __construct($allowedTypes = [], $maxSize = 0, $allowedExtensions = []) {
        $this->allowedTypes = $allowedTypes;
        $this->maxSize = $maxSize;
        $this->allowedExtensions = $allowedExtensions;
    }
    
    public function validate($fileInfo) {
        $errors = [];
        
        // 检查上传错误
        if ($fileInfo['error'] !== UPLOAD_ERR_OK) {
            $errors[] = $this->getUploadErrorMessage($fileInfo['error']);
            return $errors;
        }
        
        // 检查文件大小
        if ($this->maxSize > 0 && $fileInfo['size'] > $this->maxSize) {
            $errors[] = "文件大小超过限制 (" . $this->formatBytes($this->maxSize) . ")";
        }
        
        // 检查文件类型
        if (!empty($this->allowedTypes)) {
            $finfo = new finfo(FILEINFO_MIME_TYPE);
            $mimeType = $finfo->file($fileInfo['tmp_name']);
            
            if (!in_array($mimeType, $this->allowedTypes)) {
                $errors[] = "不允许的文件类型: $mimeType";
            }
        }
        
        // 检查文件扩展名
        if (!empty($this->allowedExtensions)) {
            $extension = strtolower(pathinfo($fileInfo['name'], PATHINFO_EXTENSION));
            
            if (!in_array($extension, $this->allowedExtensions)) {
                $errors[] = "不允许的文件扩展名: $extension";
            }
        }
        
        // 检查文件名安全性
        if (!$this->isSecureFilename($fileInfo['name'])) {
            $errors[] = "文件名包含不安全字符";
        }
        
        return $errors;
    }
    
    private function getUploadErrorMessage($errorCode) {
        $messages = [
            UPLOAD_ERR_INI_SIZE => '文件大小超过服务器限制',
            UPLOAD_ERR_FORM_SIZE => '文件大小超过表单限制',
            UPLOAD_ERR_PARTIAL => '文件上传不完整',
            UPLOAD_ERR_NO_FILE => '没有选择文件',
            UPLOAD_ERR_NO_TMP_DIR => '临时目录不存在',
            UPLOAD_ERR_CANT_WRITE => '文件写入失败',
            UPLOAD_ERR_EXTENSION => '文件上传被阻止'
        ];
        
        return $messages[$errorCode] ?? '未知上传错误';
    }
    
    private function formatBytes($bytes) {
        $units = ['B', 'KB', 'MB', 'GB'];
        $factor = floor((strlen($bytes) - 1) / 3);
        return sprintf("%.2f %s", $bytes / pow(1024, $factor), $units[$factor]);
    }
    
    private function isSecureFilename($filename) {
        // 检查危险字符
        $dangerousChars = ['..', '/', '\\', ':', '*', '?', '"', '<', '>', '|'];
        foreach ($dangerousChars as $char) {
            if (strpos($filename, $char) !== false) {
                return false;
            }
        }
        
        // 检查文件名长度
        if (strlen($filename) > 255) {
            return false;
        }
        
        return true;
    }
}

// 测试文件上传验证
$validator = new FileUploadValidator(
    ['image/jpeg', 'image/png', 'image/gif'], // 允许的MIME类型
    2 * 1024 * 1024, // 2MB大小限制
    ['jpg', 'jpeg', 'png', 'gif'] // 允许的扩展名
);

// 创建测试文件
$testFile = 'test_upload.jpg';
file_put_contents($testFile, str_repeat('x', 1000)); // 创建1KB测试文件

$testFileInfo = [
    'name' => 'test_image.jpg',
    'type' => 'image/jpeg',
    'tmp_name' => $testFile,
    'error' => UPLOAD_ERR_OK,
    'size' => filesize($testFile)
];

$errors = $validator->validate($testFileInfo);
if (empty($errors)) {
    echo "文件验证通过\n";
} else {
    echo "文件验证失败:\n";
    foreach ($errors as $error) {
        echo "  - $error\n";
    }
}

echo "\n--- 安全文件上传处理 ---\n";

// 安全文件上传处理类
class SecureFileUpload {
    private $uploadDir;
    private $validator;
    
    public function __construct($uploadDir, FileUploadValidator $validator) {
        $this->uploadDir = rtrim($uploadDir, '/\\') . DIRECTORY_SEPARATOR;
        $this->validator = $validator;
        
        // 确保上传目录存在
        if (!is_dir($this->uploadDir)) {
            mkdir($this->uploadDir, 0755, true);
        }
    }
    
    public function handleUpload($fileInfo) {
        // 验证文件
        $errors = $this->validator->validate($fileInfo);
        if (!empty($errors)) {
            throw new InvalidArgumentException('文件验证失败: ' . implode(', ', $errors));
        }
        
        // 生成安全的文件名
        $safeFilename = $this->generateSafeFilename($fileInfo['name']);
        $targetPath = $this->uploadDir . $safeFilename;
        
        // 确保目标文件不存在(避免覆盖)
        $counter = 1;
        $originalPath = $targetPath;
        while (file_exists($targetPath)) {
            $pathInfo = pathinfo($originalPath);
            $targetPath = $pathInfo['dirname'] . DIRECTORY_SEPARATOR . 
                         $pathInfo['filename'] . "_$counter." . $pathInfo['extension'];
            $counter++;
        }
        
        // 移动上传文件
        if (!move_uploaded_file($fileInfo['tmp_name'], $targetPath)) {
            // 对于测试,使用copy代替move_uploaded_file
            if (!copy($fileInfo['tmp_name'], $targetPath)) {
                throw new RuntimeException('文件移动失败');
            }
        }
        
        // 设置文件权限
        chmod($targetPath, 0644);
        
        return [
            'original_name' => $fileInfo['name'],
            'saved_name' => basename($targetPath),
            'path' => $targetPath,
            'size' => $fileInfo['size'],
            'type' => $fileInfo['type']
        ];
    }
    
    private function generateSafeFilename($originalName) {
        // 获取文件扩展名
        $extension = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
        
        // 生成唯一文件名
        $timestamp = time();
        $random = bin2hex(random_bytes(8));
        
        return $timestamp . '_' . $random . '.' . $extension;
    }
}

// 测试安全文件上传
$uploadDir = 'uploads';
$uploader = new SecureFileUpload($uploadDir, $validator);

try {
    $result = $uploader->handleUpload($testFileInfo);
    echo "文件上传成功:\n";
    foreach ($result as $key => $value) {
        echo "  $key: $value\n";
    }
} catch (Exception $e) {
    echo "文件上传失败: " . $e->getMessage() . "\n";
}

// 清理测试文件
unlink($testFile);
if (is_dir($uploadDir)) {
    $files = glob($uploadDir . '/*');
    foreach ($files as $file) {
        unlink($file);
    }
    rmdir($uploadDir);
}
?>

9.3.2 多文件上传处理

<?php
// 多文件上传处理
echo "=== 多文件上传处理 ===\n";

// 多文件上传处理类
class MultiFileUpload {
    private $uploader;
    private $results = [];
    
    public function __construct(SecureFileUpload $uploader) {
        $this->uploader = $uploader;
    }
    
    public function handleMultipleUploads($filesArray) {
        $this->results = [];
        
        // 处理多文件数组格式
        $normalizedFiles = $this->normalizeFilesArray($filesArray);
        
        foreach ($normalizedFiles as $index => $fileInfo) {
            try {
                if ($fileInfo['error'] === UPLOAD_ERR_NO_FILE) {
                    continue; // 跳过空文件
                }
                
                $result = $this->uploader->handleUpload($fileInfo);
                $this->results[] = [
                    'success' => true,
                    'index' => $index,
                    'data' => $result
                ];
            } catch (Exception $e) {
                $this->results[] = [
                    'success' => false,
                    'index' => $index,
                    'error' => $e->getMessage(),
                    'filename' => $fileInfo['name']
                ];
            }
        }
        
        return $this->results;
    }
    
    private function normalizeFilesArray($filesArray) {
        $normalized = [];
        
        if (isset($filesArray['name']) && is_array($filesArray['name'])) {
            // 处理HTML表单中的多文件上传格式
            $fileCount = count($filesArray['name']);
            for ($i = 0; $i < $fileCount; $i++) {
                $normalized[] = [
                    'name' => $filesArray['name'][$i],
                    'type' => $filesArray['type'][$i],
                    'tmp_name' => $filesArray['tmp_name'][$i],
                    'error' => $filesArray['error'][$i],
                    'size' => $filesArray['size'][$i]
                ];
            }
        } else {
            // 处理单个文件或已规范化的数组
            $normalized = is_array($filesArray) ? $filesArray : [$filesArray];
        }
        
        return $normalized;
    }
    
    public function getResults() {
        return $this->results;
    }
    
    public function getSuccessfulUploads() {
        return array_filter($this->results, function($result) {
            return $result['success'];
        });
    }
    
    public function getFailedUploads() {
        return array_filter($this->results, function($result) {
            return !$result['success'];
        });
    }
    
    public function getUploadSummary() {
        $total = count($this->results);
        $successful = count($this->getSuccessfulUploads());
        $failed = count($this->getFailedUploads());
        
        return [
            'total' => $total,
            'successful' => $successful,
            'failed' => $failed,
            'success_rate' => $total > 0 ? round(($successful / $total) * 100, 2) : 0
        ];
    }
}

// 创建测试文件
$testFiles = [];
for ($i = 1; $i <= 3; $i++) {
    $filename = "test_file_$i.txt";
    file_put_contents($filename, "测试文件 $i 的内容");
    $testFiles[] = [
        'name' => "upload_file_$i.txt",
        'type' => 'text/plain',
        'tmp_name' => $filename,
        'error' => UPLOAD_ERR_OK,
        'size' => filesize($filename)
    ];
}

// 添加一个错误文件
$testFiles[] = [
    'name' => 'error_file.txt',
    'type' => 'text/plain',
    'tmp_name' => '',
    'error' => UPLOAD_ERR_NO_FILE,
    'size' => 0
];

// 测试多文件上传
$uploadDir = 'multi_uploads';
$validator = new FileUploadValidator(
    ['text/plain', 'image/jpeg', 'image/png'],
    1024 * 1024, // 1MB
    ['txt', 'jpg', 'jpeg', 'png']
);
$uploader = new SecureFileUpload($uploadDir, $validator);
$multiUploader = new MultiFileUpload($uploader);

$results = $multiUploader->handleMultipleUploads($testFiles);

echo "多文件上传结果:\n";
foreach ($results as $result) {
    if ($result['success']) {
        echo "  ✓ 文件 {$result['index']}: {$result['data']['original_name']} -> {$result['data']['saved_name']}\n";
    } else {
        echo "  ✗ 文件 {$result['index']}: {$result['filename']} - {$result['error']}\n";
    }
}

$summary = $multiUploader->getUploadSummary();
echo "\n上传摘要:\n";
echo "  总文件数: {$summary['total']}\n";
echo "  成功: {$summary['successful']}\n";
echo "  失败: {$summary['failed']}\n";
echo "  成功率: {$summary['success_rate']}%\n";

// 清理测试文件
for ($i = 1; $i <= 3; $i++) {
    $filename = "test_file_$i.txt";
    if (file_exists($filename)) {
        unlink($filename);
    }
}

if (is_dir($uploadDir)) {
    $files = glob($uploadDir . '/*');
    foreach ($files as $file) {
        unlink($file);
    }
    rmdir($uploadDir);
}
?>

9.4 文件权限与安全

9.4.1 文件权限管理

<?php
// 文件权限管理
echo "=== 文件权限管理 ===\n";

echo "--- 权限基础概念 ---\n";

// 创建测试文件
$testFile = 'permission_test.txt';
file_put_contents($testFile, '权限测试文件');

echo "权限表示方法:\n";
echo "  八进制: 0755 (rwxr-xr-x)\n";
echo "  二进制: 111101101\n";
echo "  符号: rwxr-xr-x\n";
echo "\n权限位含义:\n";
echo "  r (4): 读权限\n";
echo "  w (2): 写权限\n";
echo "  x (1): 执行权限\n";
echo "\n权限组:\n";
echo "  所有者 (user): 文件所有者的权限\n";
echo "  组 (group): 文件所属组的权限\n";
echo "  其他 (other): 其他用户的权限\n";

echo "\n--- 权限操作函数 ---\n";

// 权限管理类
class FilePermissionManager {
    
    public static function getPermissions($file) {
        if (!file_exists($file)) {
            throw new InvalidArgumentException("文件不存在: $file");
        }
        
        $perms = fileperms($file);
        
        return [
            'octal' => decoct($perms & 0777),
            'symbolic' => self::permissionsToSymbolic($perms),
            'readable' => is_readable($file),
            'writable' => is_writable($file),
            'executable' => is_executable($file)
        ];
    }
    
    public static function setPermissions($file, $permissions) {
        if (!file_exists($file)) {
            throw new InvalidArgumentException("文件不存在: $file");
        }
        
        // 支持八进制字符串和整数
        if (is_string($permissions)) {
            $permissions = octdec($permissions);
        }
        
        if (!chmod($file, $permissions)) {
            throw new RuntimeException("设置权限失败: $file");
        }
        
        return true;
    }
    
    public static function permissionsToSymbolic($perms) {
        $symbolic = '';
        
        // 文件类型
        if (($perms & 0xC000) == 0xC000) {
            $symbolic .= 's'; // Socket
        } elseif (($perms & 0xA000) == 0xA000) {
            $symbolic .= 'l'; // Symbolic Link
        } elseif (($perms & 0x8000) == 0x8000) {
            $symbolic .= '-'; // Regular file
        } elseif (($perms & 0x6000) == 0x6000) {
            $symbolic .= 'b'; // Block special
        } elseif (($perms & 0x4000) == 0x4000) {
            $symbolic .= 'd'; // Directory
        } elseif (($perms & 0x2000) == 0x2000) {
            $symbolic .= 'c'; // Character special
        } elseif (($perms & 0x1000) == 0x1000) {
            $symbolic .= 'p'; // FIFO pipe
        } else {
            $symbolic .= 'u'; // Unknown
        }
        
        // 所有者权限
        $symbolic .= (($perms & 0x0100) ? 'r' : '-');
        $symbolic .= (($perms & 0x0080) ? 'w' : '-');
        $symbolic .= (($perms & 0x0040) ?
                     (($perms & 0x0800) ? 's' : 'x') :
                     (($perms & 0x0800) ? 'S' : '-'));
        
        // 组权限
        $symbolic .= (($perms & 0x0020) ? 'r' : '-');
        $symbolic .= (($perms & 0x0010) ? 'w' : '-');
        $symbolic .= (($perms & 0x0008) ?
                     (($perms & 0x0400) ? 's' : 'x') :
                     (($perms & 0x0400) ? 'S' : '-'));
        
        // 其他用户权限
        $symbolic .= (($perms & 0x0004) ? 'r' : '-');
        $symbolic .= (($perms & 0x0002) ? 'w' : '-');
        $symbolic .= (($perms & 0x0001) ?
                     (($perms & 0x0200) ? 't' : 'x') :
                     (($perms & 0x0200) ? 'T' : '-'));
        
        return $symbolic;
    }
    
    public static function addPermission($file, $permission) {
        $currentPerms = fileperms($file) & 0777;
        $newPerms = $currentPerms | $permission;
        return self::setPermissions($file, $newPerms);
    }
    
    public static function removePermission($file, $permission) {
        $currentPerms = fileperms($file) & 0777;
        $newPerms = $currentPerms & ~$permission;
        return self::setPermissions($file, $newPerms);
    }
    
    public static function hasPermission($file, $permission) {
        $currentPerms = fileperms($file) & 0777;
        return ($currentPerms & $permission) === $permission;
    }
}

// 测试权限管理
echo "当前文件权限:\n";
$perms = FilePermissionManager::getPermissions($testFile);
foreach ($perms as $key => $value) {
    $valueStr = is_bool($value) ? ($value ? '是' : '否') : $value;
    echo "  $key: $valueStr\n";
}

echo "\n--- 权限修改测试 ---\n";

$testPermissions = [
    0644 => 'rw-r--r--',
    0755 => 'rwxr-xr-x',
    0600 => 'rw-------',
    0777 => 'rwxrwxrwx'
];

foreach ($testPermissions as $perm => $description) {
    try {
        FilePermissionManager::setPermissions($testFile, $perm);
        $newPerms = FilePermissionManager::getPermissions($testFile);
        echo "设置权限 $perm: {$newPerms['symbolic']} (八进制: {$newPerms['octal']})\n";
        
        // 测试权限检查
        $readable = $newPerms['readable'] ? '可读' : '不可读';
        $writable = $newPerms['writable'] ? '可写' : '不可写';
        $executable = $newPerms['executable'] ? '可执行' : '不可执行';
        echo "  状态: $readable, $writable, $executable\n";
        
    } catch (Exception $e) {
        echo "设置权限失败: " . $e->getMessage() . "\n";
    }
}

echo "\n--- 权限位操作 ---\n";

// 重置为基础权限
FilePermissionManager::setPermissions($testFile, 0644);
echo "初始权限: " . FilePermissionManager::getPermissions($testFile)['octal'] . "\n";

// 添加执行权限
FilePermissionManager::addPermission($testFile, 0111); // 所有用户执行权限
echo "添加执行权限后: " . FilePermissionManager::getPermissions($testFile)['octal'] . "\n";

// 移除写权限
FilePermissionManager::removePermission($testFile, 0222); // 所有用户写权限
echo "移除写权限后: " . FilePermissionManager::getPermissions($testFile)['octal'] . "\n";

// 检查特定权限
$hasReadPerm = FilePermissionManager::hasPermission($testFile, 0444);
echo "是否有读权限: " . ($hasReadPerm ? '是' : '否') . "\n";

$hasWritePerm = FilePermissionManager::hasPermission($testFile, 0222);
echo "是否有写权限: " . ($hasWritePerm ? '是' : '否') . "\n";

// 清理
unlink($testFile);
?>

9.4.2 文件安全检查

<?php
// 文件安全检查
echo "=== 文件安全检查 ===\n";

// 文件安全检查类
class FileSecurityChecker {
    
    private static $dangerousExtensions = [
        'php', 'php3', 'php4', 'php5', 'phtml', 'phps',
        'asp', 'aspx', 'jsp', 'jspx',
        'exe', 'com', 'bat', 'cmd', 'scr',
        'sh', 'bash', 'csh', 'ksh',
        'pl', 'py', 'rb', 'js'
    ];
    
    private static $dangerousMimeTypes = [
        'application/x-php',
        'application/x-httpd-php',
        'text/x-php',
        'application/x-executable',
        'application/x-msdownload',
        'application/x-sh'
    ];
    
    public static function checkFilename($filename) {
        $issues = [];
        
        // 检查文件名长度
        if (strlen($filename) > 255) {
            $issues[] = '文件名过长';
        }
        
        // 检查危险字符
        $dangerousChars = ['..', '/', '\\', ':', '*', '?', '"', '<', '>', '|', '\0'];
        foreach ($dangerousChars as $char) {
            if (strpos($filename, $char) !== false) {
                $issues[] = "包含危险字符: $char";
            }
        }
        
        // 检查文件扩展名
        $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
        if (in_array($extension, self::$dangerousExtensions)) {
            $issues[] = "危险的文件扩展名: $extension";
        }
        
        // 检查双扩展名
        if (preg_match('/\.(\w+)\.(\w+)$/', $filename, $matches)) {
            if (in_array(strtolower($matches[1]), self::$dangerousExtensions)) {
                $issues[] = "双扩展名中包含危险扩展名: {$matches[1]}";
            }
        }
        
        // 检查隐藏文件
        if (strpos(basename($filename), '.') === 0) {
            $issues[] = '隐藏文件';
        }
        
        return $issues;
    }
    
    public static function checkFileContent($filepath) {
        $issues = [];
        
        if (!file_exists($filepath)) {
            $issues[] = '文件不存在';
            return $issues;
        }
        
        // 检查MIME类型
        $finfo = new finfo(FILEINFO_MIME_TYPE);
        $mimeType = $finfo->file($filepath);
        
        if (in_array($mimeType, self::$dangerousMimeTypes)) {
            $issues[] = "危险的MIME类型: $mimeType";
        }
        
        // 检查文件头(魔数)
        $handle = fopen($filepath, 'rb');
        if ($handle) {
            $header = fread($handle, 16);
            fclose($handle);
            
            // 检查PHP标签
            if (strpos($header, '<?php') !== false || strpos($header, '<?=') !== false) {
                $issues[] = '包含PHP代码标签';
            }
            
            // 检查脚本标签
            if (stripos($header, '<script') !== false) {
                $issues[] = '包含脚本标签';
            }
            
            // 检查可执行文件头
            $executableHeaders = [
                "\x4D\x5A" => 'Windows PE',
                "\x7F\x45\x4C\x46" => 'Linux ELF',
                "\xCA\xFE\xBA\xBE" => 'Java Class',
                "\xFE\xED\xFA\xCE" => 'Mach-O'
            ];
            
            foreach ($executableHeaders as $magic => $type) {
                if (strpos($header, $magic) === 0) {
                    $issues[] = "可执行文件: $type";
                }
            }
        }
        
        return $issues;
    }
    
    public static function checkFileSize($filepath, $maxSize = null) {
        $issues = [];
        
        if (!file_exists($filepath)) {
            $issues[] = '文件不存在';
            return $issues;
        }
        
        $size = filesize($filepath);
        
        // 检查文件大小
        if ($maxSize && $size > $maxSize) {
            $issues[] = "文件过大: " . self::formatBytes($size) . " > " . self::formatBytes($maxSize);
        }
        
        // 检查空文件
        if ($size === 0) {
            $issues[] = '空文件';
        }
        
        return $issues;
    }
    
    public static function checkDirectory($dirpath) {
        $issues = [];
        
        if (!is_dir($dirpath)) {
            $issues[] = '目录不存在';
            return $issues;
        }
        
        // 检查目录权限
        if (!is_readable($dirpath)) {
            $issues[] = '目录不可读';
        }
        
        if (!is_writable($dirpath)) {
            $issues[] = '目录不可写';
        }
        
        // 检查目录遍历漏洞
        $realPath = realpath($dirpath);
        if ($realPath !== $dirpath) {
            $issues[] = '可能存在目录遍历漏洞';
        }
        
        return $issues;
    }
    
    public static function sanitizeFilename($filename) {
        // 移除路径分隔符
        $filename = basename($filename);
        
        // 移除危险字符
        $filename = preg_replace('/[^\w\-_\.]/', '_', $filename);
        
        // 限制长度
        if (strlen($filename) > 255) {
            $extension = pathinfo($filename, PATHINFO_EXTENSION);
            $basename = pathinfo($filename, PATHINFO_FILENAME);
            $maxBasenameLength = 255 - strlen($extension) - 1;
            $filename = substr($basename, 0, $maxBasenameLength) . '.' . $extension;
        }
        
        // 确保不是隐藏文件
        if (strpos($filename, '.') === 0) {
            $filename = 'file_' . $filename;
        }
        
        return $filename;
    }
    
    private static function formatBytes($bytes) {
        $units = ['B', 'KB', 'MB', 'GB'];
        $factor = floor((strlen($bytes) - 1) / 3);
        return sprintf("%.2f %s", $bytes / pow(1024, $factor), $units[$factor]);
    }
}

echo "--- 文件名安全检查 ---\n";

$testFilenames = [
    'normal_file.txt',
    'script.php',
    'file..txt',
    'very_long_filename_' . str_repeat('x', 250) . '.txt',
    '.hidden_file',
    'file<script>.txt',
    'file.php.txt',
    'file\\path.txt'
];

foreach ($testFilenames as $filename) {
    $issues = FileSecurityChecker::checkFilename($filename);
    echo "文件名: $filename\n";
    if (empty($issues)) {
        echo "  ✓ 安全\n";
    } else {
        echo "  ✗ 问题: " . implode(', ', $issues) . "\n";
    }
    
    $sanitized = FileSecurityChecker::sanitizeFilename($filename);
    if ($sanitized !== $filename) {
        echo "  清理后: $sanitized\n";
    }
    echo "\n";
}

echo "--- 文件内容安全检查 ---\n";

// 创建测试文件
$testFiles = [
    'normal.txt' => '这是正常的文本内容',
    'php_script.txt' => '<?php echo "Hello World"; ?>',
    'html_script.txt' => '<script>alert("XSS")</script>',
    'binary.bin' => "\x4D\x5A\x90\x00" // PE文件头
];

foreach ($testFiles as $filename => $content) {
    file_put_contents($filename, $content);
    
    $issues = FileSecurityChecker::checkFileContent($filename);
    echo "文件: $filename\n";
    if (empty($issues)) {
        echo "  ✓ 内容安全\n";
    } else {
        echo "  ✗ 问题: " . implode(', ', $issues) . "\n";
    }
    
    $sizeIssues = FileSecurityChecker::checkFileSize($filename, 1024);
    if (!empty($sizeIssues)) {
        echo "  大小问题: " . implode(', ', $sizeIssues) . "\n";
    }
    
    echo "\n";
    unlink($filename);
}

echo "--- 目录安全检查 ---\n";

$testDir = 'security_test_dir';
mkdir($testDir);

$dirIssues = FileSecurityChecker::checkDirectory($testDir);
echo "目录: $testDir\n";
if (empty($dirIssues)) {
    echo "  ✓ 目录安全\n";
} else {
    echo "  ✗ 问题: " . implode(', ', $dirIssues) . "\n";
}

rmdir($testDir);
 ?>

9.5 文件锁定与并发控制

9.5.1 文件锁定机制

<?php
// 文件锁定机制
echo "=== 文件锁定机制 ===\n";

echo "--- 基础文件锁定 ---\n";

// 文件锁定类型
echo "文件锁定类型:\n";
echo "  LOCK_SH: 共享锁(读锁)\n";
echo "  LOCK_EX: 独占锁(写锁)\n";
echo "  LOCK_UN: 释放锁\n";
echo "  LOCK_NB: 非阻塞锁\n";

// 基础锁定示例
$lockFile = 'lock_test.txt';
file_put_contents($lockFile, "初始内容\n");

echo "\n--- 独占锁示例 ---\n";

$handle = fopen($lockFile, 'r+');
if ($handle) {
    echo "尝试获取独占锁...\n";
    
    if (flock($handle, LOCK_EX)) {
        echo "✓ 获取独占锁成功\n";
        
        // 模拟长时间操作
        echo "执行文件操作...\n";
        fwrite($handle, "添加的内容 - " . date('Y-m-d H:i:s') . "\n");
        
        // 释放锁
        flock($handle, LOCK_UN);
        echo "✓ 释放锁成功\n";
    } else {
        echo "✗ 获取独占锁失败\n";
    }
    
    fclose($handle);
}

echo "\n--- 共享锁示例 ---\n";

$handle1 = fopen($lockFile, 'r');
$handle2 = fopen($lockFile, 'r');

if ($handle1 && $handle2) {
    echo "尝试获取两个共享锁...\n";
    
    if (flock($handle1, LOCK_SH)) {
        echo "✓ 第一个共享锁获取成功\n";
        
        if (flock($handle2, LOCK_SH)) {
            echo "✓ 第二个共享锁获取成功\n";
            
            // 读取文件内容
            $content1 = fread($handle1, 1024);
            rewind($handle2);
            $content2 = fread($handle2, 1024);
            
            echo "两个进程都可以同时读取文件\n";
            
            flock($handle2, LOCK_UN);
            echo "✓ 第二个共享锁释放\n";
        }
        
        flock($handle1, LOCK_UN);
        echo "✓ 第一个共享锁释放\n";
    }
    
    fclose($handle1);
    fclose($handle2);
}

echo "\n--- 非阻塞锁示例 ---\n";

// 文件锁定管理类
class FileLockManager {
    private $lockHandles = [];
    
    public function acquireLock($filename, $mode = LOCK_EX, $blocking = true) {
        $handle = fopen($filename, 'c+');
        if (!$handle) {
            throw new RuntimeException("无法打开文件: $filename");
        }
        
        $lockMode = $blocking ? $mode : ($mode | LOCK_NB);
        
        if (flock($handle, $lockMode)) {
            $this->lockHandles[$filename] = $handle;
            return true;
        } else {
            fclose($handle);
            return false;
        }
    }
    
    public function releaseLock($filename) {
        if (isset($this->lockHandles[$filename])) {
            $handle = $this->lockHandles[$filename];
            flock($handle, LOCK_UN);
            fclose($handle);
            unset($this->lockHandles[$filename]);
            return true;
        }
        return false;
    }
    
    public function writeToFile($filename, $data) {
        if (!isset($this->lockHandles[$filename])) {
            throw new RuntimeException("文件未锁定: $filename");
        }
        
        $handle = $this->lockHandles[$filename];
        fseek($handle, 0, SEEK_END);
        return fwrite($handle, $data);
    }
    
    public function readFromFile($filename) {
        if (!isset($this->lockHandles[$filename])) {
            throw new RuntimeException("文件未锁定: $filename");
        }
        
        $handle = $this->lockHandles[$filename];
        rewind($handle);
        return fread($handle, filesize($filename));
    }
    
    public function __destruct() {
        foreach ($this->lockHandles as $filename => $handle) {
            $this->releaseLock($filename);
        }
    }
}

// 测试锁定管理器
$lockManager = new FileLockManager();

try {
    // 尝试获取非阻塞锁
    if ($lockManager->acquireLock($lockFile, LOCK_EX, false)) {
        echo "✓ 获取非阻塞独占锁成功\n";
        
        $lockManager->writeToFile($lockFile, "锁定管理器写入的内容\n");
        echo "✓ 写入数据成功\n";
        
        $content = $lockManager->readFromFile($lockFile);
        echo "读取内容: " . trim($content) . "\n";
        
        $lockManager->releaseLock($lockFile);
        echo "✓ 释放锁成功\n";
    } else {
        echo "✗ 获取非阻塞独占锁失败\n";
    }
} catch (Exception $e) {
    echo "锁定操作异常: " . $e->getMessage() . "\n";
}

// 清理
unlink($lockFile);
?>

9.5.2 并发安全的文件操作

<?php
// 并发安全的文件操作
echo "=== 并发安全的文件操作 ===\n";

// 安全的计数器类
class SafeCounter {
    private $filename;
    
    public function __construct($filename) {
        $this->filename = $filename;
        
        // 初始化计数器文件
        if (!file_exists($filename)) {
            file_put_contents($filename, '0');
        }
    }
    
    public function increment() {
        $handle = fopen($this->filename, 'r+');
        if (!$handle) {
            throw new RuntimeException("无法打开计数器文件");
        }
        
        try {
            // 获取独占锁
            if (!flock($handle, LOCK_EX)) {
                throw new RuntimeException("无法获取文件锁");
            }
            
            // 读取当前值
            $current = (int)fread($handle, 1024);
            
            // 增加计数
            $new = $current + 1;
            
            // 写回文件
            rewind($handle);
            ftruncate($handle, 0);
            fwrite($handle, (string)$new);
            
            return $new;
            
        } finally {
            flock($handle, LOCK_UN);
            fclose($handle);
        }
    }
    
    public function getValue() {
        $handle = fopen($this->filename, 'r');
        if (!$handle) {
            return 0;
        }
        
        try {
            if (flock($handle, LOCK_SH)) {
                $value = (int)fread($handle, 1024);
                return $value;
            }
            return 0;
        } finally {
            flock($handle, LOCK_UN);
            fclose($handle);
        }
    }
    
    public function reset() {
        $handle = fopen($this->filename, 'w');
        if (!$handle) {
            throw new RuntimeException("无法重置计数器文件");
        }
        
        try {
            if (flock($handle, LOCK_EX)) {
                fwrite($handle, '0');
                return true;
            }
            return false;
        } finally {
            flock($handle, LOCK_UN);
            fclose($handle);
        }
    }
}

// 测试安全计数器
$counterFile = 'safe_counter.txt';
$counter = new SafeCounter($counterFile);

echo "--- 安全计数器测试 ---\n";

// 重置计数器
$counter->reset();
echo "初始值: " . $counter->getValue() . "\n";

// 模拟并发增加
for ($i = 0; $i < 5; $i++) {
    $newValue = $counter->increment();
    echo "增加后: $newValue\n";
}

echo "最终值: " . $counter->getValue() . "\n";

// 安全的日志写入类
class SafeLogger {
    private $filename;
    private $maxSize;
    
    public function __construct($filename, $maxSize = 1048576) { // 1MB
        $this->filename = $filename;
        $this->maxSize = $maxSize;
    }
    
    public function log($level, $message) {
        $timestamp = date('Y-m-d H:i:s');
        $logEntry = "[$timestamp] [$level] $message\n";
        
        $handle = fopen($this->filename, 'a');
        if (!$handle) {
            throw new RuntimeException("无法打开日志文件");
        }
        
        try {
            // 获取独占锁
            if (!flock($handle, LOCK_EX)) {
                throw new RuntimeException("无法获取日志文件锁");
            }
            
            // 检查文件大小
            $size = ftell($handle);
            if ($size > $this->maxSize) {
                $this->rotateLog();
            }
            
            // 写入日志
            fwrite($handle, $logEntry);
            
        } finally {
            flock($handle, LOCK_UN);
            fclose($handle);
        }
    }
    
    private function rotateLog() {
        $backupFile = $this->filename . '.old';
        
        // 如果备份文件存在,删除它
        if (file_exists($backupFile)) {
            unlink($backupFile);
        }
        
        // 重命名当前日志文件
        rename($this->filename, $backupFile);
    }
    
    public function getLogs($lines = 10) {
        if (!file_exists($this->filename)) {
            return [];
        }
        
        $handle = fopen($this->filename, 'r');
        if (!$handle) {
            return [];
        }
        
        try {
            if (flock($handle, LOCK_SH)) {
                $content = file_get_contents($this->filename);
                $allLines = explode("\n", trim($content));
                return array_slice($allLines, -$lines);
            }
            return [];
        } finally {
            flock($handle, LOCK_UN);
            fclose($handle);
        }
    }
}

echo "\n--- 安全日志测试 ---\n";

$logFile = 'safe_log.txt';
$logger = new SafeLogger($logFile, 1024); // 1KB限制用于测试

// 写入一些日志
$logger->log('INFO', '应用程序启动');
$logger->log('DEBUG', '调试信息');
$logger->log('WARNING', '警告信息');
$logger->log('ERROR', '错误信息');

// 读取日志
$logs = $logger->getLogs(5);
echo "最近的日志条目:\n";
foreach ($logs as $log) {
    if (!empty($log)) {
        echo "  $log\n";
    }
}

// 清理测试文件
unlink($counterFile);
if (file_exists($logFile)) {
    unlink($logFile);
}
if (file_exists($logFile . '.old')) {
    unlink($logFile . '.old');
}
?>

9.6 特殊文件格式处理

9.6.1 CSV文件处理

<?php
// CSV文件处理
echo "=== CSV文件处理 ===\n";

echo "--- CSV写入 ---\n";

// CSV写入示例
$csvFile = 'test_data.csv';

// 准备测试数据
$data = [
    ['姓名', '年龄', '城市', '薪资'],
    ['张三', 25, '北京', 8000],
    ['李四', 30, '上海', 12000],
    ['王五', 28, '广州', 10000],
    ['赵六', 35, '深圳', 15000]
];

// 使用fputcsv写入CSV
$handle = fopen($csvFile, 'w');
if ($handle) {
    // 设置UTF-8 BOM(用于Excel正确显示中文)
    fwrite($handle, "\xEF\xBB\xBF");
    
    foreach ($data as $row) {
        fputcsv($handle, $row);
    }
    
    fclose($handle);
    echo "✓ CSV文件写入成功\n";
}

echo "\n--- CSV读取 ---\n";

// 使用fgetcsv读取CSV
$handle = fopen($csvFile, 'r');
if ($handle) {
    // 跳过BOM
    if (fread($handle, 3) !== "\xEF\xBB\xBF") {
        rewind($handle);
    }
    
    $rowNum = 0;
    while (($row = fgetcsv($handle)) !== false) {
        $rowNum++;
        echo "行 $rowNum: " . implode(' | ', $row) . "\n";
    }
    
    fclose($handle);
}

echo "\n--- CSV处理类 ---\n";

// CSV处理类
class CSVProcessor {
    private $filename;
    private $delimiter;
    private $enclosure;
    private $escape;
    
    public function __construct($filename, $delimiter = ',', $enclosure = '"', $escape = '\\') {
        $this->filename = $filename;
        $this->delimiter = $delimiter;
        $this->enclosure = $enclosure;
        $this->escape = $escape;
    }
    
    public function writeData($data, $headers = null) {
        $handle = fopen($this->filename, 'w');
        if (!$handle) {
            throw new RuntimeException("无法创建CSV文件: {$this->filename}");
        }
        
        try {
            // 写入UTF-8 BOM
            fwrite($handle, "\xEF\xBB\xBF");
            
            // 写入表头
            if ($headers) {
                fputcsv($handle, $headers, $this->delimiter, $this->enclosure, $this->escape);
            }
            
            // 写入数据
            foreach ($data as $row) {
                fputcsv($handle, $row, $this->delimiter, $this->enclosure, $this->escape);
            }
            
            return true;
        } finally {
            fclose($handle);
        }
    }
    
    public function readData($hasHeaders = true) {
        if (!file_exists($this->filename)) {
            throw new InvalidArgumentException("CSV文件不存在: {$this->filename}");
        }
        
        $handle = fopen($this->filename, 'r');
        if (!$handle) {
            throw new RuntimeException("无法读取CSV文件: {$this->filename}");
        }
        
        try {
            // 检查并跳过BOM
            $bom = fread($handle, 3);
            if ($bom !== "\xEF\xBB\xBF") {
                rewind($handle);
            }
            
            $data = [];
            $headers = null;
            
            if ($hasHeaders) {
                $headers = fgetcsv($handle, 0, $this->delimiter, $this->enclosure, $this->escape);
            }
            
            while (($row = fgetcsv($handle, 0, $this->delimiter, $this->enclosure, $this->escape)) !== false) {
                if ($hasHeaders && $headers) {
                    $data[] = array_combine($headers, $row);
                } else {
                    $data[] = $row;
                }
            }
            
            return [
                'headers' => $headers,
                'data' => $data
            ];
        } finally {
            fclose($handle);
        }
    }
    
    public function filterData($filterCallback) {
        $result = $this->readData(true);
        $filteredData = array_filter($result['data'], $filterCallback);
        
        return [
            'headers' => $result['headers'],
            'data' => array_values($filteredData)
        ];
    }
    
    public function sortData($sortField, $ascending = true) {
        $result = $this->readData(true);
        
        usort($result['data'], function($a, $b) use ($sortField, $ascending) {
            $valueA = $a[$sortField] ?? '';
            $valueB = $b[$sortField] ?? '';
            
            // 尝试数值比较
            if (is_numeric($valueA) && is_numeric($valueB)) {
                $comparison = $valueA <=> $valueB;
            } else {
                $comparison = strcasecmp($valueA, $valueB);
            }
            
            return $ascending ? $comparison : -$comparison;
        });
        
        return [
            'headers' => $result['headers'],
            'data' => $result['data']
        ];
    }
}

// 测试CSV处理类
$processor = new CSVProcessor('employees.csv');

// 写入员工数据
$employees = [
    ['张三', 25, '开发部', 8000],
    ['李四', 30, '设计部', 12000],
    ['王五', 28, '开发部', 10000],
    ['赵六', 35, '管理部', 15000],
    ['钱七', 26, '开发部', 9000]
];

$headers = ['姓名', '年龄', '部门', '薪资'];
$processor->writeData($employees, $headers);
echo "✓ 员工数据写入成功\n";

// 读取数据
$result = $processor->readData(true);
echo "\n读取的数据:\n";
foreach ($result['data'] as $employee) {
    echo "  {$employee['姓名']} - {$employee['年龄']}岁 - {$employee['部门']} - {$employee['薪资']}元\n";
}

// 过滤数据(开发部员工)
$filtered = $processor->filterData(function($employee) {
    return $employee['部门'] === '开发部';
});

echo "\n开发部员工:\n";
foreach ($filtered['data'] as $employee) {
    echo "  {$employee['姓名']} - {$employee['薪资']}元\n";
}

// 按薪资排序
$sorted = $processor->sortData('薪资', false); // 降序

echo "\n按薪资排序(降序):\n";
foreach ($sorted['data'] as $employee) {
    echo "  {$employee['姓名']} - {$employee['薪资']}元\n";
}

// 清理
unlink($csvFile);
unlink('employees.csv');
?>

9.6.2 JSON文件处理

<?php
// JSON文件处理
echo "=== JSON文件处理 ===\n";

echo "--- JSON基础操作 ---\n";

// JSON文件处理类
class JSONFileManager {
    private $filename;
    
    public function __construct($filename) {
        $this->filename = $filename;
    }
    
    public function write($data, $prettyPrint = true) {
        $flags = JSON_UNESCAPED_UNICODE;
        if ($prettyPrint) {
            $flags |= JSON_PRETTY_PRINT;
        }
        
        $json = json_encode($data, $flags);
        if ($json === false) {
            throw new RuntimeException('JSON编码失败: ' . json_last_error_msg());
        }
        
        $bytes = file_put_contents($this->filename, $json, LOCK_EX);
        if ($bytes === false) {
            throw new RuntimeException("写入JSON文件失败: {$this->filename}");
        }
        
        return $bytes;
    }
    
    public function read($assoc = true) {
        if (!file_exists($this->filename)) {
            throw new InvalidArgumentException("JSON文件不存在: {$this->filename}");
        }
        
        $content = file_get_contents($this->filename);
        if ($content === false) {
            throw new RuntimeException("读取JSON文件失败: {$this->filename}");
        }
        
        $data = json_decode($content, $assoc);
        if ($data === null && json_last_error() !== JSON_ERROR_NONE) {
            throw new RuntimeException('JSON解码失败: ' . json_last_error_msg());
        }
        
        return $data;
    }
    
    public function update($callback) {
        $data = $this->read(true);
        $newData = $callback($data);
        return $this->write($newData);
    }
    
    public function append($newItem, $arrayKey = null) {
        return $this->update(function($data) use ($newItem, $arrayKey) {
            if ($arrayKey) {
                if (!isset($data[$arrayKey]) || !is_array($data[$arrayKey])) {
                    $data[$arrayKey] = [];
                }
                $data[$arrayKey][] = $newItem;
            } else {
                if (!is_array($data)) {
                    $data = [];
                }
                $data[] = $newItem;
            }
            return $data;
        });
    }
    
    public function merge($newData) {
        return $this->update(function($data) use ($newData) {
            if (is_array($data) && is_array($newData)) {
                return array_merge_recursive($data, $newData);
            }
            return $newData;
        });
    }
    
    public function validate($schema = null) {
        try {
            $data = $this->read(true);
            
            // 基础验证
            if (!is_array($data) && !is_object($data)) {
                return ['valid' => false, 'error' => 'JSON数据必须是对象或数组'];
            }
            
            // 如果提供了schema,进行schema验证
            if ($schema && is_callable($schema)) {
                $schemaResult = $schema($data);
                if (!$schemaResult) {
                    return ['valid' => false, 'error' => 'Schema验证失败'];
                }
            }
            
            return ['valid' => true];
        } catch (Exception $e) {
            return ['valid' => false, 'error' => $e->getMessage()];
        }
    }
}

// 测试JSON文件管理
$jsonFile = 'test_data.json';
$jsonManager = new JSONFileManager($jsonFile);

// 写入初始数据
$initialData = [
    'application' => [
        'name' => 'PHP教程示例',
        'version' => '1.0.0',
        'author' => 'PHP开发者'
    ],
    'users' => [
        ['id' => 1, 'name' => '张三', 'email' => 'zhangsan@example.com'],
        ['id' => 2, 'name' => '李四', 'email' => 'lisi@example.com']
    ],
    'settings' => [
        'debug' => true,
        'timezone' => 'Asia/Shanghai'
    ]
];

$jsonManager->write($initialData);
echo "✓ JSON数据写入成功\n";

// 读取数据
$data = $jsonManager->read();
echo "\n应用信息: {$data['application']['name']} v{$data['application']['version']}\n";
echo "用户数量: " . count($data['users']) . "\n";

// 添加新用户
$newUser = ['id' => 3, 'name' => '王五', 'email' => 'wangwu@example.com'];
$jsonManager->append($newUser, 'users');
echo "\n✓ 新用户添加成功\n";

// 更新设置
$jsonManager->merge([
    'settings' => [
        'cache_enabled' => true,
        'max_users' => 1000
    ]
]);
echo "✓ 设置更新成功\n";

// 验证JSON文件
$validation = $jsonManager->validate(function($data) {
    // 简单的schema验证
    return isset($data['application']) && 
           isset($data['users']) && 
           is_array($data['users']) &&
           isset($data['settings']);
});

if ($validation['valid']) {
    echo "✓ JSON文件验证通过\n";
} else {
    echo "✗ JSON文件验证失败: {$validation['error']}\n";
}

// 显示最终数据
$finalData = $jsonManager->read();
echo "\n最终用户列表:\n";
foreach ($finalData['users'] as $user) {
    echo "  ID: {$user['id']}, 姓名: {$user['name']}, 邮箱: {$user['email']}\n";
}

echo "\n最终设置:\n";
foreach ($finalData['settings'] as $key => $value) {
    $valueStr = is_bool($value) ? ($value ? 'true' : 'false') : $value;
    echo "  $key: $valueStr\n";
}

// 配置文件管理示例
echo "\n--- 配置文件管理 ---\n";

class ConfigManager {
    private $jsonManager;
    private $config;
    
    public function __construct($configFile) {
        $this->jsonManager = new JSONFileManager($configFile);
        $this->loadConfig();
    }
    
    private function loadConfig() {
        try {
            $this->config = $this->jsonManager->read(true);
        } catch (Exception $e) {
            // 如果配置文件不存在,创建默认配置
            $this->config = $this->getDefaultConfig();
            $this->save();
        }
    }
    
    private function getDefaultConfig() {
        return [
            'database' => [
                'host' => 'localhost',
                'port' => 3306,
                'username' => 'root',
                'password' => '',
                'database' => 'test'
            ],
            'cache' => [
                'enabled' => true,
                'ttl' => 3600,
                'driver' => 'file'
            ],
            'logging' => [
                'level' => 'info',
                'file' => 'app.log'
            ]
        ];
    }
    
    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;
        return $this->save();
    }
    
    public function save() {
        return $this->jsonManager->write($this->config);
    }
    
    public function getAll() {
        return $this->config;
    }
}

// 测试配置管理
$configFile = 'app_config.json';
$config = new ConfigManager($configFile);

echo "数据库主机: " . $config->get('database.host') . "\n";
echo "缓存启用: " . ($config->get('cache.enabled') ? '是' : '否') . "\n";
echo "日志级别: " . $config->get('logging.level') . "\n";

// 更新配置
$config->set('database.host', '192.168.1.100');
$config->set('cache.ttl', 7200);
$config->set('app.name', 'PHP教程应用');

echo "\n✓ 配置更新成功\n";
echo "新的数据库主机: " . $config->get('database.host') . "\n";
echo "新的缓存TTL: " . $config->get('cache.ttl') . "\n";
echo "应用名称: " . $config->get('app.name') . "\n";

// 清理测试文件
unlink($jsonFile);
unlink($configFile);
?>

9.7 路径操作与文件系统函数

9.7.1 路径处理

<?php
// 路径处理
echo "=== 路径处理 ===\n";

echo "--- 基础路径函数 ---\n";

$testPaths = [
    '/home/user/documents/file.txt',
    'C:\\Users\\User\\Documents\\file.txt',
    '../relative/path/file.txt',
    './current/file.txt',
    'simple_file.txt'
];

foreach ($testPaths as $path) {
    echo "\n路径: $path\n";
    echo "  目录名: " . dirname($path) . "\n";
    echo "  基名: " . basename($path) . "\n";
    echo "  扩展名: " . pathinfo($path, PATHINFO_EXTENSION) . "\n";
    echo "  文件名(无扩展名): " . pathinfo($path, PATHINFO_FILENAME) . "\n";
    
    $pathInfo = pathinfo($path);
    echo "  完整路径信息:\n";
    foreach ($pathInfo as $key => $value) {
        echo "    $key: $value\n";
    }
}

echo "\n--- 路径操作类 ---\n";

// 路径操作类
class PathHelper {
    
    public static function normalize($path) {
        // 统一路径分隔符
        $path = str_replace(['\\', '/'], DIRECTORY_SEPARATOR, $path);
        
        // 处理相对路径
        $parts = explode(DIRECTORY_SEPARATOR, $path);
        $normalized = [];
        
        foreach ($parts as $part) {
            if ($part === '' || $part === '.') {
                continue;
            } elseif ($part === '..') {
                if (!empty($normalized) && end($normalized) !== '..') {
                    array_pop($normalized);
                } else {
                    $normalized[] = $part;
                }
            } else {
                $normalized[] = $part;
            }
        }
        
        $result = implode(DIRECTORY_SEPARATOR, $normalized);
        
        // 保持绝对路径的前导分隔符
        if (strpos($path, DIRECTORY_SEPARATOR) === 0) {
            $result = DIRECTORY_SEPARATOR . $result;
        }
        
        return $result;
    }
    
    public static function join(...$parts) {
        $path = implode(DIRECTORY_SEPARATOR, $parts);
        return self::normalize($path);
    }
    
    public static function isAbsolute($path) {
        if (DIRECTORY_SEPARATOR === '\\') {
            // Windows
            return preg_match('/^[a-zA-Z]:\\/', $path) || strpos($path, '\\\\') === 0;
        } else {
            // Unix-like
            return strpos($path, '/') === 0;
        }
    }
    
    public static function makeAbsolute($path, $basePath = null) {
        if (self::isAbsolute($path)) {
            return self::normalize($path);
        }
        
        $basePath = $basePath ?: getcwd();
        return self::normalize(self::join($basePath, $path));
    }
    
    public static function getRelativePath($from, $to) {
        $from = self::normalize($from);
        $to = self::normalize($to);
        
        $fromParts = explode(DIRECTORY_SEPARATOR, $from);
        $toParts = explode(DIRECTORY_SEPARATOR, $to);
        
        // 找到公共前缀
        $commonLength = 0;
        $minLength = min(count($fromParts), count($toParts));
        
        for ($i = 0; $i < $minLength; $i++) {
            if ($fromParts[$i] === $toParts[$i]) {
                $commonLength++;
            } else {
                break;
            }
        }
        
        // 构建相对路径
        $relativeParts = [];
        
        // 添加 ".." 用于回到公共祖先
        for ($i = $commonLength; $i < count($fromParts); $i++) {
            $relativeParts[] = '..';
        }
        
        // 添加目标路径的剩余部分
        for ($i = $commonLength; $i < count($toParts); $i++) {
            $relativeParts[] = $toParts[$i];
        }
        
        return empty($relativeParts) ? '.' : implode(DIRECTORY_SEPARATOR, $relativeParts);
    }
    
    public static function changeExtension($path, $newExtension) {
        $pathInfo = pathinfo($path);
        $newExtension = ltrim($newExtension, '.');
        
        $newPath = $pathInfo['dirname'] . DIRECTORY_SEPARATOR . $pathInfo['filename'];
        if (!empty($newExtension)) {
            $newPath .= '.' . $newExtension;
        }
        
        return self::normalize($newPath);
    }
    
    public static function ensureDirectory($path) {
        $directory = is_dir($path) ? $path : dirname($path);
        
        if (!is_dir($directory)) {
            if (!mkdir($directory, 0755, true)) {
                throw new RuntimeException("无法创建目录: $directory");
            }
        }
        
        return $directory;
    }
    
    public static function findFiles($directory, $pattern = '*', $recursive = false) {
        $files = [];
        
        if (!is_dir($directory)) {
            return $files;
        }
        
        $globPattern = rtrim($directory, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $pattern;
        $matches = glob($globPattern);
        
        foreach ($matches as $match) {
            if (is_file($match)) {
                $files[] = $match;
            }
        }
        
        if ($recursive) {
            $subdirs = glob(rtrim($directory, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . '*', GLOB_ONLYDIR);
            foreach ($subdirs as $subdir) {
                $files = array_merge($files, self::findFiles($subdir, $pattern, true));
            }
        }
        
        return $files;
    }
}

// 测试路径操作
echo "--- 路径规范化测试 ---\n";

$testPaths = [
    './test/../file.txt',
    '/home/user/../user/documents/./file.txt',
    'folder//subfolder///file.txt'
];

foreach ($testPaths as $path) {
    $normalized = PathHelper::normalize($path);
    echo "原路径: $path\n";
    echo "规范化: $normalized\n\n";
}

echo "--- 路径连接测试 ---\n";

$joined = PathHelper::join('home', 'user', 'documents', 'file.txt');
echo "连接路径: $joined\n";

$joined2 = PathHelper::join('/var', 'www', '../log', 'app.log');
echo "连接路径2: $joined2\n";

echo "\n--- 绝对路径测试 ---\n";

$relativePath = 'documents/file.txt';
$absolutePath = PathHelper::makeAbsolute($relativePath);
echo "相对路径: $relativePath\n";
echo "绝对路径: $absolutePath\n";

echo "\n--- 相对路径计算 ---\n";

$from = '/home/user/projects/app';
$to = '/home/user/documents/file.txt';
$relative = PathHelper::getRelativePath($from, $to);
echo "从: $from\n";
echo "到: $to\n";
echo "相对路径: $relative\n";

echo "\n--- 扩展名更改 ---\n";

$originalFile = '/path/to/document.txt';
$newFile = PathHelper::changeExtension($originalFile, 'pdf');
echo "原文件: $originalFile\n";
echo "新文件: $newFile\n";

// 创建测试目录结构
$testDir = 'path_test';
PathHelper::ensureDirectory($testDir . '/subdir1/subdir2');
file_put_contents($testDir . '/file1.txt', 'test');
file_put_contents($testDir . '/file2.log', 'test');
file_put_contents($testDir . '/subdir1/file3.txt', 'test');
file_put_contents($testDir . '/subdir1/subdir2/file4.txt', 'test');

echo "\n--- 文件查找测试 ---\n";

$txtFiles = PathHelper::findFiles($testDir, '*.txt', false);
echo "当前目录的txt文件:\n";
foreach ($txtFiles as $file) {
    echo "  $file\n";
}

$allTxtFiles = PathHelper::findFiles($testDir, '*.txt', true);
echo "\n递归查找的txt文件:\n";
foreach ($allTxtFiles as $file) {
    echo "  $file\n";
}

// 清理测试目录
function removeDirectory($dir) {
    if (!is_dir($dir)) return;
    
    $files = array_diff(scandir($dir), ['.', '..']);
    foreach ($files as $file) {
        $path = $dir . DIRECTORY_SEPARATOR . $file;
        if (is_dir($path)) {
            removeDirectory($path);
        } else {
            unlink($path);
        }
    }
    rmdir($dir);
}

removeDirectory($testDir);
?>

9.8 本章小结

本章详细介绍了PHP中的文件系统操作,涵盖了以下主要内容:

核心知识点

  1. 文件基础操作

    • 文件信息获取(大小、权限、时间戳等)
    • 文件读取(一次性读取、逐行读取、按块读取)
    • 文件写入(覆盖、追加、二进制写入)
    • 文件指针操作和二进制文件处理
  2. 目录操作

    • 目录创建、删除、重命名
    • 目录遍历(递归和非递归)
    • 目录权限管理
    • 目录监控和变化检测
  3. 文件上传处理

    • 上传错误处理和验证
    • 安全的文件上传实现
    • 多文件上传处理
    • 文件类型和大小限制
  4. 文件权限与安全

    • 文件权限管理(读、写、执行)
    • 文件安全检查(文件名、内容、大小)
    • 危险文件类型识别
    • 文件名清理和验证
  5. 文件锁定与并发控制

    • 文件锁定机制(共享锁、独占锁)
    • 并发安全的文件操作
    • 安全计数器和日志系统实现
  6. 特殊文件格式处理

    • CSV文件读写和处理
    • JSON文件操作和配置管理
    • 数据过滤、排序和验证
  7. 路径操作

    • 路径规范化和连接
    • 绝对路径和相对路径转换
    • 文件查找和目录遍历
    • 跨平台路径处理

最佳实践

  1. 安全性

    • 始终验证用户输入的文件名和路径
    • 使用白名单验证文件类型
    • 设置适当的文件权限
    • 防止目录遍历攻击
  2. 性能优化

    • 使用适当的读写缓冲区大小
    • 对大文件使用流式处理
    • 合理使用文件锁定避免死锁
    • 缓存文件信息减少系统调用
  3. 错误处理

    • 检查文件操作的返回值
    • 使用异常处理机制
    • 提供有意义的错误信息
    • 实现优雅的降级处理
  4. 代码组织

    • 封装文件操作到专门的类中
    • 使用依赖注入提高可测试性
    • 分离业务逻辑和文件操作
    • 遵循单一职责原则

9.9 实践练习

练习1:文件备份系统

创建一个文件备份系统,要求: - 支持增量备份和完整备份 - 压缩备份文件 - 备份文件完整性验证 - 自动清理过期备份

练习2:日志分析工具

开发一个日志分析工具,功能包括: - 解析不同格式的日志文件 - 统计访问量、错误率等指标 - 生成分析报告 - 支持大文件流式处理

练习3:文件同步工具

实现一个简单的文件同步工具: - 比较源目录和目标目录的差异 - 同步新增、修改、删除的文件 - 处理文件冲突 - 支持排除规则

练习4:配置管理系统

构建一个配置管理系统: - 支持多种配置格式(JSON、YAML、INI) - 配置文件热重载 - 配置验证和默认值 - 环境变量覆盖

练习5:文件上传组件

开发一个完整的文件上传组件: - 支持拖拽上传 - 文件预览和进度显示 - 断点续传 - 图片自动压缩和缩略图生成

9.10 扩展阅读

  1. PHP官方文档

  2. 相关技术

    • SPL文件处理类
    • 流(Streams)处理
    • 文件系统监控
    • 分布式文件系统
  3. 安全资源

    • OWASP文件上传安全指南
    • 文件系统安全最佳实践
    • 路径遍历攻击防护

下一章预告:第十章将介绍《数据库操作》,包括PDO使用、SQL查询构建、事务处理、连接池管理等内容。 “`