首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 目錄遍歷完全指南:dir()、readdir()、RecursiveDirectoryIterator 多種方案對比

     編輯:PHP 相關知識

遍歷目錄獲取文件列表是 PHP 開發中的高頻需求:構建文件管理器、掃描上傳目錄、生成網站地圖、批量處理文件等。本文系統講解三種主流方案:傳統 dir()/readdir()、面向對象 DirectoryIterator、現代 RecursiveDirectoryIterator,並提供生產級封裝類。

方案一:傳統 dir() 函數(面向過程)

基本語法

resource dir(string $directory): Directory|false

返回 Directory 類實例,提供 read()、rewind()、close() 方法。

基礎示例

<?php
$dir = @dir('uploads'); // @ 抑制目錄不存在時的警告
if (!$dir) {
    die('目錄不存在或無權限');
}

echo "目錄: {$dir->path}\n";
while (($file = $dir->read()) !== false) {
    echo "文件: $file\n";
}
$dir->close();
?>

遞歸遍歷實現

<?php
function treeDir(string $path, int $level = 0): void {
    $dir = @dir($path);
    if (!$dir) return;
    
    while (($file = $dir->read()) !== false) {
        if ($file === '.' || $file === '..') continue;
        
        $fullPath = $dir->path . DIRECTORY_SEPARATOR . $file;
        $indent = str_repeat('  ', $level);
        
        if (is_dir($fullPath)) {
            echo "{$indent}📁 $file/\n";
            treeDir($fullPath, $level + 1);
        } else {
            $size = filesize($fullPath);
            echo "{$indent}📄 $file (" . formatBytes($size) . ")\n";
        }
    }
    $dir->close();
}

function formatBytes(int $bytes): string {
    $units = ['B', 'KB', 'MB', 'GB', 'TB'];
    $i = 0;
    while ($bytes >= 1024 && $i < count($units) - 1) {
        $bytes /= 1024;
        $i++;
    }
    return round($bytes, 2) . ' ' . $units[$i];
}

treeDir('./phpmyadmin');
?>

方案二:readdir() + opendir()(底層控制)

基本語法

resource opendir(string $path): resource|false
string|false readdir(resource $handle): string|false
void closedir(resource $handle): void

遞歸實現(生產級錯誤處理)

<?php
function listDirRecursive(string $path): array {
    $result = ['dirs' => [], 'files' => []];
    
    if (!is_dir($path)) {
        throw new InvalidArgumentException("無效目錄: $path");
    }
    
    $handle = @opendir($path);
    if (!$handle) {
        throw new RuntimeException("無法打開目錄: $path (權限不足?)");
    }
    
    try {
        while (($file = readdir($handle)) !== false) {
            if ($file === '.' || $file === '..') continue;
            
            $fullPath = $path . DIRECTORY_SEPARATOR . $file;
            
            if (is_dir($fullPath)) {
                $result['dirs'][$file] = listDirRecursive($fullPath);
            } else {
                $result['files'][$file] = [
                    'size' => filesize($fullPath),
                    'mtime' => filemtime($fullPath),
                    'mime' => mime_content_type($fullPath),
                ];
            }
        }
    } finally {
        closedir($handle); // 確保資源釋放
    }
    
    return $result;
}

// 使用
try {
    $tree = listDirRecursive('./data');
    print_r($tree);
} catch (Exception $e) {
    error_log("遍歷失敗: " . $e->getMessage());
}
?>

方案三:SPL 迭代器(現代、面向對象、推薦)

PHP 5.3+ 提供標準庫 (SPL) 迭代器,代碼更優雅、功能更強、自動實現 Countable、Iterator、SeekableIterator 接口。

DirectoryIterator:單層遍歷

<?php
$dir = new DirectoryIterator('./uploads');

foreach ($dir as $fileInfo) {
    if ($fileInfo->isDot()) continue; // 自動過濾 . 和 ..
    
    echo "名稱: {$fileInfo->getFilename()}\n";
    echo "路徑: {$fileInfo->getPathname()}\n";
    echo "大小: {$fileInfo->getSize()} bytes\n";
    echo "修改時間: " . date('Y-m-d H:i:s', $fileInfo->getMTime()) . "\n";
    echo "類型: " . ($fileInfo->isDir() ? '目錄' : '文件') . "\n";
    echo "權限: " . substr(sprintf('%o', $fileInfo->getPerms()), -4) . "\n";
    echo "---\n";
}
?>

RecursiveDirectoryIterator:遞歸遍歷(核心推薦)

<?php
$iterator = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator('./project', [
        RecursiveDirectoryIterator::SKIP_DOTS,        // 跳過 . 和 ..
        RecursiveDirectoryIterator::UNIX_PATHS,       // 統一使用 / 分隔符
    ]),
    RecursiveIteratorIterator::SELF_FIRST             // 先目錄後文件
);

foreach ($iterator as $path => $fileInfo) {
    $depth = $iterator->getDepth();
    $indent = str_repeat('  ', $depth);
    $type = $fileInfo->isDir() ? '📁' : '📄';
    $size = $fileInfo->isFile() ? formatBytes($fileInfo->getSize()) : '';
    echo "{$indent}{$type} {$fileInfo->getBasename()} {$size}\n";
}
?>

高級過濾:RegexIterator + FilterIterator

<?php
// 只獲取 .php 文件
$phpFiles = new RegexIterator(
    new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator('./src', RecursiveDirectoryIterator::SKIP_DOTS)
    ),
    '/\.php$/i'
);

foreach ($phpFiles as $file) {
    echo $file->getPathname() . "\n";
}

// 自定義過濾器:只獲取大於 1MB 的圖片
class LargeImageFilter extends FilterIterator {
    public function accept(): bool {
        return $this->current()->isFile() 
            && $this->current()->getSize() > 1024 * 1024
            && in_array($this->current()->getExtension(), ['jpg', 'png', 'gif', 'webp']);
    }
}

$largeImages = new LargeImageFilter(
    new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator('./uploads', RecursiveDirectoryIterator::SKIP_DOTS)
    )
);

foreach ($largeImages as $img) {
    echo "大圖: {$img->getPathname()} (" . formatBytes($img->getSize()) . ")\n";
}
?>

方案四:GlobIterator(模式匹配)

<?php
// 獲取所有 PHP 文件(非遞歸)
$phpFiles = new GlobIterator('*.php', GlobIterator::CURRENT_AS_FILEINFO);

foreach ($phpFiles as $file) {
    echo $file->getFilename() . "\n";
}

// 遞歸模式需配合 RecursiveDirectoryIterator
$allPhp = new RegexIterator(
    new RecursiveIteratorIterator(new RecursiveDirectoryIterator('.')),
    '/\.php$/'
);
?>

性能對比與選型建議

方案PHP 版本內存效率代碼複雜度功能豐富度推薦場景
dir()/readdir()4.0+低(需手動管理)低基礎舊項目維護、極簡腳本
DirectoryIterator5.0+高(惰性加載)低豐富(文件信息)單層目錄掃描、文件屬性讀取
RecursiveDirectoryIterator5.0+高(惰性加載)中最豐富(過濾、深度控制)遞歸遍歷、文件搜索、批量處理
glob()/scandir()4.3+/5.0+低(一次性加載數組)最低基礎小目錄、簡單匹配、快速原型

生產級封裝類:FileScanner

<?php
class FileScanner {
    public function scan(string $path, array $options = []): Generator {
        $options = array_merge([
            'recursive' => true,
            'skip_dots' => true,
            'follow_links' => false,
            'pattern' => null,        // 正則過濾
            'min_size' => null,       // 最小文件大小
            'max_size' => null,       // 最大文件大小
            'extensions' => null,     // 允許擴展名數組
            'callback' => null,       // 自定義過濾回調
        ], $options);
        
        $flags = 0;
        if ($options['skip_dots']) $flags |= RecursiveDirectoryIterator::SKIP_DOTS;
        if ($options['follow_links']) $flags |= RecursiveDirectoryIterator::FOLLOW_SYMLINKS;
        $flags |= RecursiveDirectoryIterator::UNIX_PATHS;
        
        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($path, $flags),
            RecursiveIteratorIterator::SELF_FIRST
        );
        
        if ($options['pattern']) {
            $iterator = new RegexIterator($iterator, $options['pattern']);
        }
        
        foreach ($iterator as $file) {
            if (!$file->isFile()) continue;
            
            // 大小過濾
            $size = $file->getSize();
            if ($options['min_size'] !== null && $size < $options['min_size']) continue;
            if ($options['max_size'] !== null && $size > $options['max_size']) continue;
            
            // 擴展名過濾
            if ($options['extensions'] && !in_array(strtolower($file->getExtension()), $options['extensions'])) continue;
            
            // 自定義回調
            if ($options['callback'] && !$options['callback']($file)) continue;
            
            yield $file->getPathname() => [
                'name' => $file->getBasename(),
                'path' => $file->getPathname(),
                'size' => $size,
                'mtime' => $file->getMTime(),
                'extension' => $file->getExtension(),
                'mime' => function_exists('mime_content_type') ? mime_content_type($file->getPathname()) : null,
            ];
        }
    }
    
    public function getDirectories(string $path): array {
        $dirs = [];
        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS),
            RecursiveIteratorIterator::SELF_FIRST
        );
        foreach ($iterator as $file) {
            if ($file->isDir()) {
                $dirs[] = $file->getPathname();
            }
        }
        return $dirs;
    }
}

// 使用示例
$scanner = new FileScanner();

// 掃描所有 PHP 文件
foreach ($scanner->scan('./src', ['pattern' => '/\.php$/']) as $path => $info) {
    echo "$path ({$info['size']} bytes)\n";
}

// 掃描大於 1MB 的圖片
foreach ($scanner->scan('./uploads', [
    'extensions' => ['jpg', 'png', 'webp'],
    'min_size' => 1024 * 1024,
]) as $path => $info) {
    echo "大圖: $path\n";
}

// 獲取所有子目錄
$dirs = $scanner->getDirectories('./project');
?>

最佳實踐總結

  1. 優先使用 SPL 迭代器:現代 PHP 標準、內存高效、功能強大
  2. 使用 Generator 封裝:大目錄遍歷時避免一次性加載所有結果到內存
  3. 善用過濾器鏈:FilterIterator + RegexIterator 組合實現複雜業務邏輯
  4. 異常處理:權限不足、符號鏈接循環、文件被刪除等邊界情況必須捕獲
  5. 符號鏈接謹慎處理:FOLLOW_SYMLINKS 可能導致無限循環,建議默認關閉
  6. 跨平台路徑:使用 DIRECTORY_SEPARATOR 或 UNIX_PATHS 標誌

掌握 PHP 目錄遍歷的多種方案,根據項目 PHP 版本、目錄規模、業務複雜度選擇最適合的工具,編寫高效、健壯、可維護的文件系統操作代碼。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved