接收第三方回調、舊系統遷移、用戶上傳 CSV 等場景,**輸入編碼不可控**(可能是 GBK、UTF-8、Big5、ISO-8859-1...),但內部系統統一使用 UTF-8。手動 iconv('gbk','utf-8',$str) 只能處理已知編碼,強行轉換會導致亂碼或截斷。
auto_charset() —— 一行代碼搞定標量與陣列<?php
/**
* 自動字符集轉換,支援字符串、陣列(鍵值同步轉換)、巢狀結構
* @param mixed $fContents 輸入數據
* @param string $from 來源編碼(預設 gbk),支援 'gbk','utf-8','big5' 等
* @param string $to 目標編碼(預設 utf-8)
* @return mixed 轉換後的數據,結構保持不變
*/
function auto_charset($fContents, $from = 'gbk', $to = 'utf-8') {
// 統一編碼名稱大小寫
$from = strtoupper($from) === 'UTF8' ? 'utf-8' : $from;
$to = strtoupper($to) === 'UTF8' ? 'utf-8' : $to;
// 無需轉換:編碼相同、空值、非字符串標量(int/float/bool/null)
if (strtoupper($from) === strtoupper($to) || empty($fContents) || (is_scalar($fContents) && !is_string($fContents))) {
return $fContents;
}
// 字符串:優先 mb_convert_encoding,回退 iconv
if (is_string($fContents)) {
if (function_exists('mb_convert_encoding')) {
return mb_convert_encoding($fContents, $to, $from);
} elseif (function_exists('iconv')) {
// iconv 遇無法轉換字符會拋 E_NOTICE,加 //IGNORE 忽略或 //TRANSLIT 音譯
return @iconv($from, $to . '//IGNORE', $fContents);
}
return $fContents; // 無擴展時原樣返回
}
// 陣列:遞歸處理鍵與值,鍵名變更時刪除舊鍵
if (is_array($fContents)) {
foreach ($fContents as $key => $val) {
$_key = auto_charset($key, $from, $to);
$fContents[$_key] = auto_charset($val, $from, $to);
if ($key !== $_key) unset($fContents[$key]);
}
return $fContents;
}
return $fContents; // resource 等其他類型不處理
}
<?php
// 1. 單一字符串
$gbkStr = "測試"; // 假設此文件為 GBK 編碼保存
echo auto_charset($gbkStr, 'gbk', 'utf-8'); // 正確輸出 UTF-8 "測試"
// 2. 關聯陣列(鍵值皆轉換)
$post = [
'用戶名' => '張三',
'地址' => '北京市朝陽區',
'tags' => ['技術', 'PHP', '編碼']
];
$utf8Post = auto_charset($post, 'gbk', 'utf-8');
// 結果鍵名也變為 UTF-8:['用戶名'=>'張三', '地址'=>'北京市朝陽區', 'tags'=>['技術','PHP','編碼']]
// 3. $_POST/$_GET 批量轉換(中介軟體模式)
$_POST = auto_charset($_POST, 'gbk', 'utf-8');
$_GET = auto_charset($_GET, 'gbk', 'utf-8');
當連 $from 都不確定時,需先「猜測」編碼。PHP 提供兩條路徑:
mb_check_encoding()(推薦,準確)<?php
function detect_encoding_mb(string $str): string {
// 按優先級嘗試
$encodings = ['UTF-8', 'GBK', 'GB2312', 'BIG5', 'ISO-8859-1'];
foreach ($encodings as $enc) {
if (mb_check_encoding($str, $enc)) return $enc;
}
return 'UTF-8'; // 預設回退
}
// 使用
$raw = file_get_contents('unknown.csv');
$from = detect_encoding_mb($raw);
$data = auto_charset($raw, $from, 'utf-8');
<?php
/**
* 簡易判斷:是否為合法 GBK(雙字節範圍 0x81-0xFE)
* 注意:誤判率較高,僅作無擴展環境應急
*/
function is_gbk(string $str): bool {
for ($i = 0, $len = strlen($str); $i < $len; $i++) {
$c = ord($str[$i]);
if ($c <= 0x7F) continue; // ASCII
if ($c >= 0x81 && $c <= 0xFE) {
// 雙字節首字節,檢查次字節
if ($i + 1 >= $len) return false;
$c2 = ord($str[$i + 1]);
if ($c2 >= 0x40 && $c2 <= 0xFE && $c2 != 0x7F) {
$i++; // 跳過次字節
continue;
}
}
return false; // 不符合 GBK 規則
}
return true;
}
/**
* 簡易判斷:是否為合法 UTF-8(正則法,較準確)
*/
function is_utf8(string $str): bool {
return (bool)preg_match('%^(?:
[\x09\x0A\x0D\x20-\x7E] # ASCII
| [\xC2-\xDF][\x80-\xBF] # 非過長 2 字節
| \xE0[\xA0-\xBF][\x80-\xBF] # 排除過長 3 字節
| [\xE1-\xEC\xEE-\xEF][\x80-\xBF]{2} # 直 3 字節
| \xED[\x80-\x9F][\x80-\xBF] # 排除代理區
| \xF0[\x90-\xBF][\x80-\xBF]{2} # 平面 1-3
| [\xF1-\xF3][\x80-\xBF]{3} # 平面 4-15
| \xF4[\x80-\x8F][\x80-\xBF]{2} # 平面 16
)*$%xs', $str);
}
// 綜合檢測
function detect_simple(string $str): string {
if (is_utf8($str)) return 'UTF-8';
if (is_gbk($str)) return 'GBK';
return 'UTF-8'; // 預設
}
<?php
// middleware/CharsetConvert.php
class CharsetConvert {
public function handle($request, \Closure $next) {
// 僅處理 application/x-www-form-urlencoded / multipart
if (in_array($request->getContentType(), ['application/x-www-form-urlencoded', 'multipart/form-data'])) {
$input = $request->all(); // 獲取所有輸入(含檔案資訊陣列)
$request->replace(auto_charset($input, 'gbk', 'utf-8')); // 或自動檢測
}
return $next($request);
}
}
| 場景 | 建議 |
|---|---|
| 高併發 API | 避免運行時檢測,**協議約定編碼**(強制客戶端發 UTF-8 或攜帶 Content-Type: application/json; charset=utf-8) |
| 大文件/大陣列 | auto_charset 遞歸拷貝內存翻倍,考慮流式處理或分塊轉換 |
| 資料庫讀寫 | 連接層指定 charset=utf8mb4,**不要**在業務代碼裡轉換 |
| iconv //IGNORE | 會靜默丟棄無法轉換字符,關鍵業務改用 //TRANSLIT 或預先清洗 |
| 鍵名轉換 | 陣列鍵名若包含非 ASCII,轉碼後鍵名改變,unset($old) 必不可少 |
auto_charset($data, 'gbk', 'utf-8'),支援多維陣列、鍵值同步。mb_check_encoding 逐個嘗試;無 mbstring 時用正則/啟發式兜底。將編碼轉換封裝為通用函數/中介軟體,徹底告別「亂碼」、「問號」、「菱形問號」等經典噩夢。