目录操作

目录操作是文件系统管理的重要组成部分。在PHP中,我们可以创建、删除、遍历和管理目录,这对于构建文件管理系统、内容管理系统和其他需要组织文件的应用程序非常重要。

学习目标

完成本节学习后,你将能够:

  • 创建和删除目录
  • 遍历目录内容
  • 管理目录权限
  • 递归操作目录树
  • 构建目录管理工具

目录的基本操作

1. 创建目录

使用 mkdir() 函数可以创建新目录。

<?php
// 基本目录创建
$dirName = 'new_directory';

if (mkdir($dirName)) {
    echo "目录 '{$dirName}' 创建成功<br>";
} else {
    echo "目录 '{$dirName}' 创建失败<br>";
}

// 创建多层目录(递归创建)
$nestedDir = 'path/to/nested/directory';

if (mkdir($nestedDir, 0755, true)) {
    echo "多层目录 '{$nestedDir}' 创建成功<br>";
} else {
    echo "多层目录 '{$nestedDir}' 创建失败<br>";
}

// 设置权限创建目录
$secureDir = 'secure_directory';
$permissions = 0755; // rwxr-xr-x (所有者可读写执行,组和其他用户可读执行)

if (mkdir($secureDir, $permissions)) {
    echo "安全目录 '{$secureDir}' 创建成功,权限:" . decoct($permissions) . "<br>";
}
?>

2. 检查目录

<?php
// 目录检查函数
function checkDirectory($path) {
    echo "检查路径:{$path}<br>";
    echo "是否存在:" . (file_exists($path) ? "是" : "否") . "<br>";

    if (file_exists($path)) {
        echo "是否为目录:" . (is_dir($path) ? "是" : "否") . "<br>";
        echo "是否可读:" . (is_readable($path) ? "是" : "否") . "<br>";
        echo "是否可写:" . (is_writable($path) ? "是" : "否") . "<br>";

        if (is_dir($path)) {
            $permissions = fileperms($path);
            echo "权限:" . substr(sprintf('%o', $permissions), -4) . "<br>";
            echo "修改时间:" . date('Y-m-d H:i:s', filemtime($path)) . "<br>";
        }
    }
    echo "<hr>";
}

// 测试不同路径
checkDirectory('new_directory');
checkDirectory('nonexistent_directory');
checkDirectory('.');
checkDirectory(__DIR__);
?>

3. 删除目录

<?php
// 删除空目录
$emptyDir = 'empty_directory';
mkdir($emptyDir);

echo "目录 '{$emptyDir}' 是否存在:" . (file_exists($emptyDir) ? "是" : "否") . "<br>";

if (rmdir($emptyDir)) {
    echo "目录 '{$emptyDir}' 删除成功<br>";
} else {
    echo "目录 '{$emptyDir}' 删除失败(可能不为空或权限不足)<br>";
}

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

    if (!is_dir($dir)) {
        return unlink($dir);
    }

    foreach (scandir($dir) as $item) {
        if ($item == '.' || $item == '..') {
            continue;
        }

        $itemPath = $dir . DIRECTORY_SEPARATOR . $item;
        if (!removeDirectory($itemPath)) {
            return false;
        }
    }

    return rmdir($dir);
}

// 测试递归删除
$testDir = 'test_remove_dir';
mkdir($testDir, 0755, true);
file_put_contents($testDir . '/file1.txt', 'test content 1');
mkdir($testDir . '/subdir');
file_put_contents($testDir . '/subdir/file2.txt', 'test content 2');

echo "递归删除目录 '{$testDir}':<br>";
if (removeDirectory($testDir)) {
    echo "删除成功<br>";
} else {
    echo "删除失败<br>";
}
?>

遍历目录内容

1. 使用 scandir()

<?php
// 基本目录扫描
function listDirectoryContents($dir) {
    if (!is_dir($dir)) {
        echo "路径不是有效目录:{$dir}<br>";
        return;
    }

    echo "<h3>目录内容:{$dir}</h3>";

    $items = scandir($dir);

    echo "<ul>";
    foreach ($items as $item) {
        if ($item === '.' || $item === '..') {
            continue;
        }

        $itemPath = $dir . DIRECTORY_SEPARATOR . $item;
        $isDir = is_dir($itemPath);

        $icon = $isDir ? "📁" : "📄";
        $type = $isDir ? "目录" : "文件";
        $size = $isDir ? "-" : number_format(filesize($itemPath)) . " 字节";
        $modified = date('Y-m-d H:i:s', filemtime($itemPath));

        echo "<li>";
        echo "{$icon} <strong>" . htmlspecialchars($item) . "</strong> - {$type}";
        echo " (大小: {$size}, 修改: {$modified})";
        echo "</li>";
    }
    echo "</ul>";
}

// 列出当前目录内容
listDirectoryContents('.');
?>

2. 使用 DirectoryIterator

<?php
// 使用 DirectoryIterator 遍历目录
function listDirectoryWithIterator($dir) {
    if (!is_dir($dir)) {
        echo "路径不是有效目录:{$dir}<br>";
        return;
    }

    echo "<h3>使用 DirectoryIterator 遍历:{$dir}</h3>";

    $iterator = new DirectoryIterator($dir);

    echo "<table border='1' style='border-collapse: collapse; width: 100%;'>";
    echo "<tr><th>名称</th><th>类型</th><th>大小</th><th>修改时间</th><th>权限</th></tr>";

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

        $name = htmlspecialchars($fileInfo->getFilename());
        $type = $fileInfo->isDir() ? '目录' : '文件';
        $size = $fileInfo->isFile() ? number_format($fileInfo->getSize()) . ' 字节' : '-';
        $modified = $fileInfo->getMTime() ? date('Y-m-d H:i:s', $fileInfo->getMTime()) : '-';
        $permissions = substr(sprintf('%o', $fileInfo->getPerms()), -4);

        echo "<tr>";
        echo "<td>{$name}</td>";
        echo "<td>{$type}</td>";
        echo "<td>{$size}</td>";
        echo "<td>{$modified}</td>";
        echo "<td>{$permissions}</td>";
        echo "</tr>";
    }

    echo "</table>";
}

// 使用迭代器列出当前目录
listDirectoryWithIterator('.');
?>

3. 递归遍历目录树

<?php
// 递归遍历目录树
class DirectoryTreeWalker {
    private $maxDepth;
    private $showHidden;
    private $results;

    public function __construct($maxDepth = 5, $showHidden = false) {
        $this->maxDepth = $maxDepth;
        $this->showHidden = $showHidden;
        $this->results = [];
    }

    public function walk($dir, $currentDepth = 0) {
        if ($currentDepth >= $this->maxDepth) {
            return;
        }

        if (!is_dir($dir)) {
            return;
        }

        $items = scandir($dir);

        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }

            if (!$this->showHidden && strpos($item, '.') === 0) {
                continue;
            }

            $itemPath = $dir . DIRECTORY_SEPARATOR . $item;
            $isDir = is_dir($itemPath);

            $this->results[] = [
                'path' => $itemPath,
                'name' => $item,
                'type' => $isDir ? 'directory' : 'file',
                'size' => $isDir ? 0 : filesize($itemPath),
                'depth' => $currentDepth,
                'modified' => filemtime($itemPath)
            ];

            if ($isDir) {
                $this->walk($itemPath, $currentDepth + 1);
            }
        }
    }

    public function getResults() {
        return $this->results;
    }

    public function displayTree($dir) {
        echo "<h3>目录树:{$dir}</h3>";
        echo "<pre style='background: #f5f5f5; padding: 10px; font-family: monospace;'>";
        $this->displayTreeRecursive($dir, '', 0);
        echo "</pre>";
    }

    private function displayTreeRecursive($dir, $prefix, $currentDepth) {
        if ($currentDepth >= $this->maxDepth) {
            return;
        }

        if (!is_dir($dir)) {
            return;
        }

        $items = scandir($dir);
        $dirs = [];
        $files = [];

        // 分别处理目录和文件
        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }

            if (!$this->showHidden && strpos($item, '.') === 0) {
                continue;
            }

            $itemPath = $dir . DIRECTORY_SEPARATOR . $item;
            if (is_dir($itemPath)) {
                $dirs[] = $item;
            } else {
                $files[] = $item;
            }
        }

        // 先显示目录,再显示文件
        $allItems = array_merge($dirs, $files);
        $count = count($allItems);

        foreach ($allItems as $index => $item) {
            $itemPath = $dir . DIRECTORY_SEPARATOR . $item;
            $isLast = ($index === $count - 1);
            $currentPrefix = $prefix . ($isLast ? '└── ' : '├── ');

            echo $currentPrefix . $item;

            if (is_dir($itemPath)) {
                echo "/<br>";
                $nextPrefix = $prefix . ($isLast ? '    ' : '│   ');
                $this->displayTreeRecursive($itemPath, $nextPrefix, $currentDepth + 1);
            } else {
                $size = number_format(filesize($itemPath));
                echo " ({$size} 字节)<br>";
            }
        }
    }
}

// 使用目录树遍历器
$walker = new DirectoryTreeWalker(3, true);
$walker->walk('.');
$allItems = $walker->getResults();

echo "<h3>统计信息:</h3>";
$fileCount = 0;
$dirCount = 0;
$totalSize = 0;

foreach ($allItems as $item) {
    if ($item['type'] === 'file') {
        $fileCount++;
        $totalSize += $item['size'];
    } else {
        $dirCount++;
    }
}

echo "文件数量:{$fileCount}<br>";
echo "目录数量:{$dirCount}<br>";
echo "总大小:" . number_format($totalSize) . " 字节<br>";

// 显示目录树
$walker->displayTree('.');
?>

目录管理工具

1. 目录复制

<?php
// 目录复制类
class DirectoryCopier {
    private $overwrite;
    private $preservePermissions;

    public function __construct($overwrite = false, $preservePermissions = true) {
        $this->overwrite = $overwrite;
        $this->preservePermissions = $preservePermissions;
    }

    public function copy($source, $destination) {
        if (!is_dir($source)) {
            throw new Exception("源目录不存在:{$source}");
        }

        // 创建目标目录
        if (!is_dir($destination)) {
            if (!mkdir($destination, 0755, true)) {
                throw new Exception("无法创建目标目录:{$destination}");
            }
        }

        return $this->copyRecursive($source, $destination);
    }

    private function copyRecursive($source, $destination) {
        $items = scandir($source);

        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }

            $sourcePath = $source . DIRECTORY_SEPARATOR . $item;
            $destPath = $destination . DIRECTORY_SEPARATOR . $item;

            if (is_dir($sourcePath)) {
                // 复制子目录
                if (!is_dir($destPath)) {
                    if (!mkdir($destPath, 0755, true)) {
                        throw new Exception("无法创建子目录:{$destPath}");
                    }
                }

                $this->copyRecursive($sourcePath, $destPath);

                // 保持权限
                if ($this->preservePermissions) {
                    chmod($destPath, fileperms($sourcePath));
                }

            } else {
                // 复制文件
                if (file_exists($destPath) && !$this->overwrite) {
                    continue;
                }

                if (!copy($sourcePath, $destPath)) {
                    throw new Exception("无法复制文件:{$sourcePath} -> {$destPath}");
                }

                // 保持权限
                if ($this->preservePermissions) {
                    chmod($destPath, fileperms($sourcePath));
                }
            }
        }

        return true;
    }

    public function getCopyStats($source, $destination) {
        $stats = [
            'files_copied' => 0,
            'dirs_created' => 0,
            'bytes_copied' => 0
        ];

        $this->calculateStats($source, $destination, $stats);
        return $stats;
    }

    private function calculateStats($source, $destination, &$stats) {
        if (!is_dir($source)) {
            return;
        }

        $items = scandir($source);

        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }

            $sourcePath = $source . DIRECTORY_SEPARATOR . $item;
            $destPath = $destination . DIRECTORY_SEPARATOR . $item;

            if (is_dir($sourcePath)) {
                if (!is_dir($destPath)) {
                    $stats['dirs_created']++;
                }
                $this->calculateStats($sourcePath, $destPath, $stats);
            } else {
                if (file_exists($destPath)) {
                    $sourceSize = filesize($sourcePath);
                    $destSize = filesize($destPath);
                    if ($sourceSize === $destSize) {
                        continue; // 文件已存在且大小相同
                    }
                }

                $stats['files_copied']++;
                $stats['bytes_copied'] += filesize($sourcePath);
            }
        }
    }
}

// 使用目录复制工具
try {
    // 创建测试源目录
    $sourceDir = 'test_source';
    if (!is_dir($sourceDir)) {
        mkdir($sourceDir, 0755, true);
    }

    // 创建一些测试文件
    file_put_contents($sourceDir . '/file1.txt', 'Test file 1 content');
    file_put_contents($sourceDir . '/file2.txt', 'Test file 2 content');
    mkdir($sourceDir . '/subdir');
    file_put_contents($sourceDir . '/subdir/file3.txt', 'Test file 3 content');

    $copier = new DirectoryCopier(true, true);
    $destinationDir = 'test_copy';

    // 执行复制
    $copier->copy($sourceDir, $destinationDir);
    echo "目录复制成功<br>";

    // 显示统计信息
    $stats = $copier->getCopyStats($sourceDir, $destinationDir);
    echo "<h3>复制统计:</h3>";
    echo "复制的文件:{$stats['files_copied']}<br>";
    echo "创建的目录:{$stats['dirs_created']}<br>";
    echo "复制的字节:" . number_format($stats['bytes_copied']) . "<br>";

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

2. 目录同步

<?php
// 目录同步类
class DirectorySynchronizer {
    private $source;
    private $destination;
    private $deleteExtra;
    private $log;

    public function __construct($source, $destination, $deleteExtra = false) {
        $this->source = rtrim($source, '/\\');
        $this->destination = rtrim($destination, '/\\');
        $this->deleteExtra = $deleteExtra;
        $this->log = [];
    }

    public function sync() {
        if (!is_dir($this->source)) {
            throw new Exception("源目录不存在:{$this->source}");
        }

        // 确保目标目录存在
        if (!is_dir($this->destination)) {
            mkdir($this->destination, 0755, true);
            $this->addLog("创建目标目录:{$this->destination}");
        }

        $this->syncRecursive($this->source, $this->destination);

        return $this->log;
    }

    private function syncRecursive($sourceDir, $destDir) {
        // 确保目标目录存在
        if (!is_dir($destDir)) {
            mkdir($destDir, 0755, true);
            $this->addLog("创建目录:" . $this->getRelativePath($destDir));
        }

        $sourceItems = scandir($sourceDir);
        $destItems = is_dir($destDir) ? scandir($destDir) : [];

        // 同步源目录中的项目
        foreach ($sourceItems as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }

            $sourcePath = $sourceDir . DIRECTORY_SEPARATOR . $item;
            $destPath = $destDir . DIRECTORY_SEPARATOR . $item;

            if (is_dir($sourcePath)) {
                // 递归同步子目录
                $this->syncRecursive($sourcePath, $destPath);
            } else {
                // 同步文件
                $this->syncFile($sourcePath, $destPath);
            }
        }

        // 删除目标目录中多余的项目
        if ($this->deleteExtra) {
            foreach ($destItems as $item) {
                if ($item === '.' || $item === '..') {
                    continue;
                }

                $sourcePath = $sourceDir . DIRECTORY_SEPARATOR . $item;
                $destPath = $destDir . DIRECTORY_SEPARATOR . $item;

                if (!file_exists($sourcePath)) {
                    if (is_dir($destPath)) {
                        $this->deleteDirectoryRecursive($destPath);
                        $this->addLog("删除多余目录:" . $this->getRelativePath($destPath));
                    } else {
                        unlink($destPath);
                        $this->addLog("删除多余文件:" . $this->getRelativePath($destPath));
                    }
                }
            }
        }
    }

    private function syncFile($sourceFile, $destFile) {
        $needsUpdate = false;

        if (!file_exists($destFile)) {
            $needsUpdate = true;
            $action = "复制新文件";
        } elseif (filemtime($sourceFile) > filemtime($destFile)) {
            $needsUpdate = true;
            $action = "更新文件";
        } elseif (filesize($sourceFile) !== filesize($destFile)) {
            $needsUpdate = true;
            $action = "同步文件大小";
        }

        if ($needsUpdate) {
            if (copy($sourceFile, $destFile)) {
                touch($destFile, filemtime($sourceFile)); // 保持修改时间
                $this->addLog($action . ":" . $this->getRelativePath($destFile));
            } else {
                $this->addLog("同步失败:" . $this->getRelativePath($destFile));
            }
        }
    }

    private function deleteDirectoryRecursive($dir) {
        if (!is_dir($dir)) {
            return;
        }

        $items = scandir($dir);
        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }

            $itemPath = $dir . DIRECTORY_SEPARATOR . $item;
            if (is_dir($itemPath)) {
                $this->deleteDirectoryRecursive($itemPath);
            } else {
                unlink($itemPath);
            }
        }

        rmdir($dir);
    }

    private function getRelativePath($path) {
        $basePath = dirname($this->destination);
        return str_replace($basePath . DIRECTORY_SEPARATOR, '', $path);
    }

    private function addLog($message) {
        $this->log[] = date('Y-m-d H:i:s') . ' - ' . $message;
    }

    public function getLog() {
        return $this->log;
    }
}

// 使用目录同步工具
try {
    // 创建测试环境
    $sourceDir = 'sync_source';
    $destDir = 'sync_dest';

    if (!is_dir($sourceDir)) {
        mkdir($sourceDir, 0755, true);
        file_put_contents($sourceDir . '/test.txt', 'Original content');
        mkdir($sourceDir . '/subdir');
        file_put_contents($sourceDir . '/subdir/nested.txt', 'Nested content');
    }

    if (!is_dir($destDir)) {
        mkdir($destDir, 0755, true);
        file_put_contents($destDir . '/old_file.txt', 'Old content');
    }

    $synchronizer = new DirectorySynchronizer($sourceDir, $destDir, true);
    $log = $synchronizer->sync();

    echo "<h3>同步日志:</h3>";
    echo "<ul>";
    foreach ($log as $entry) {
        echo "<li>" . htmlspecialchars($entry) . "</li>";
    }
    echo "</ul>";

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

实用工具

1. 目录大小分析器

<?php
// 目录大小分析器
class DirectoryAnalyzer {
    private $excludePatterns;
    private $maxDepth;
    private $results;

    public function __construct($excludePatterns = [], $maxDepth = 10) {
        $this->excludePatterns = array_merge([
            '/^\./',          // 隐藏文件
            '/\.(git|svn)$/', // 版本控制目录
            '/node_modules/', // Node.js依赖
            '/vendor/',       // Composer依赖
        ], $excludePatterns);

        $this->maxDepth = $maxDepth;
        $this->results = [];
    }

    public function analyze($directory) {
        if (!is_dir($directory)) {
            throw new Exception("目录不存在:{$directory}");
        }

        $this->results = [
            'total_size' => 0,
            'file_count' => 0,
            'dir_count' => 0,
            'extensions' => [],
            'largest_files' => [],
            'directory_sizes' => []
        ];

        $this->analyzeRecursive($directory, 0);

        return $this->results;
    }

    private function analyzeRecursive($directory, $currentDepth) {
        if ($currentDepth >= $this->maxDepth) {
            return 0;
        }

        $dirSize = 0;
        $items = scandir($directory);

        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }

            // 检查排除模式
            if ($this->shouldExclude($item)) {
                continue;
            }

            $path = $directory . DIRECTORY_SEPARATOR . $item;

            if (is_dir($path)) {
                $this->results['dir_count']++;
                $subDirSize = $this->analyzeRecursive($path, $currentDepth + 1);
                $dirSize += $subDirSize;

                $relativePath = $this->getRelativePath($path);
                $this->results['directory_sizes'][$relativePath] = $subDirSize;

            } elseif (is_file($path)) {
                $fileSize = filesize($path);
                $dirSize += $fileSize;
                $this->results['file_count']++;

                // 统计扩展名
                $extension = strtolower(pathinfo($path, PATHINFO_EXTENSION));
                if ($extension) {
                    if (!isset($this->results['extensions'][$extension])) {
                        $this->results['extensions'][$extension] = [
                            'count' => 0,
                            'size' => 0
                        ];
                    }
                    $this->results['extensions'][$extension]['count']++;
                    $this->results['extensions'][$extension]['size'] += $fileSize;
                }

                // 记录大文件
                $this->results['largest_files'][] = [
                    'path' => $this->getRelativePath($path),
                    'size' => $fileSize
                ];
            }
        }

        return $dirSize;
    }

    private function shouldExclude($name) {
        foreach ($this->excludePatterns as $pattern) {
            if (preg_match($pattern, $name)) {
                return true;
            }
        }
        return false;
    }

    private function getRelativePath($path) {
        $basePath = getcwd();
        return str_replace($basePath . DIRECTORY_SEPARATOR, '', $path);
    }

    public function displayResults() {
        echo "<h2>目录分析报告</h2>";

        // 总体统计
        echo "<h3>总体统计:</h3>";
        echo "<ul>";
        echo "<li>总大小:" . $this->formatBytes($this->results['total_size']) . "</li>";
        echo "<li>文件数量:" . $this->results['file_count'] . "</li>";
        echo "<li>目录数量:" . $this->results['dir_count'] . "</li>";
        echo "<li>平均文件大小:" . $this->formatBytes($this->results['total_size'] / max(1, $this->results['file_count'])) . "</li>";
        echo "</ul>";

        // 文件类型统计
        if (!empty($this->results['extensions'])) {
            echo "<h3>文件类型统计:</h3>";
            echo "<table border='1' style='border-collapse: collapse;'>";
            echo "<tr><th>扩展名</th><th>文件数</th><th>总大小</th><th>平均大小</th><th>占比</th></tr>";

            // 按大小排序
            uasort($this->results['extensions'], function($a, $b) {
                return $b['size'] - $a['size'];
            });

            foreach ($this->results['extensions'] as $ext => $stats) {
                $percentage = round(($stats['size'] / $this->results['total_size']) * 100, 2);
                $avgSize = $this->formatBytes($stats['size'] / $stats['count']);

                echo "<tr>";
                echo "<td>.{$ext}</td>";
                echo "<td>{$stats['count']}</td>";
                echo "<td>" . $this->formatBytes($stats['size']) . "</td>";
                echo "<td>{$avgSize}</td>";
                echo "<td>{$percentage}%</td>";
                echo "</tr>";
            }
            echo "</table>";
        }

        // 最大文件
        if (!empty($this->results['largest_files'])) {
            echo "<h3>最大文件(前10个):</h3>";

            // 按大小排序并取前10个
            usort($this->results['largest_files'], function($a, $b) {
                return $b['size'] - $a['size'];
            });

            $topFiles = array_slice($this->results['largest_files'], 0, 10);

            echo "<table border='1' style='border-collapse: collapse;'>";
            echo "<tr><th>文件</th><th>大小</th></tr>";

            foreach ($topFiles as $file) {
                echo "<tr>";
                echo "<td>" . htmlspecialchars($file['path']) . "</td>";
                echo "<td>" . $this->formatBytes($file['size']) . "</td>";
                echo "</tr>";
            }
            echo "</table>";
        }

        // 目录大小
        if (!empty($this->results['directory_sizes'])) {
            echo "<h3>目录大小(前10个):</h3>";

            // 按大小排序并取前10个
            arsort($this->results['directory_sizes']);
            $topDirs = array_slice($this->results['directory_sizes'], 0, 10, true);

            echo "<table border='1' style='border-collapse: collapse;'>";
            echo "<tr><th>目录</th><th>大小</th></tr>";

            foreach ($topDirs as $dir => $size) {
                echo "<tr>";
                echo "<td>" . htmlspecialchars($dir) . "</td>";
                echo "<td>" . $this->formatBytes($size) . "</td>";
                echo "</tr>";
            }
            echo "</table>";
        }
    }

    private function formatBytes($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];
    }
}

// 使用目录分析器
try {
    $analyzer = new DirectoryAnalyzer([], 5);
    $results = $analyzer->analyze('.');

    // 设置结果(analyze方法已经更新了results)
    $analyzer->results = $results;
    $analyzer->displayResults();

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

2. 目录清理工具

<?php
// 目录清理工具
class DirectoryCleaner {
    private $rules;
    private $dryRun;
    private $deleted;
    private $errors;

    public function __construct($dryRun = true) {
        $this->dryRun = $dryRun;
        $this->rules = [];
        $this->deleted = [];
        $this->errors = [];
    }

    // 添加清理规则
    public function addRule($name, $callback) {
        $this->rules[$name] = $callback;
    }

    // 清理目录
    public function clean($directory) {
        if (!is_dir($directory)) {
            throw new Exception("目录不存在:{$directory}");
        }

        $this->deleted = [];
        $this->errors = [];

        $this->cleanRecursive($directory);

        return [
            'deleted' => $this->deleted,
            'errors' => $this->errors,
            'dry_run' => $this->dryRun
        ];
    }

    private function cleanRecursive($directory) {
        $items = scandir($directory);

        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }

            $path = $directory . DIRECTORY_SEPARATOR . $item;

            if (is_dir($path)) {
                $this->cleanRecursive($path);

                // 尝试删除空目录
                $this->tryDeleteEmptyDirectory($path);
            } else {
                $this->tryDeleteFile($path);
            }
        }
    }

    private function tryDeleteFile($path) {
        foreach ($this->rules as $ruleName => $rule) {
            try {
                if ($rule($path)) {
                    if ($this->dryRun) {
                        $this->deleted[] = [
                            'path' => $path,
                            'rule' => $ruleName,
                            'action' => 'would_delete'
                        ];
                    } else {
                        if (unlink($path)) {
                            $this->deleted[] = [
                                'path' => $path,
                                'rule' => $ruleName,
                                'action' => 'deleted'
                            ];
                        } else {
                            $this->errors[] = [
                                'path' => $path,
                                'message' => '无法删除文件'
                            ];
                        }
                    }
                    break; // 一旦匹配一个规则就停止
                }
            } catch (Exception $e) {
                $this->errors[] = [
                    'path' => $path,
                    'message' => $e->getMessage()
                ];
            }
        }
    }

    private function tryDeleteEmptyDirectory($path) {
        $items = scandir($path);
        if (count($items) === 2) { // 只有 . 和 ..
            if ($this->dryRun) {
                $this->deleted[] = [
                    'path' => $path,
                    'rule' => 'empty_directory',
                    'action' => 'would_delete'
                ];
            } else {
                if (rmdir($path)) {
                    $this->deleted[] = [
                        'path' => $path,
                        'rule' => 'empty_directory',
                        'action' => 'deleted'
                    ];
                }
            }
        }
    }

    public function setDryRun($dryRun) {
        $this->dryRun = $dryRun;
    }

    public function displayResults() {
        $action = $this->dryRun ? '将要删除' : '已删除';

        echo "<h3>清理结果({$action})</h3>";

        if (!empty($this->deleted)) {
            echo "<p>文件/目录数量:" . count($this->deleted) . "</p>";
            echo "<ul>";
            foreach ($this->deleted as $item) {
                $rule = $item['rule'] === 'empty_directory' ? '空目录' : $item['rule'];
                echo "<li>" . htmlspecialchars($item['path']) . " (规则: {$rule})</li>";
            }
            echo "</ul>";
        } else {
            echo "<p>没有文件或目录被标记为删除</p>";
        }

        if (!empty($this->errors)) {
            echo "<h4>错误:</h4>";
            echo "<ul>";
            foreach ($this->errors as $error) {
                echo "<li>" . htmlspecialchars($error['path']) . ": " . htmlspecialchars($error['message']) . "</li>";
            }
            echo "</ul>";
        }
    }
}

// 创建清理工具并添加规则
$cleaner = new DirectoryCleaner(true); // 默认为试运行模式

// 添加常见清理规则
$cleaner->addRule('temp_files', function($path) {
    $name = basename($path);
    $tempPatterns = ['~$', '.tmp', '.temp', '.bak', '.swp', '.log'];
    foreach ($tempPatterns as $pattern) {
        if (strpos($name, $pattern) !== false) {
            return true;
        }
    }
    return false;
});

$cleaner->addRule('old_logs', function($path) {
    $ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
    if ($ext === 'log') {
        // 删除7天前的日志文件
        $mtime = filemtime($path);
        return ($mtime < (time() - 7 * 24 * 60 * 60));
    }
    return false;
});

$cleaner->addRule('old_cache', function($path) {
    $path = strtolower($path);
    if (strpos($path, 'cache') !== false || strpos($path, 'temp') !== false) {
        $mtime = filemtime($path);
        // 删除30天前的缓存文件
        return ($mtime < (time() - 30 * 24 * 60 * 60));
    }
    return false;
});

$cleaner->addRule('php_sessions', function($path) {
    $name = basename($path);
    // 删除旧的PHP会话文件
    return (strpos($name, 'sess_') === 0 && filemtime($path) < (time() - 24 * 60 * 60));
});

// 运行清理(试运行模式)
echo "<h2>目录清理(试运行模式)</h2>";
echo "<p>注意:这是试运行模式,没有实际删除任何文件</p>";

try {
    $results = $cleaner->clean('.');
    $cleaner->displayResults();

    echo "<p>要实际删除文件,请将 dryRun 参数设置为 false</p>";

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

总结

目录操作是PHP文件系统编程的重要组成部分。通过本节学习,你应该掌握:

关键技能:

  1. 基本目录操作

    • 创建、删除、检查目录
    • 设置和管理目录权限
    • 处理嵌套目录结构
  2. 目录遍历

    • 使用 scandir() 进行基本遍历
    • 使用 DirectoryIterator 进行面向对象遍历
    • 实现递归目录树遍历
  3. 实用工具

    • 目录复制和同步
    • 目录大小分析
    • 自动化清理工具
  4. 最佳实践

    • 错误处理和异常管理
    • 权限检查和安全考虑
    • 性能优化和内存管理

这些技能将帮助你构建功能完整的文件管理系统和内容管理平台。