PHP copy() 函數支援 URL 封裝協議(需 allow_url_fopen=On),可直接將遠端檔案下載至本地。但網路操作充滿不確定性(逾時、404、權限、磁碟空間不足),必須建立完善的錯誤處理機制。
<?php
$remoteUrl = 'https://example.com/assets/logo.png';
$localPath = __DIR__ . '/downloads/logo.png';
// 確保目標目錄存在
$dir = dirname($localPath);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
// 執行複製並捕獲錯誤
if (@copy($remoteUrl, $localPath)) {
echo "下載成功:$localPath";
} else {
// PHP 7+ 可用 error_get_last() 取得最後錯誤詳情
$error = error_get_last();
$msg = $error ? $error['message'] : '未知錯誤';
echo "下載失敗:$msg";
}
?>
copy() 無法自訂 HTTP 請求細節。需用 stream_context_create() 建立上下文:
<?php
$url = 'https://api.example.com/protected/file.zip';
$dest = '/tmp/file.zip';
$context = stream_context_create([
'http' => [
'method' => 'GET',
'header' => "Authorization: Bearer YOUR_TOKEN\r\n" .
"User-Agent: MyApp/1.0\r\n",
'timeout' => 30, // 連線逾時(秒)
'ignore_errors' => true, // 即使 4xx/5xx 也取得內容
],
'ssl' => [
'verify_peer' => true,
'verify_peer_name' => true,
],
]);
if (@copy($url, $dest, $context)) {
echo "下載成功";
} else {
$err = error_get_last();
echo "失敗:" . ($err['message'] ?? '未知');
}
?>
生產環境強烈建議使用 cURL,支援斷點續傳、進度回調、代理、憑證等高級功能:
<?php
function downloadWithCurl(string $url, string $dest, array $options = []): bool
{
$fp = @fopen($dest, 'wb');
if (!$fp) return false;
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_FILE => $fp,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 300, // 總逾時 5 分鐘
CURLOPT_USERAGENT => 'MyApp/1.0',
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
] + $options);
$result = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$errno = curl_errno($ch);
$error = curl_error($ch);
curl_close($ch);
fclose($fp);
if ($errno || $httpCode >= 400) {
@unlink($dest); // 失敗刪除不完整檔案
error_log("cURL 下載失敗 [$url]: HTTP $httpCode, cURL $errno: $error");
return false;
}
return true;
}
?>
php.ini 中必須啟用,否則 copy() 無法開啟 URL。cacert.pem 並設定 openssl.cafile。max_execution_time,需 set_time_limit(0) 或調整 php.ini。disk_free_space()。tempnam())完成後再 rename() 覆蓋(原子操作)。copy() + error_get_last() 足矣。