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

PHP SPL 標準庫實戰:RecursiveDirectoryIterator 遞歸遍歷目錄與檔案過濾

     編輯:PHP 綜合知識

前言

PHP SPL(Standard PHP Library)提供了一組強大的迭代器,能以物件導向、記憶體高效、可組合的方式處理目錄遍歷、檔案過濾、資料流處理等任務。本文以遞歸查找指定副檔名圖片為例,深度解析 RecursiveDirectoryIterator、RecursiveIteratorIterator、FilterIterator 的組合用法。

核心類別關係圖


RecursiveDirectoryIterator (實現 RecursiveIterator)
    │
    ▼
RecursiveIteratorIterator (實現 Iterator, OuterIterator)
    │  ├─ 遍歷遞歸結構,展平為線性序列
    │  └─ 模式:LEAVES_ONLY / SELF_FIRST / CHILD_FIRST
    ▼
FilterIterator (抽象類,實現 Iterator)
    │  ├─ accept():決定是否保留當前元素
    │  └─ 可鏈式組合多個過濾器
    ▼
你的具體過濾類 (extends FilterIterator)

完整實現:查找 jpg/gif 圖片

<?php
class RecursiveFileFilterIterator extends FilterIterator {
    /** @var string[] 允許的副檔名(小寫) */
    protected array $allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];

    public function __construct(string $path) {
        // 1. 目錄遞歸迭代器
        $dirIter = new RecursiveDirectoryIterator(
            $path,
            RecursiveDirectoryIterator::SKIP_DOTS // 忽略 . 和 ..
        );
        // 2. 遞歸迭代器展平器(只取葉子節點 = 檔案)
        $recursiveIter = new RecursiveIteratorIterator(
            $dirIter,
            RecursiveIteratorIterator::LEAVES_ONLY
        );
        // 3. 父類 FilterIterator 接收外層迭代器
        parent::__construct($recursiveIter);
    }

    /** 核心過濾邏輯:只保留檔案且副檔名符合 */
    public function accept(): bool {
        /** @var SplFileInfo $current */
        $current = $this->getInnerIterator()->current();
        
        if (!$current->isFile()) return false;
        
        $ext = strtolower($current->getExtension());
        return in_array($ext, $this->allowedExtensions, true);
    }

    /** 可選:自訂鍵名(預設為路徑字串) */
    public function key(): string {
        return $this->getInnerIterator()->key(); // 完整路徑
    }

    /** 可選:自訂當前值(預設為 SplFileInfo 物件) */
    public function current(): SplFileInfo {
        return $this->getInnerIterator()->current();
    }
}

// 使用範例
$iterator = new RecursiveFileFilterIterator('/var/www/uploads');

foreach ($iterator as $fileInfo) {
    // $fileInfo 是 SplFileInfo 物件,豐富 API:
    echo "檔案: {$fileInfo->getPathname()}\n";
    echo "大小: {$fileInfo->getSize()} bytes\n";
    echo "修改時間: " . $fileInfo->getMTime() . "\n";
    echo "副檔名: {$fileInfo->getExtension()}\n";
    echo "MIME 類型: " . mime_content_type($fileInfo->getPathname()) . "\n";
    echo "---\n";
}
?>

進階擴展:多條件組合過濾

<?php
class CompositeFileFilterIterator extends FilterIterator {
    public function __construct(Iterator $iterator, private array $filters) {
        parent::__construct($iterator);
    }

    public function accept(): bool {
        foreach ($this->filters as $filter) {
            if (!$filter($this->current())) return false;
        }
        return true;
    }
}

// 定義獨立過濾器(可複用、可測試)
$filters = [
    fn(SplFileInfo $f) => $f->isFile(),
    fn(SplFileInfo $f) => in_array(strtolower($f->getExtension()), ['jpg','png']),
    fn(SplFileInfo $f) => $f->getSize() > 1024, // > 1KB
    fn(SplFileInfo $f) => $f->getMTime() > strtotime('-7 days'), // 7 天內修改
];

$baseIter = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator('/var/www/uploads', RecursiveDirectoryIterator::SKIP_DOTS),
    RecursiveIteratorIterator::LEAVES_ONLY
);

$filtered = new CompositeFileFilterIterator($baseIter, $filters);

foreach ($filtered as $file) {
    // 處理符合所有條件的檔案
}
?>

常用 SPL 迭代器速查表

類別用途關鍵方法/常數
DirectoryIterator單層目錄遍歷isFile(), isDir(), getExtension()
RecursiveDirectoryIterator遞歸目錄遍歷SKIP_DOTS, FOLLOW_SYMLINKS
RecursiveIteratorIterator展平遞歸結構LEAVES_ONLY, SELF_FIRST, CHILD_FIRST, getDepth()
FilterIterator過濾迭代器元素抽象 accept()
CallbackFilterIterator用閉包快速過濾new CallbackFilterIterator($it, fn($v) => ...)
RegexIterator正則過濾路徑/檔名new RegexIterator($it, '/\.jpg$/i')
LimitIterator分頁/切片new LimitIterator($it, $offset, $count)
NoRewindIterator禁止重置(單向流)包裝不可重置的資源
AppendIterator合併多個迭代器$it->append($otherIter)

效能與記憶體優勢

  • 惰性求值:只有 foreach 實際迭代時才讀取檔案系統,不會一次性載入所有檔名到陣列。
  • 常數記憶體:處理百萬級檔案目錄,記憶體佔用穩定在極低水位。
  • 可中斷:break 即可停止遍歷,釋放資源。

常見坑點

問題解決
符號連結導致無限遞歸建構子傳入 RecursiveDirectoryIterator::FOLLOW_SYMLINKS 旗標(預設不跟隨),或自行在 accept() 中檢查 $current->isLink()
權限不足拋出例外中斷遍歷包裝 try-catch 在 accept() 內,或使用 CallbackFilterIterator 捕獲
Windows 路徑分隔符不一致使用 $fileInfo->getPathname() 取得系統原生路徑,或 DIRECTORY_SEPARATOR
大量檔案 getMTime() 等呼叫慢SplFileInfo 會快取 stat 結果;若需極致效能可改用 FilesystemIterator::CURRENT_AS_FILEINFO 旗標

現代替代方案:Symfony Finder / League Flysystem

// Symfony Finder(更語義化、鏈式 API)
use Symfony\Component\Finder\Finder;

$files = Finder::create()
    ->files()
    ->in('/var/www/uploads')
    ->name('*.jpg')
    ->name('*.png')
    ->size('> 1K')
    ->date('since 7 days ago')
    ->depth('< 5');

foreach ($files as $file) {
    // $file 是 SplFileInfo
}

// League Flysystem(抽象儲存層:本地、S3、FTP、SFTP 統一 API)
use League\Flysystem\Filesystem;
use League\Flysystem\Local\LocalFilesystemAdapter;

$adapter = new LocalFilesystemAdapter('/var/www/uploads');
$fs = new Filesystem($adapter);
$files = $fs->listContents('', true); // 遞歸列表,回傳陣列

總結

SPL 迭代器是 PHP 處理檔案系統、資料流、集合操作的標準化、高效能、可組合基礎設施。掌握 RecursiveDirectoryIterator → RecursiveIteratorIterator → FilterIterator 這條組合鏈,即能以幾行代碼實現複雜的遞歸查找、過濾、轉換邏輯,且天然支援惰性求值與常數記憶體。建議將常用過濾器封裝為獨立類別/閉包,構建專案級的 FileSystemFinder 服務,複用於上傳掃描、日誌清理、靜態資源收集等場景。

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