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

PHP 5 SPL 迭代器實戰:DirectoryIterator 遞歸掃描目錄的優雅寫法

     編輯:PHP 綜合知識

為什麼要用 SPL 迭代器?

  • 統一介面:foreach 直接遍歷檔案、XML、資料庫結果、陣列,無需記憶不同 API。
  • 記憶體友善:惰性載入,不會一次將所有檔案名讀入陣列(scandir 會)。
  • 內建過濾:RegexIterator、CallbackFilterIterator 組合式過濾。
  • 遞歸原生支援:RecursiveDirectoryIterator + RecursiveIteratorIterator 兩行搞定樹狀遍歷。

基礎:單層目錄遍歷

<?php
$dir = new DirectoryIterator(__DIR__ . '/../storage/logs');

foreach ($dir as $fileInfo) {
    if ($fileInfo->isDot()) continue;           // 跳過 . 和 ..
    if ($fileInfo->isFile()) {
        printf("%s  %10d  %s\n",
            $fileInfo->getMTime()->format('Y-m-d H:i:s'),
            $fileInfo->getSize(),
            $fileInfo->getFilename()
        );
    }
}

常用 FileInfo 方法

方法回傳
getFilename()檔名(不含路徑)
getPathname()完整路徑
getBasename($suffix='')檔名(可去副檔名)
isFile() / isDir() / isLink()型別判斷
isReadable() / isWritable()權限
getSize()位元組大小
getMTime() / getATime() / getCTime()DateTime 物件
getPerms()權限位元遮罩
getOwner() / getGroup()UID/GID

進階:遞歸遍歷整棵目錄樹

<?php
$root = new RecursiveDirectoryIterator(
    __DIR__ . '/../src',
    FilesystemIterator::SKIP_DOTS | FilesystemIterator::UNIX_PATHS
);

// RecursiveIteratorIterator 扁平化遞歸遍歷
$iterator = new RecursiveIteratorIterator($root, RecursiveIteratorIterator::SELF_FIRST);

// 只取 .php 檔案
$phpFiles = new RegexIterator($iterator, '/\.php$/i');

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

遍歷模式差異

  • SELF_FIRST:先輸出目錄本身,再遞歸子目錄(適合複製樹結構)。
  • CHILD_FIRST:先遞歸子目錄,再輸出目錄本身(適合刪除樹、計算資料夾大小)。
  • LEAVES_ONLY:只輸出葉子節點(檔案),忽略目錄節點。

實戰:生成 ASCII 目錄樹(類 tree 命令)

<?php
class TreeIterator extends RecursiveIteratorIterator {
    public function __construct(RecursiveIterator $it) {
        parent::__construct($it, self::SELF_FIRST);
    }

    public function current() {
        $prefix = str_repeat('│   ', $this->getDepth() - 1);
        $prefix .= $this->getDepth() ? '├── ' : '';
        return $prefix . parent::current()->getBasename();
    }

    public function beginChildren() {
        if ($this->getDepth() > 0) {
            echo str_repeat('│   ', $this->getDepth() - 1) . "├── \n";
        }
    }
}

$dir = new RecursiveDirectoryIterator('/etc/nginx', FilesystemIterator::SKIP_DOTS);
foreach (new TreeIterator($dir) as $name) {
    echo $name . "\n";
}

輸出範例:

nginx.conf
├── conf.d
│   ├── default.conf
│   └── ssl.conf
├── sites-available
│   └── example.com
└── sites-enabled
    └── example.com -> ../sites-available/example.com

過濾器組合技巧

<?php
$dir = new RecursiveDirectoryIterator('/var/www', FilesystemIterator::SKIP_DOTS);
$rec = new RecursiveIteratorIterator($dir, RecursiveIteratorIterator::LEAVES_ONLY);

// 組合:只取 .php、大小 > 1KB、修改時間 7 天內
$filtered = new CallbackFilterIterator($rec, function ($file) {
    return $file->isFile()
        && $file->getExtension() === 'php'
        && $file->getSize() > 1024
        && $file->getMTime() > new DateTime('-7 days');
});

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

效能對比:SPL vs scandir vs glob

方法記憶體速度功能豐富度
scandir()全量陣列快(單層)低
glob()全量陣列中(模式匹配)中
DirectoryIterator惰性、常數快高(資訊豐富、可組合過濾)

萬級檔案目錄建議用 SPL;百萬級需考慮 inotify 或資料庫索引。

常見坑

  • 忘記 SKIP_DOTS:導致無限遞歸或處理 . ..。
  • 權限不足拋出 Exception:包裹 try-catch 或用 FilesystemIterator::CURRENT_AS_FILEINFO 配合 @ 抑制。
  • 符號連結循環:RecursiveDirectoryIterator::FOLLOW_SYMLINKS 預設關閉,開啟時需自行偵測循環。
  • Windows 路徑分隔符:用 FilesystemIterator::UNIX_PATHS 統一輸出 /。

原文《用PHP5的DirectoryIterators遞歸掃描目錄》重構,補充 SPL 完整體系、過濾器組合、ASCII 樹生成、效能對比與避雷指南。

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