在系統整合、API 串接或第三方支付回調中,常需以 Content-Type: text/xml 或 application/xml 格式 POST XML 資料。PHP 提供 cURL(推薦)與原生 Socket 兩種主流實作,本文提供完整、可直接部署的代碼範例,並封裝為可重用類別。
<?php
function postXmlByCurl(string $url, string $xmlData, array $options = []): ?string {
$ch = curl_init($url);
$defaultOpts = [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $xmlData,
CURLOPT_HTTPHEADER => [
'Content-Type: application/xml; charset=utf-8',
'Content-Length: ' . strlen($xmlData),
],
CURLOPT_TIMEOUT => 30,
CURLOPT_SSL_VERIFYPEER => false, // 測試環境可關閉,生產請開啟並配置 CA
CURLOPT_SSL_VERIFYHOST => 2,
];
curl_setopt_array($ch, $defaultOpts + $options);
$response = curl_exec($ch);
if (curl_errno($ch)) {
error_log('cURL Error: ' . curl_error($ch));
return null;
}
curl_close($ch);
return $response;
}
// 使用範例
$xml = '<?xml version="1.0" encoding="UTF-8"?><order><id>20240101</id></order>';
$result = postXmlByCurl('https://api.example.com/notify', $xml);
var_dump($result);
?>
<?php
function postXmlBySocket(string $host, string $path, string $xmlData, int $port = 80, bool $ssl = false): ?string {
$target = ($ssl ? 'ssl://' : '') . $host;
$fp = @fsockopen($target, $port, $errno, $errstr, 10);
if (!$fp) {
error_log("Socket Error [$errno]: $errstr");
return null;
}
$request = "POST $path HTTP/1.1\r\n";
$request .= "Host: $host\r\n";
$request .= "Content-Type: application/xml; charset=utf-8\r\n";
$request .= "Content-Length: " . strlen($xmlData) . "\r\n";
$request .= "Connection: close\r\n\r\n";
$request .= $xmlData;
fwrite($fp, $request);
$response = '';
while (!feof($fp)) {
$response .= fgets($fp, 1024);
}
fclose($fp);
// 簡易移除 HTTP 標頭,僅回傳 Body
list(, $body) = explode("\r\n\r\n", $response, 2) + [null, ''];
return $body;
}
// 使用範例
$xml = '<data><item>test</item></data>';
$result = postXmlBySocket('api.example.com', '/endpoint', $xml, 443, true);
var_dump($result);
?>
<?php
class XmlSender {
private int $timeout = 30;
private int $maxRetries = 2;
private ?string $caCertPath = null;
public function __construct(array $config = []) {
if (!extension_loaded('curl')) {
throw new RuntimeException('cURL extension required');
}
$this->timeout = $config['timeout'] ?? 30;
$this->maxRetries = $config['retries'] ?? 2;
$this->caCertPath = $config['ca_cert'] ?? null;
}
public function send(string $url, string $xml, array $extraHeaders = []): ?string {
$headers = array_merge([
'Content-Type: application/xml; charset=utf-8',
'Content-Length: ' . strlen($xml),
'Expect:', // 避免 100 Continue 延遲
], $extraHeaders);
for ($attempt = 0; $attempt <= $this->maxRetries; $attempt++) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $xml,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_TIMEOUT => $this->timeout,
CURLOPT_SSL_VERIFYPEER => $this->caCertPath !== null,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_CAINFO => $this->caCertPath,
]);
$response = curl_exec($ch);
$errno = curl_errno($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($errno === 0 && $httpCode >= 200 && $httpCode < 300) {
return $response;
}
if ($attempt < $this->maxRetries) {
usleep(200000 * ($attempt + 1)); // 指數退避
}
}
error_log("XmlSender failed after {$this->maxRetries} retries: $url");
return null;
}
}
// 使用
$sender = new XmlSender(['timeout' => 20, 'retries' => 3]);
$xml = '<request><action>sync</action></request>';
$resp = $sender->send('https://partner.api/gateway', $xml);
?>
encoding="UTF-8"、PHP 檔案儲存為 UTF-8 無 BOM、cURL 傳送前確認 mb_internal_encoding('UTF-8')。CURLOPT_READFUNCTION 分塊讀取,避免記憶體暴增。simplexml_load_string 或 DOMDocument 解析驗證結構,防止惡意回應導致 XXE 攻擊(關閉 libxml_disable_entity_loader(true))。總結: cURL 為首選,功能完整且效能佳;fsockopen 作為無擴充套件環境的備案。建議封裝統一發送類別,集中管理超時、重試、SSL、日誌,提升維護性與安全性。