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

PHP 中文亂碼終極解決方案:編碼檢測、轉換與資料庫層面的完整實戰

     編輯:PHP 綜合知識

前言

中文亂碼是 PHP 開發者永恆的痛點:UTF-8、GBK、GB2312、BIG5、CP936 交織,加上瀏覽器、資料庫、API、檔案編碼不一致,稍有不慎即成「亂碼現場」。本文系統梳理編碼轉換核心函數、資料庫連線編碼設定、統一處理類封裝、以及**從源頭避免亂碼的工程化規範**。

一、核心編碼轉換函數對比

函數優點缺點適用場景
iconv() 內建、支援 //TRANSLIT //IGNORE 模式、標準 POSIX 某些字符無法轉換時靜默失敗、需知確切源編碼 已知確切編碼的單字串轉換
mb_convert_encoding() 支援自動檢測、處理陣列/物件、多字節安全、效能較好 需 mbstring 擴展(現代 PHP 通常內建) 首選方案:未知編碼、批次轉換、資料庫結果集
mb_detect_encoding() 可檢測字串編碼 不可靠:短字串、相似編碼(GBK/GB2312)易誤判 輔助判斷,**不可作為唯一依據**

二、實戰:資料庫層面的編碼統一(以 SQL Server 為例)

場景:PHP (UTF-8) ↔ SQL Server (GBK/CP936),第三方系統固定編碼無法修改。

1. PDO 連線指定編碼(推薦)

<?php
$dsn = "sqlsrv:Server=127.0.0.1;Database=MyDB;CharacterSet=UTF-8";
// CharacterSet=UTF-8 告訴驅動:PHP 端用 UTF-8,驅動自動與資料庫編碼轉換
$pdo = new PDO($dsn, 'user', 'pass', [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_EMULATE_PREPARES => false,
]);

// 測試
$stmt = $pdo->query("SELECT TOP 1 name FROM users");
$row = $stmt->fetch(PDO::FETCH_ASSOC);
echo "資料庫讀取: " . $row['name'] . "\n"; // 應正常顯示中文
?>

2. 舊版 mssql/odbc 驅動:手動轉換

<?php
// 連線不指定編碼,讀出來是 GBK 位元組串流
$conn = odbc_connect("Driver={SQL Server};Server=127.0.0.1;Database=MyDB;", 'user', 'pass');
$result = odbc_exec($conn, "SELECT name FROM users");

$rows = [];
while ($row = odbc_fetch_array($result)) {
    // 關鍵:將每個欄位從 GBK 轉 UTF-8
    $rows[] = array_map(fn($v) => is_string($v) ? mb_convert_encoding($v, 'UTF-8', 'CP936') : $v, $row);
}
print_r($rows);
?>

三、萬能轉換類:auto_charset() 深度解析與增強版

<?php
/**
 * 統一編碼轉換工具類
 * 支援:字串、陣列(鍵值皆轉)、巢狀結構、物件(轉陣列)、null/純量保護
 */
class CharsetConverter {
    // 常見編碼別名標準化
    private static array $aliasMap = [
        'utf8' => 'UTF-8', 'utf-8' => 'UTF-8',
        'gbk' => 'GBK', 'cp936' => 'GBK', 'gb2312' => 'GBK',
        'big5' => 'BIG-5', 'big-5' => 'BIG-5',
        'latin1' => 'ISO-8859-1', 'iso-8859-1' => 'ISO-8859-1',
    ];

    public static function normalize(string $encoding): string {
        $key = strtolower($encoding);
        return self::$aliasMap[$key] ?? strtoupper($encoding);
    }

    /**
     * 核心轉換方法
     * @param mixed $data 輸入資料
     * @param string $from 源編碼 (預設 GBK)
     * @param string $to 目標編碼 (預設 UTF-8)
     * @return mixed 相同結構的轉換後資料
     */
    public static function convert(mixed $data, string $from = 'GBK', string $to = 'UTF-8'): mixed {
        $from = self::normalize($from);
        $to = self::normalize($to);

        // 1. 無需轉換情況
        if ($from === $to) return $data;
        if ($data === null || $data === '' || (is_scalar($data) && !is_string($data))) return $data;

        // 2. 字串轉換
        if (is_string($data)) {
            if (!function_exists('mb_convert_encoding')) {
                trigger_error('mbstring 擴展未安裝,無法轉換編碼', E_USER_WARNING);
                return $data;
            }
            return mb_convert_encoding($data, $to, $from);
        }

        // 3. 陣列遞迴轉換(鍵值皆轉)
        if (is_array($data)) {
            $result = [];
            foreach ($data as $key => $value) {
                $newKey = self::convert($key, $from, $to);
                $newVal = self::convert($value, $from, $to);
                $result[$newKey] = $newVal;
            }
            return $result;
        }

        // 4. 物件轉陣列再轉換
        if (is_object($data)) {
            return self::convert((array)$data, $from, $to);
        }

        return $data;
    }

    // 便捷靜態方法
    public static function toUtf8(mixed $data, string $from = 'GBK'): mixed {
        return self::convert($data, $from, 'UTF-8');
    }

    public static function fromUtf8(mixed $data, string $to = 'GBK'): mixed {
        return self::convert($data, 'UTF-8', $to);
    }
}

// ========== 使用範例 ==========
// 1. 資料庫結果集轉換
$gbkRows = [
    ['id'=>1, 'name'=>'張三', 'city'=>'北京'],
    ['id'=>2, 'name'=>'李四', 'city'=>'上海'],
];
$utf8Rows = CharsetConverter::toUtf8($gbkRows); // 鍵值皆轉

// 2. API 請求參數轉換(接收 GBK POST)
$_POST = CharsetConverter::toUtf8($_POST, 'GBK');

// 3. 輸出 GBK 給舊系統
$gbkXml = CharsetConverter::fromUtf8($utf8Xml, 'GBK');
?>

四、從源頭避免亂碼:工程化規範清單

✅ 強制規範(專案啟動即落地)

  1. 所有 PHP 檔案儲存為 UTF-8 無 BOM(IDE 統一設定、.editorconfig 強制)。
  2. 資料庫、資料表、欄位預設排序規則:utf8mb4_unicode_ci(支援 Emoji、完整 Unicode)。
  3. 連線字串明確指定字符集:PDO DSN charset=utf8mb4、mysqli set_charset('utf8mb4')。
  4. HTTP 響應 Header 強制宣告:header('Content-Type: application/json; charset=utf-8'); 或 Nginx/Apache 全域設定。
  5. HTML 頁面 <meta charset="utf-8"> 放在 <head> 首行。
  6. 前端表單提交 accept-charset="utf-8"。
  7. 外部 API 對接:合約文檔強制註明編碼,驗收時用 curl -v 檢查 Content-Type 與實際位元組。
  8. 日誌/檔案輸出統一 UTF-8,避免 error_log 寫入 GBK 導致後續分析工具亂碼。

五、常見亂碼現象與對應修復

現象原因修復
(菱形問號) UTF-8 解碼無效位元組序列 檢查源資料是否真為 UTF-8;用 mb_convert_encoding($str, 'UTF-8', 'GBK')
鏈靈 鏈靈 (兩個中文變四個亂碼) UTF-8 被當作 GBK 讀取(雙字節誤讀) 資料庫連線指定 charset=utf8mb4;Header 宣告 UTF-8
??? (問號) 目標編碼無法表示該字符(如 UTF-8 存入 latin1 欄位) 修改欄位/資料庫編碼為 utf8mb4;或用 //IGNORE 模式丟棄
瀏覽器正常、資料庫亂碼 寫入時未轉碼、連線編碼不匹配 PDO DSN 加 charset;mysqli_set_charset
BOM 頭干擾 JSON/API 檔案儲存為 UTF-8 with BOM IDE 另存為 UTF-8 無 BOM;PHP 端 substr($str, 0, 3) === "\xEF\xBB\xBF" && $str = substr($str, 3)

六、總結

PHP 中文亂碼的本質是**編碼不一致導致的位元組序列錯誤解碼**。解決之道:

  1. 工具層:掌握 mb_convert_encoding 為核心的轉換利器,封裝 CharsetConverter 統一處理陣列/巢狀結構。
  2. 資料層:PDO 連線強制 charset=utf8mb4,資料庫預設 utf8mb4_unicode_ci。
  3. 傳輸層:HTTP Header、HTML Meta、表單 accept-charset 三位一體宣告 UTF-8。
  4. 規範層:專案級 .editorconfig、CI 檢查檔案編碼、API 合約註明編碼。

**「預防勝於治療」**:從專案第一天起建立 UTF-8 全鏈路規範,能節省 90% 的亂碼排查時間。

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