檔案操作是後端開發核心技能。PHP 提供豐富的檔案系統函數,支援從簡單讀寫到複雜指標控制。本文系統整理檔案處理全流程與最佳實踐。
核心函數,回傳檔案資源控制代碼(resource handle)。
$handle = fopen(string $filename, string $mode, bool $use_include_path = false, resource $context = null): resource|false
| 模式 | 讀 | 寫 | 指標位置 | 截斷 | 建立 | 說明 |
|---|---|---|---|---|---|---|
| r | ✓ | ✗ | 開頭 | 否 | 否 | 唯讀,檔案必須存在 |
| r+ | ✓ | ✓ | 開頭 | 否 | 否 | 讀寫,檔案必須存在 |
| w | ✗ | ✓ | 開頭 | 是 | 是 | 唯寫,清空或建立 |
| w+ | ✓ | ✓ | 開頭 | 是 | 是 | 讀寫,清空或建立 |
| a | ✗ | ✓ | 結尾 | 否 | 是 | 附加寫入,建立 |
| a+ | ✓ | ✓ | 結尾 | 否 | 是 | 讀寫附加,建立 |
| x | ✗ | ✓ | 開頭 | 否 | 獨佔 | 唯寫,檔案不可存在 |
| x+ | ✓ | ✓ | 開頭 | 否 | 獨佔 | 讀寫,檔案不可存在 |
失敗處理: fopen() 失敗回傳 false,建議使用 or die() 或拋出異常。
$fp = fopen("data.txt", "r") or throw new Exception("無法開啟檔案");
釋放資源,確保緩衝區寫入磁碟。
$fp = fopen("log.txt", "a");
fwrite($fp, "新日誌\n");
fclose($fp); // 必須關閉
迴圈讀取未知長度檔案時的終止條件。
$fp = fopen("data.txt", "r");
while (!feof($fp)) {
$line = fgets($fp);
// 處理...
}
fclose($fp);
每次呼叫移動指標到下一行,適合大檔案逐行處理(記憶體友善)。
$fp = fopen("large.log", "r");
while (($line = fgets($fp)) !== false) {
// 處理單行,如解析 JSON、過濾關鍵字
if (strpos($line, 'ERROR') !== false) {
error_log($line);
}
}
fclose($fp);
精細控制,效率較低,適用特定解析場景。
$fp = fopen("data.bin", "r");
while (($char = fgetc($fp)) !== false) {
// 位元組級處理
}
fclose($fp);
小檔案適用,大檔案會耗盡記憶體。
// 讀取整個檔案為字串
$content = file_get_contents("config.json");
$config = json_decode($content, true);
// 讀取為陣列(每行一元素)
$lines = file("data.txt", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$fp = fopen("output.txt", "w");
fwrite($fp, "第一行\n");
fwrite($fp, "第二行\n");
fclose($fp);
// 覆寫
file_put_contents("log.txt", "新內容\n");
// 附加模式
file_put_contents("log.txt", "追加行\n", FILE_APPEND);
// 獨佔鎖定寫入(防止併發衝突)
file_put_contents("counter.txt", $newValue, LOCK_EX);
| 函數 | 說明 |
|---|---|
| ftell($fp) | 取得目前指標位置(位元組偏移) |
| fseek($fp, $offset, SEEK_SET|SEEK_CUR|SEEK_END) | 移動指標 |
| rewind($fp) | 指標移至開頭(等同 fseek($fp, 0)) |
$fp = fopen("data.bin", "r+");
fseek($fp, 100, SEEK_SET); // 移至第 100 位元組
fseek($fp, -50, SEEK_END); // 移至倒數第 50 位元組
$pos = ftell($fp);
rewind($fp);
| 函數 | 說明 |
|---|---|
| filesize($path) | 檔案大小(位元組) |
| file_exists($path) | 檢查存在性 |
| is_file($path) / is_dir($path) | 類型判斷 |
| filemtime($path) | 最後修改時間 |
| fileperms($path) | 權限 |
| pathinfo($path) | 解析路徑資訊 |
| realpath($path) | 取得絕對路徑 |
$info = pathinfo("/var/www/uploads/image.png");
/*
Array
(
[dirname] => /var/www/uploads
[basename] => image.png
[extension] => png
[filename] => image
)
*/
// 掃描目錄
$files = scandir("/var/www/uploads");
// 或使用 DirectoryIterator (OO 風格)
$dir = new DirectoryIterator("/var/www/uploads");
foreach ($dir as $fileinfo) {
if ($fileinfo->isFile()) {
echo $fileinfo->getFilename() . " (" . $fileinfo->getSize() . " bytes)\n";
}
}
// 建立目錄
mkdir("/var/www/uploads/2024/01", 0755, true); // 遞迴建立
// 刪除檔案/目錄
unlink("file.txt");
rmdir("empty_dir"); // 僅空目錄
協同程式存取同一檔案時防止競態條件。
$fp = fopen("counter.txt", "c+"); // 開啟讀寫,不截斷
if (flock($fp, LOCK_EX)) { // 獨佔鎖定
$count = (int)fread($fp, filesize("counter.txt"));
$count++;
ftruncate($fp, 0); // 清空內容
rewind($fp);
fwrite($fp, (string)$count);
fflush($fp); // 刷新緩衝區
flock($fp, LOCK_UN); // 釋放鎖定
} else {
echo "無法取得鎖定";
}
fclose($fp);
$fp = fopen("data.txt", "r");
try {
// 讀取邏輯
} finally {
fclose($fp);
}
file_get_contents() 造成記憶體溢位is_readable()、is_writable()$tmp = tempnam(sys_get_temp_dir(), 'data_');
file_put_contents($tmp, $newContent);
rename($tmp, "data.txt"); // 原子替換
現代 PHP 推薦使用 SPL 類別封裝檔案操作。
$file = new SplFileObject("data.csv");
$file->setFlags(SplFileObject::READ_CSV);
foreach ($file as $row) {
// $row 為陣列
print_r($row);
}
// 寫入 CSV
$file = new SplFileObject("output.csv", "w");
$file->fputcsv(['姓名', '年齡', '城市']);
$file->fputcsv(['張三', 25, '台北']);
$file->fputcsv(['李四', 30, '台中']);
掌握 PHP 檔案處理關鍵在於:選擇正確模式、善用串流處理大檔案、嚴謹管理資源生命週期、併發場景下正確加鎖。這些基礎技能是構建高效、穩定後端系統的基石。