原生 cURL 選項繁多、錯誤處理分散、重複代碼多。封裝類提供:**統一介面、預設安全選項、自動錯誤轉換、支援鏈式調用**。
ch = curl_init();
$this->setDefaults();
$this->setOptions($defaultOptions);
}
private function setDefaults(): void {
$this->options = [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => false,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5,
CURLOPT_TIMEOUT => 30,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_SSL_VERIFYPEER => true, // 生產環境保持 true
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; PHP-Curl/1.0)',
CURLOPT_ENCODING => 'gzip,deflate', // 自動解壓
];
}
public function setOptions(array $options): self {
foreach ($options as $key => $val) {
$this->options[$key] = $val;
}
return $this;
}
public function setOpt(int $option, $value): self {
$this->options[$option] = $value;
return $this;
}
/** 核心執行 */
public function execute(string $method, string $url, $data = null, array $headers = []): string {
$this->applyOptions();
curl_setopt($this->ch, CURLOPT_URL, $url);
curl_setopt($this->ch, CURLOPT_CUSTOMREQUEST, strtoupper($method));
if ($data !== null) {
if (is_array($data)) {
// POST fields: 自動判斷是否為檔案上傳 (CURLFile)
$hasFile = false;
foreach ($data as $v) {
if ($v instanceof \CURLFile) { $hasFile = true; break; }
}
if (!$hasFile) {
$data = http_build_query($data);
}
}
curl_setopt($this->ch, CURLOPT_POSTFIELDS, $data);
}
if ($headers) {
$headerLines = [];
foreach ($headers as $k => $v) {
$headerLines[] = "$k: $v";
}
curl_setopt($this->ch, CURLOPT_HTTPHEADER, $headerLines);
}
$response = curl_exec($this->ch);
$errno = curl_errno($this->ch);
$error = curl_error($this->ch);
$httpCode = curl_getinfo($this->ch, CURLINFO_HTTP_CODE);
if ($errno) {
throw new \RuntimeException("cURL error ($errno): $error", $errno);
}
if ($httpCode >= 400) {
throw new \HttpException("HTTP $httpCode", $httpCode, $response);
}
return $response;
}
private function applyOptions(): void {
curl_setopt_array($this->ch, $this->options);
}
// 便利方法
public function get(string $url, array $headers = []): string {
return $this->execute('GET', $url, null, $headers);
}
public function post(string $url, $data = null, array $headers = []): string {
return $this->execute('POST', $url, $data, $headers);
}
public function put(string $url, $data = null, array $headers = []): string {
return $this->execute('PUT', $url, $data, $headers);
}
public function delete(string $url, array $headers = []): string {
return $this->execute('DELETE', $url, null, $headers);
}
public function __destruct() {
if (is_resource($this->ch)) {
curl_close($this->ch);
}
}
}
// 自訂例外
class HttpException extends \Exception {
public $response;
public function __construct(string $message, int $code, $response = null) {
parent::__construct($message, $code);
$this->response = $response;
}
}
?>
get('https://httpbin.org/get');
echo $html;
} catch (\Exception $e) {
echo "請求失敗: " . $e->getMessage();
}
?>
setOpt(CURLOPT_HTTPHEADER, ['Content-Type: application/json', 'Accept: application/json']);
$payload = ['name' => 'John', 'email' => 'john@example.com'];
try {
$response = $curl->post('https://httpbin.org/post', json_encode($payload));
$data = json_decode($response, true);
print_r($data['json']); // 回傳的 JSON 資料
} catch (\HttpException $e) {
echo "API 錯誤 {$e->getCode()}: " . $e->getMessage();
// $e->response 包含原始回應
} catch (\Exception $e) {
echo "網路錯誤: " . $e->getMessage();
}
?>
new \CURLFile('/path/to/image.jpg', 'image/jpeg', 'avatar.jpg'),
'user_id' => 123,
];
$response = $curl->post('https://api.example.com/upload', $data);
?>
setOpt(CURLOPT_USERPWD, 'username:password'); // Bearer Token $curl->setOpt(CURLOPT_HTTPHEADER, ['Authorization: Bearer ' . $accessToken]); ?>
setOptions([
CURLOPT_COOKIEJAR => $cookieFile, // 儲存 Cookie
CURLOPT_COOKIEFILE => $cookieFile, // 讀取 Cookie
]);
// 1. 登入
$curl->post('https://example.com/login', ['user' => 'a', 'pass' => 'b']);
// 2. 後續請求自動攜帶 Cookie
$html = $curl->get('https://example.com/dashboard');
?>
| 問題 | 解決 |
|---|---|
| SSL 憑證驗證失敗 | 正確安裝 CA 憑證 yum install ca-certificates,勿關閉 CURLOPT_SSL_VERIFYPEER |
| 大檔案下載記憶體爆炸 | 用 CURLOPT_WRITEFUNCTION 寫入檔案流,不回傳字串 |
| 重定向循環 | 設定 CURLOPT_MAXREDIRS、CURLOPT_FOLLOWLOCATION |
| 並發請求 | 單實例不支援並發,改用 curl_multi 或 Guzzle Promise |
| 超時設定 | CURLOPT_CONNECTTIMEOUT (連線)、CURLOPT_TIMEOUT (整體) |
新專案**強烈建議直接使用 Guzzle 或 Symfony HttpClient**,僅維護舊專案才手寫封裝類。
Curl 封裝類核心價值在於**統一錯誤處理、預設安全選項、簡化常用介面**。理解 cURL 關鍵選項(SSL、超時、重定向、Cookie、Header)比封裝類本身更重要。現代開發請擁抱 PSR-18/PSR-17 標準客戶端,減少造輪子,專注業務邏輯。