在 API 串接、爬蟲、第三方支付回調測試等場景,常需模擬瀏覽器表單提交。本文提供一個乾淨、可復用的 SimulantForm 類,支援陣列/字串雙模式資料、Cookie 持久化、錯誤處理,開箱即用。
<?php
class SimulantForm
{
protected string $url;
protected $ch;
protected array $defaultOptions = [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5,
CURLOPT_TIMEOUT => 30,
CURLOPT_SSL_VERIFYPEER => false, // 僅測試環境建議關閉
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; SimulantForm/1.0)',
];
public function __construct(string $url)
{
$this->url = $url;
$this->ch = curl_init();
$this->setOptions($this->defaultOptions);
curl_setopt($this->ch, CURLOPT_URL, $url);
}
/** GET 請求:自動將陣列轉為 query string 附加至 URL */
public function get($data = ''): string
{
$query = $this->buildQuery($data);
$this->setOption(CURLOPT_URL, $this->url . $query);
$this->setOption(CURLOPT_HTTPGET, true);
return $this->execute();
}
/** POST 請求:支援陣列(multipart/form-data)或字串 */
public function post($data): string
{
$this->setOption(CURLOPT_POST, true);
$this->setOption(CURLOPT_POSTFIELDS, $data);
$this->setOption(CURLOPT_URL, $this->url); // 確保 URL 乾淨
return $this->execute();
}
/** 設定 Cookie 檔案(讀取/寫入)實現會話保持 */
public function setCookieFile(string $file): void
{
$this->setOption(CURLOPT_COOKIEFILE, $file);
$this->setOption(CURLOPT_COOKIEJAR, $file);
}
/** 取得最後一次請求的錯誤資訊 */
public function getLastError(): array
{
return [curl_errno($this->ch), curl_error($this->ch)];
}
/** 取得 HTTP 狀態碼 */
public function getHttpCode(): int
{
return (int)curl_getinfo($this->ch, CURLINFO_HTTP_CODE);
}
/** 批次設定選項 */
protected function setOptions(array $options): void
{
curl_setopt_array($this->ch, $options);
}
protected function setOption(int $option, $value): void
{
curl_setopt($this->ch, $option, $value);
}
/** 將陣列轉為 URL query string,支援多值鍵 foo[]=1&foo[]=2 */
protected function buildQuery($data): string
{
if (empty($data)) return '';
if (is_string($data)) return (strpos($data, '?') === 0 ? '' : '?') . $data;
$parts = [];
foreach ($data as $key => $value) {
if (is_array($value)) {
foreach ($value as $v) {
$parts[] = urlencode($key) . '[]=' . urlencode($v);
}
} else {
$parts[] = urlencode($key) . '=' . urlencode($value);
}
}
return '?' . implode('&', $parts);
}
/** 執行請求並關閉 handle */
protected function execute(): string
{
$response = curl_exec($this->ch);
curl_close($this->ch);
return $response ?: '';
}
/** 解構時確保資源釋放 */
public function __destruct()
{
if (is_resource($this->ch)) curl_close($this->ch);
}
}
$form = new SimulantForm('https://httpbin.org/get');
$html = $form->get(['q' => 'php curl', 'page' => 2]);
echo $html;
$form = new SimulantForm('https://httpbin.org/post');
$result = $form->post([
'username' => 'demo',
'password' => 'secret',
'tags' => ['php', 'curl', 'form'],
]);
print_r(json_decode($result, true));
$form = new SimulantForm('https://api.example.com/upload');
$result = $form->post([
'file' => new CURLFile('/path/to/image.jpg', 'image/jpeg', 'avatar.jpg'),
'folder' => 'avatars',
]);
$cookieFile = sys_get_temp_dir() . '/simulant_cookies.txt';
// 步驟 1:登入
$login = new SimulantForm('https://example.com/login');
$login->setCookieFile($cookieFile);
$login->post(['email' => 'user@test.com', 'pwd' => '123456']);
// 步驟 2:訪問需登入頁面
$dashboard = new SimulantForm('https://example.com/dashboard');
$dashboard->setCookieFile($cookieFile);
echo $dashboard->get();
curl_init(),execute() 結束時關閉,__destruct() 雙重保障避免洩漏。buildQuery() 統一使用 urlencode(),正確處理中文、特殊符號與多值陣列。CURLOPT_POSTFIELDS,陣列自動編碼為 multipart/form-data,字串則作為 application/x-www-form-urlencoded。getLastError() 與 getHttpCode() 便於除錯與重試邏輯。將此類納入專案共用函式庫,即可在任何需要模擬 HTTP 表單提交的場合一行代碼解決,專注於業務邏輯而非 cURL 參數細節。