首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 字符編碼自動轉換終極方案:支援陣列、多編碼檢測、mbstring/iconv 雙引擎

     編輯:PHP 綜合知識

痛點:未知來源編碼,統一轉 UTF-8

接收第三方回調、舊系統遷移、用戶上傳 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 提供兩條路徑:

方案 A:mbstring 擴展 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');

方案 B:無 mbstring 時的啟發式判斷(GBK/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'; // 預設
}

實戰中介軟體:Laravel/ThinkPHP/Slim 通用寫法

<?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) 必不可少

總結

  1. 已知編碼:直接用 auto_charset($data, 'gbk', 'utf-8'),支援多維陣列、鍵值同步。
  2. 未知編碼:優先 mb_check_encoding 逐個嘗試;無 mbstring 時用正則/啟發式兜底。
  3. 架構層面:在中介軟體/入口文件統一轉換,**業務代碼只見 UTF-8**。
  4. 擴展依賴:生產環境**務必安裝 mbstring**,iconv 只作兜底。

將編碼轉換封裝為通用函數/中介軟體,徹底告別「亂碼」、「問號」、「菱形問號」等經典噩夢。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved