常見需求:采集圖片、鏡像存儲、生成縮略圖。本文對比三種方案優劣,推薦生產環境使用 cURL。
function saveImageOb(string $url, string $filename = ''): string|false {
if (!$filename) $filename = date('YmdHis') . '.jpg';
ob_start();
readfile($url);
$img = ob_get_contents();
ob_end_clean();
return file_put_contents($filename, $img) ? $filename : false;
}
缺點:無超時控制、無重試、無法設置 User-Agent/Referer、大文件佔用 PHP 內存 (ob 緩衝)、無法獲取 HTTP 狀態碼判斷 404/403。
function saveImageStream(string $url, string $filename = ''): string|false {
$ctx = stream_context_create(['http' => ['timeout' => 10, 'ignore_errors' => true]]);
$img = @file_get_contents($url, false, $ctx);
if (!$img) return false;
return file_put_contents($filename ?: basename($url), $img) ? $filename : false;
}
function saveImageCurl(string $url, string $savePath, array $options = []): string|false {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5,
CURLOPT_TIMEOUT => 15,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; PHP Image Bot)',
CURLOPT_SSL_VERIFYPEER => false, // 僅測試
CURLOPT_FILE => fopen($savePath . '.tmp', 'w'), // 直接寫文件避免內存
] + $options);
$success = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$errno = curl_errno($ch);
curl_close($ch);
$fp = fopen($savePath . '.tmp', 'r');
fclose($fp); // 確保 flush
if ($success && $httpCode === 200 && $errno === 0) {
rename($savePath . '.tmp', $savePath);
return $savePath;
}
@unlink($savePath . '.tmp');
return false;
}
| 特性 | readfile+ob | stream_context | cURL |
|---|---|---|---|
| 超時控制 | ❌ | ✅ 總超時 | ✅ 連接/傳輸分離 |
| 重試/重定向 | ❌ | ✅ 重定向 | ✅ 完整控制 |
| 自定義 Header | ❌ | ✅ | ✅ |
| 大文件內存 | 高 (全載入) | 高 | 低 (直接落盤) |
| 錯誤診斷 | 困難 | 一般 | 豐富 (錯誤碼、信息) |
| 並發下載 | ❌ | ❌ | ✅ curl_multi |
getimagesize($url) 預檢 MIME 與尺寸,避免垃圾文件.tmp + rename 保證原子性