遍歷目錄獲取文件列表是 PHP 開發中的高頻需求:構建文件管理器、掃描上傳目錄、生成網站地圖、批量處理文件等。本文系統講解三種主流方案:傳統 dir()/readdir()、面向對象 DirectoryIterator、現代 RecursiveDirectoryIterator,並提供生產級封裝類。
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');
?>
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());
}
?>
PHP 5.3+ 提供標準庫 (SPL) 迭代器,代碼更優雅、功能更強、自動實現 Countable、Iterator、SeekableIterator 接口。
<?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";
}
?>
<?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";
}
?>
<?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";
}
?>
<?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+ | 低(需手動管理) | 低 | 基礎 | 舊項目維護、極簡腳本 |
| DirectoryIterator | 5.0+ | 高(惰性加載) | 低 | 豐富(文件信息) | 單層目錄掃描、文件屬性讀取 |
| RecursiveDirectoryIterator | 5.0+ | 高(惰性加載) | 中 | 最豐富(過濾、深度控制) | 遞歸遍歷、文件搜索、批量處理 |
| glob()/scandir() | 4.3+/5.0+ | 低(一次性加載數組) | 最低 | 基礎 | 小目錄、簡單匹配、快速原型 |
<?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');
?>
FilterIterator + RegexIterator 組合實現複雜業務邏輯FOLLOW_SYMLINKS 可能導致無限循環,建議默認關閉DIRECTORY_SEPARATOR 或 UNIX_PATHS 標誌掌握 PHP 目錄遍歷的多種方案,根據項目 PHP 版本、目錄規模、業務複雜度選擇最適合的工具,編寫高效、健壯、可維護的文件系統操作代碼。