檔案寫入三步驟核心流程
- 打開檔案:`fopen($filename, $mode)` → 返回檔案指標 resource
- 寫入資料:`fwrite($handle, $data, $length)` → 返回寫入位元組數
- 關閉檔案:`fclose($handle)` → 釋放資源,強制刷新緩衝區
fopen() 模式完整對照表
| 模式 | 說明 | 指標位置 | 建立檔案 | 截斷檔案 | 讀/寫 |
r | 唯讀 | 開頭 | 否 | 否 | 讀 |
r+ | 讀寫 | 開頭 | 否 | 否 | 讀/寫 |
w | 唯寫 | 開頭 | 是 | 是(清空) | 寫 |
w+ | 讀寫 | 開頭 | 是 | 是(清空) | 讀/寫 |
a | 唯寫(附加) | 結尾 | 是 | 否 | 寫 |
a+ | 讀寫(附加) | 結尾 | 是 | 否 | 讀/寫 |
x | 唯寫(獨佔建立) | 開頭 | 僅不存在時 | 否 | 寫 |
| x+ | 讀寫(獨佔建立) | 開頭 | 僅不存在時 | 否 | 讀/寫 |
c | 唯寫(不截斷) | 開頭 | 是 | 否 | 寫 |
c+ | 讀寫(不截斷) | 開頭 | 是 | 否 | 讀/寫 |
基礎寫入範例
<?php
$file = 'data/log.txt';
$data = "Log entry at " . date('Y-m-d H:i:s') . "\n";
// 追加模式(最常用於日誌)
$fp = fopen($file, 'a');
if ($fp === false) {
die("無法開啟檔案: $file");
}
$bytes = fwrite($fp, $data);
if ($bytes === false) {
die("寫入失敗");
}
fclose($fp);
echo "寫入 $bytes 位元組";
檔案鎖定:防止並發寫入資料損毀
<?php
$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);
flock($fp, LOCK_UN); // 釋放鎖
} else {
die("無法取得鎖定,檔案可能被其他程序佔用");
}
fclose($fp);
鎖定類型常數
LOCK_SH:共享鎖(讀取用,多人可同時持有)
LOCK_EX:獨佔鎖(寫入用,同時間僅一程序持有)
LOCK_UN:釋放鎖
LOCK_NB:非阻塞模式(配合 LOCK_EX\|LOCK_NB 避免等待)
高階封裝:安全寫入函數
<?php
/**
* 安全寫入檔案(原子性、鎖定、錯誤處理)
* @param string $filepath
* @param string $data
* @param string $mode 'w' 覆寫 | 'a' 附加
* @param int $flags LOCK_EX 預設
* @return bool|int 寫入位元組數或 false
*/
function safe_file_write($filepath, $data, $mode = 'w', $flags = LOCK_EX) {
// 確保目錄存在
$dir = dirname($filepath);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
$fp = @fopen($filepath, $mode === 'a' ? 'a' : 'c');
if (!$fp) return false;
// 非阻塞嘗試鎖定,避免永久等待
$locked = flock($fp, $flags | LOCK_NB);
$attempts = 0;
while (!$locked && $attempts < 10) {
usleep(10000); // 10ms
$locked = flock($fp, $flags | LOCK_NB);
$attempts++;
}
if (!$locked) {
fclose($fp);
return false;
}
// 覆寫模式需截斷
if ($mode === 'w') {
ftruncate($fp, 0);
rewind($fp);
}
$written = fwrite($fp, $data);
flock($fp, LOCK_UN);
fclose($fp);
return $written;
}
// 使用
safe_file_write('data/config.json', json_encode($config, JSON_UNESCAPED_UNICODE));
safe_file_write('logs/app.log', "[$date] $message\n", 'a');
現代替代方案:file_put_contents()
<?php
// 簡單寫入(內部自動 fopen/fwrite/fclose)
file_put_contents('file.txt', $data); // 覆寫
file_put_contents('file.txt', $data, FILE_APPEND); // 附加
file_put_contents('file.txt', $data, FILE_APPEND | LOCK_EX); // 附加+鎖定
// 返回寫入位元組數,失敗返回 false
- 優點:程式碼簡潔、內部處理資源釋放
- 缺點:無法精細控制鎖定行為、大檔案會佔用記憶體
- 大檔案/高併發仍建議用 `fopen`/`fwrite`/`flock` 手動控制
常見錯誤避坑
- ❌ 忘記 `fclose()` → 檔案鎖定不釋放、資源洩漏、資料未刷新到磁碟
- ❌ `w` 模式直接寫入重要檔案 → 若寫入中斷檔案損毀 → ✅ 寫暫存檔再 `rename()` 原子替換
- ❌ 並發寫入無鎖定 → 資料交錯/遺失 → ✅ 必須 `flock()`
- ❌ Windows 下 `flock()` 不支援網路磁碟 → 需替代方案(資料庫、Redis)
原子性寫入模式(生產環境推薦)
function atomic_write($filepath, $data) {
$temp = $filepath . '.tmp.' . uniqid();
$written = @file_put_contents($temp, $data, LOCK_EX);
if ($written === false) {
@unlink($temp);
return false;
}
// rename 在同一檔案系統上是原子操作
if (!@rename($temp, $filepath)) {
@unlink($temp);
return false;
}
return true;
}