API 除錯、安全審計、請求快照都需要將 $_POST 多維陣列扁平化為字串。本文提供健壯的遞歸實現,支援任意深度巢狀陣列、鍵名編碼、特殊字元處理,輸出標準 application/x-www-form-urlencoded 格式,可直接寫入日誌或資料庫。
<?php
/**
* 將多維 POST 陣列轉為 URL 編碼字串
*
* @param array $data 輸入陣列 (預設 $_POST)
* @param string $prefix 遞歸用鍵名前綴
* @return string 格式:key1=val1&key2[sub]=val2&...
*/
function postArrayToString(array $data = [], string $prefix = ''): string {
$parts = [];
foreach ($data as $key => $value) {
// 鍵名處理:URL 編碼,支援中文鍵名
$encodedKey = rawurlencode($key);
$fullKey = $prefix ? "{$prefix}[{$encodedKey}]" : $encodedKey;
if (is_array($value)) {
// 遞歸處理巢狀陣列
$parts[] = postArrayToString($value, $fullKey);
} elseif (is_scalar($value) || (is_object($value) && method_exists($value, '__toString'))) {
// 標量值或可字串化物件
$encodedValue = rawurlencode((string)$value);
$parts[] = "{$fullKey}={$encodedValue}";
} elseif (is_null($value)) {
// null 值輸出為空字串
$parts[] = "{$fullKey}=";
} else {
// resource 等不可序列化型別
$parts[] = "{$fullKey}=[unserializable:" . gettype($value) . "]";
}
}
return implode('&', $parts);
}
// 便捷封裝:直接處理 $_POST
function getPostLogString(): string {
return postArrayToString($_POST);
}
?>
<?php
// 模擬複雜 POST 資料
$_POST = [
'user' => [
'name' => '張三',
'age' => 25,
'tags' => ['vip', 'beta-tester'],
'address' => [
'city' => '台北',
'zip' => '100'
]
],
'action' => 'update_profile',
'empty_val' => '',
'null_val' => null,
'files' => [ // 檔案上傳陣列結構
'avatar' => [
'name' => 'photo.jpg',
'type' => 'image/jpeg',
'size' => 102400
]
]
];
echo getPostLogString();
/*
輸出:
action=update_profile
&empty_val=
&files[avatar][name]=photo.jpg
&files[avatar][size]=102400
&files[avatar][type]=image%2Fjpeg
&null_val=
&user[address][city]=%E5%8F%B0%E5%8C%97
&user[address][zip]=100
&user[age]=25
&user[name]=%E5%BC%B5%E4%B8%89
&user[tags][0]=vip
&user[tags][1]=beta-tester
*/
?>
| 決策點 | 選擇 | 理由 |
|---|---|---|
| 編碼函式 | rawurlencode() | RFC 3986 標準,空格編為 %20 而非 + |
| 巢狀格式 | PHP 風格 key[sub][sub] | 與 parse_str() 完美互逆,原生支援 |
| 排序 | 依陣列順序 (PHP 7+ 保持插入序) | 確定性輸出,利於日誌比對 |
| Null 處理 | 輸出 key= | 區別於未傳遞參數,符合表單語意 |
| 型別支援 | 標量 + __toString 物件 | 覆蓋 99% 實際場景,resource 標記不可序列化 |
$str = 'user[name]=John&user[tags][0]=vip&user[tags][1]=beta&action=login';
parse_str($str, $output);
/*
$output = [
'user' => [
'name' => 'John',
'tags' => ['vip', 'beta']
],
'action' => 'login'
];
*/
function postToJsonString(array $data = [], int $flags = JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES): string {
// 直接 JSON 編碼,保留結構,適合 ELK/Logstash 分析
return json_encode($data ?: $_POST, $flags);
}
// 輸出範例:
// {"user":{"name":"張三","age":25,"tags":["vip","beta-tester"],"address":{"city":"台北","zip":"100"}},"action":"update_profile"}
// middleware 裡記錄請求
function logRequest() {
$logData = [
'timestamp' => date('c'),
'method' => $_SERVER['REQUEST_METHOD'],
'uri' => $_SERVER['REQUEST_URI'],
'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
'post_data' => getPostLogString(), // 或 postToJsonString()
'get_data' => http_build_query($_GET),
'session_id' => session_id(),
];
// 寫入檔案/資料庫/送往 Logstash
error_log(json_encode($logData, JSON_UNESCAPED_UNICODE) . "\n", 3, '/var/log/api_requests.log');
}
function sanitizePostForLog(array $data): array {
$sensitiveKeys = ['password', 'passwd', 'pwd', 'secret', 'token', 'api_key', 'credit_card', 'cvv'];
$masked = '***MASKED***';
array_walk_recursive($data, function (&$value, $key) use ($sensitiveKeys, $masked) {
if (in_array(strtolower($key), $sensitiveKeys, true)) {
$value = $masked;
}
});
return $data;
}
// 使用
$safePost = sanitizePostForLog($_POST);
$logStr = postArrayToString($safePost);
遞歸扁平化 + URL 編碼是將 POST 資料序列化為字串的標準做法,配合敏感欄位遮罩與結構化 JSON 選項,能滿足從除錯到合規審計的多樣化日誌需求。