在 PHP 中安全、可靠地創建文件需要處理目錄遞歸創建、權限設置、寫入失敗回滾等細節。本文提供一個生產級的封裝函數。
<?php
/**
* 安全創建文件並寫入內容
*
* @param string $filePath 完整文件路徑
* @param string $content 寫入內容
* @param int $dirPerm 目錄權限(默認 0755)
* @param int $filePerm 文件權限(默認 0644)
* @return bool 成功返回 true,失敗拋出異常
*/
function createFileSafe(string $filePath, string $content, int $dirPerm = 0755, int $filePerm = 0644): bool {
// 1. 規範化路徑
$filePath = realpath(dirname($filePath)) . DIRECTORY_SEPARATOR . basename($filePath);
$dir = dirname($filePath);
// 2. 遞歸創建目錄
if (!is_dir($dir)) {
if (!mkdir($dir, $dirPerm, true) && !is_dir($dir)) {
throw new RuntimeException("無法創建目錄: $dir");
}
}
// 3. 以原子方式寫入(臨時文件 + rename)
$tmpName = $filePath . '.tmp.' . bin2hex(random_bytes(8));
$handle = @fopen($tmpName, 'wb');
if (!$handle) {
throw new RuntimeException("無法創建臨時文件: $tmpName");
}
try {
// 獨佔鎖防止併發寫入
if (!flock($handle, LOCK_EX)) {
throw new RuntimeException("無法獲取文件鎖: $tmpName");
}
$bytesWritten = fwrite($handle, $content);
if ($bytesWritten === false || $bytesWritten !== strlen($content)) {
throw new RuntimeException("文件寫入失敗或不完整: $filePath");
}
// 刷新緩衝區
fflush($handle);
flock($handle, LOCK_UN);
fclose($handle);
// 設置文件權限
if (!chmod($tmpName, $filePerm)) {
@unlink($tmpName);
throw new RuntimeException("無法設置文件權限: $tmpName");
}
// 原子重命名(同文件系統下保證原子性)
if (!rename($tmpName, $filePath)) {
@unlink($tmpName);
throw new RuntimeException("文件重命名失敗: $tmpName -> $filePath");
}
return true;
} catch (Exception $e) {
// 確保清理臨時文件
if (is_resource($handle)) {
flock($handle, LOCK_UN);
fclose($handle);
}
@unlink($tmpName);
throw $e;
}
}
// ========== 使用示例 ==========
try {
$dateDir = date('Ymd');
$file = "/var/www/data/$dateDir/log_" . date('His') . ".txt";
$content = "這是測試內容\n時間: " . date('Y-m-d H:i:s') . "\n";
if (createFileSafe($file, $content)) {
echo "文件創建成功: $file";
}
} catch (Exception $e) {
error_log("創建文件失敗: " . $e->getMessage());
echo "操作失敗: " . $e->getMessage();
}
?>
function appendFileSafe(string $filePath, string $content): bool {
$dir = dirname($filePath);
if (!is_dir($dir) && !mkdir($dir, 0755, true)) return false;
return (bool)file_put_contents($filePath, $content, FILE_APPEND | LOCK_EX);
}
function writeLargeFile(string $filePath, Generator $chunks): bool {
$tmp = $filePath . '.part';
$fp = fopen($tmp, 'wb');
foreach ($chunks as $chunk) {
fwrite($fp, $chunk);
}
fclose($fp);
return rename($tmp, $filePath);
}
掌握這些模式,能有效避免文件操作中的數據損壞、權限問題、併發衝突等線上故障。