PHP 提供兩套檔案操作 API:**資源流式 (fopen 系)** 與 **便利函式 (file_get_contents 系)**。本文詳細對比兩套 API 的記憶體模型、適用場景、錯誤處理差異,並提供生產級封裝範例。
| 特性 | fopen/fread/fwrite | file_get/put_contents |
|---|---|---|
| 記憶體模型 | 流式 (可逐塊讀寫) | 一次性載入/寫入完整內容 |
| 大檔案支援 | 優秀 (常數記憶體) | 差 (O(n) 記憶體,易爆限制) |
| 遠端 URL | 支援 (需 `allow_url_fopen`) | 支援 (同樣需設定) |
| 二進制安全 | 需 `b` 模式 (`rb`, `wb`) | 原生二進制安全 |
| 鎖定機制 | `flock()` 精細控制 | `FILE_APPEND | LOCK_EX` 旗標 |
| 資源管理 | 手動 `fclose()` | 自動管理 |
| 效能 (小檔) | 稍慢 (函式呼叫開銷) | 極快 (C 級優化) |
<?php
// 讀取大檔逐行處理 (記憶體常數)
$fp = fopen('huge.log', 'rb');
if ($fp) {
while (($line = fgets($fp)) !== false) {
// 處理單行
processLine($line);
}
fclose($fp);
}
// 寫入大量資料流式
$fp = fopen('output.csv', 'wb');
if ($fp) {
foreach ($hugeDataset as $row) {
fputcsv($fp, $row);
}
fclose($fp);
}
?>
<?php
// 讀取完整內容到字串
$content = file_get_contents('config.json');
$config = json_decode($content, true);
// 寫入/覆蓋 (原子性:臨時檔 + rename)
$tmp = tempnam(sys_get_temp_dir(), 'cfg_');
file_put_contents($tmp, json_encode($config, JSON_PRETTY_PRINT));
rename($tmp, 'config.json'); // 原子替換
// 追加模式 + 獨佔鎖
file_put_contents('access.log', $logLine . "\n", FILE_APPEND | LOCK_EX);
?>
<?php
class FileUtil {
// 安全讀取 (自動判斷大小選擇策略)
public static function read(string $path, int $maxMemory = 10 * 1024 * 1024): string|false {
$size = @filesize($path);
if ($size === false) return false;
if ($size <= $maxMemory) {
return file_get_contents($path);
}
// 大檔流式讀取拼接 (或直接回傳 Generator)
$chunks = [];
$fp = fopen($path, 'rb');
if (!$fp) return false;
while (!feof($fp)) {
$chunks[] = fread($fp, 8192);
}
fclose($fp);
return implode('', $chunks);
}
// 原子寫入 (防止部分寫入被讀取)
public static function write(string $path, string $data, bool $append = false): bool {
$dir = dirname($path);
if (!is_dir($dir)) mkdir($dir, 0755, true);
$tmp = tempnam($dir, 'fw_');
$flags = $append ? (FILE_APPEND | LOCK_EX) : LOCK_EX;
$result = file_put_contents($tmp, $data, $flags);
if ($result === false) {
@unlink($tmp);
return false;
}
return rename($tmp, $path);
}
// 安全 JSON 讀寫
public static function readJson(string $path, mixed $default = null): mixed {
$content = self::read($path);
return $content ? json_decode($content, true) : $default;
}
public static function writeJson(string $path, mixed $data): bool {
return self::write($path, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
}
}
?>
<?php
function safeHttpGet(string $url, int $timeout = 10): string|false {
$ctx = stream_context_create([
'http' => [
'timeout' => $timeout,
'method' => 'GET',
'header' => "User-Agent: PHP/" . PHP_VERSION . "\r\nAccept: */*\r\n",
'ignore_errors' => true, // 取得 4xx/5xx 內容
],
'ssl' => [
'verify_peer' => true,
'verify_peer_name' => true,
]
]);
$content = @file_get_contents($url, false, $ctx);
if ($content === false) return false;
// 檢查 HTTP 狀態碼 (需 PHP 7.1+ $http_response_header)
$statusLine = $http_response_header[0] ?? '';
if (preg_match('/\s(\d{3})\s/', $statusLine, $m)) {
$code = (int)$m[1];
if ($code >= 400) {
error_log("HTTP $code for $url");
return false;
}
}
return $content;
}
?>
檔案大小 > 10MB 或 未知大小?
├─ 是 → fopen/fread/fwrite (流式)
└─ 否 → 需要精確鎖控/逐行處理/二進制模式?
├─ 是 → fopen/fread/fwrite
└─ 否 → file_get/put_contents (簡潔、效能佳)
file_get_contents 讀取大檔案導致 `Allowed memory size exhausted`。fopen('php://input')` 只能讀取一次,二次讀取為空。根據檔案大小、併發需求、控制精度選擇適當 API,封裝統一工具類可顯著降低檔案操作 Bug 率。