readdir() 是 PHP 遍歷目錄的基礎函數,但其返回值特性極易導致一類難以排查的 Bug:當目錄下存在名為 "0" 的文件或子目錄時,while ($name = readdir($handle)) 會提前終止。本文解析根因並給出標準修正寫法。
<?php
$dir = '/tmp/test_dir';
mkdir($dir . '/0'); // 創建名為 "0" 的子目錄
mkdir($dir . '/normal'); // 正常目錄
$handle = opendir($dir);
// ❌ 錯誤寫法:直接判斷賦值結果
while ($name = readdir($handle)) {
echo "Found: $name\n";
}
// 輸出僅包含 "."、"0","normal" 被漏掉!
closedir($handle);
readdir() 成功時返回文件名(字符串),失敗/結束時返回 false;"0" 在 PHP 中為 **falsy 值**(弱類型轉換為 false);while ($name = readdir($handle)) 將賦值結果隱式轉為布爾判斷,遇到 "0" 即判定為 false,循環中止。while (($name = readdir($handle)) !== false) {
// 處理 $name
}
while (true) {
$name = readdir($handle);
if ($name === false) break;
// 處理 $name
}
$files = [];
while (($name = readdir($handle)) !== false) {
$files[] = $name;
}
foreach ($files as $name) { /* 處理 */ }
function listDir(string $path): array {
if (!is_dir($path)) throw new InvalidArgumentException("Not a directory: $path");
$handle = opendir($path);
if (!$handle) throw new RuntimeException("Cannot open directory: $path");
$result = [];
try {
while (($name = readdir($handle)) !== false) {
if ($name === '.' || $name === '..') continue; // 跳過特殊目錄
$result[] = $name;
}
} finally {
closedir($handle); // 確保關閉句柄
}
return $result;
}
| 函數/類 | 優點 | 適用場景 |
|---|---|---|
scandir() | 一次性返回數組、自動排序、無 falsy 陷阱 | 中小目錄、需排序 |
glob() | 支援模式匹配、返回完整路徑 | 按擴展名/前綴篩選 |
DirectoryIterator (SPL) | 面向對象、可迭代、支援 SplFileInfo | 大目錄、需文件元數據 |
RecursiveDirectoryIterator | 遞歸遍歷子目錄 | 樹形結構處理 |
!== false** 判斷 readdir() 返回值;scandir() 或 SPL 迭代器,避免手動資源管理;is_dir()、is_readable();try/finally 保證 closedir() 執行,防句柄洩漏。