編碼問題是 PHP 開發者最常遇到的噩夢:亂碼、問號、菱形問號、資料庫存入錯誤。本文系統梳理「頁面輸出編碼」與「資料庫連線編碼」兩大核心層面,解析 header()、<meta>、Apache AddDefaultCharset 的優先級關係,並提供生產環境標準化配置方案。
<?php
// 必須在任何輸出之前呼叫
header('Content-Type: text/html; charset=UTF-8');
<meta> 宣告AddDefaultCharset (次優先級)# httpd.conf 或 .htaccess
AddDefaultCharset UTF-8
# 或關閉: AddDefaultCharset Off
header(),Apache 不會覆蓋Off) 或設為 UTF-8,統一由 PHP 控制,提高可移植性<meta> 標籤 (最低優先級)<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<!-- HTML5 簡寫 -->
<meta charset="UTF-8">
| 優先級 | 機制 | 可移植性 | 建議 |
|---|---|---|---|
| 1 (最高) | PHP header() | 最佳 (跨伺服器) | **必須在每個入口設定** |
| 2 | Apache AddDefaultCharset | 依賴 Apache 設定 | 設為 Off 或 UTF-8 |
| 3 (最低) | HTML <meta charset> | 僅靜態檔案有效 | 保留作為後備 |
SET NAMES 核心機制SET NAMES?MySQL 連線涉及三個編碼層面:
character_set_client:客戶端發送 SQL 的編碼character_set_connection:連線層轉換目標編碼character_set_results:結果集返回給客戶端的編碼SET NAMES 'utf8' (或 utf8mb4) 相當於同時設定以上三者,確保「PHP 字串 ↔ MySQL 儲存/讀取」編碼一致。
<?php
// 共用資料庫連線檔案 (如 config/db.php)
$pdo = new PDO(
'mysql:host=localhost;dbname=test;charset=utf8mb4',
'user', 'pass',
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
// 關鍵:PDO DSN 中 charset=utf8mb4 會自動執行 SET NAMES
// 若使用 mysqli:
// mysqli_set_charset($link, 'utf8mb4');
]
);
| 頁面 charset | MySQL SET NAMES | 備註 |
|---|---|---|
| UTF-8 | utf8mb4 (推薦) / utf8 | utf8mb4 支援 Emoji、完整 Unicode |
| GBK/GB2312 | gbk | 舊系統相容 |
| BIG5 | big5 | 繁體中文舊系統 |
| 現象 | 可能原因 | 排查步驟 | 解決 |
|---|---|---|---|
| 頁面顯示 (菱形問號) | 瀏覽器以錯誤編碼解碼 UTF-8 位元組 | 檢查 HTTP Header charset、<meta>、Apache 設定 | 統一輸出 header('Content-Type: ...; charset=UTF-8') |
| 頁面顯示 ???? (問號) | 資料庫連線編碼錯誤,字元無法映射被替換為 ? | 檢查 SET NAMES、資料表/欄位編碼、PHP 字串實際編碼 | 連線 DSN 加 charset=utf8mb4、資料表 CHARSET=utf8mb4 |
| 中文變成亂碼 (如 涓婃捣) | UTF-8 位元組被當作 GBK 解碼 | 確認頁面實際輸出編碼、資料庫讀出編碼 | 統一全鏈路 UTF-8 |
| 資料庫存入正常,讀出亂碼 | 寫入/讀取連線編碼不一致 | 檢查寫入腳本與讀取腳本的 SET NAMES | 集中管理 DB 連線,統一編碼設定 |
<?php
// 1. 設定內部編碼 (影響 mbstring、iconv 等函式預設值)
mb_internal_encoding('UTF-8');
mb_http_output('UTF-8');
mb_http_input('UTF-8');
mb_regex_encoding('UTF-8');
// 2. 輸出 HTTP Header (最早執行)
header('Content-Type: text/html; charset=UTF-8');
// 3. 資料庫連線 (PDO 範例)
$pdo = new PDO(
'mysql:host=127.0.0.1;dbname=app;charset=utf8mb4',
getenv('DB_USER'),
getenv('DB_PASS'),
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false, // 使用真實預處理
]
);
# 關閉預設 charset,交由 PHP 完全控制
AddDefaultCharset Off
# 或強制 UTF-8 (若有靜態 HTML 檔案無 PHP 處理)
# AddDefaultCharset UTF-8
-- 新建資料庫/資料表指定 utf8mb4
CREATE DATABASE app CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 修改現有資料表
ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>...</title>
</head>
<body>...</body>
</html>
fgetcsv 讀取前先 mb_convert_encoding($line, 'UTF-8', 'auto') 或指定來源編碼 (如 Big5/GBK)json_encode($data, JSON_UNESCAPED_UNICODE)),HTTP Header Content-Type: application/json; charset=UTF-8編碼問題的根源在於「編碼不一致」。解決之道是**全鏈路統一 UTF-8 (utf8mb4)**:PHP 內部編碼、HTTP 輸出 Header、資料庫連線、資料表欄位、HTML 模板、外部檔案匯入。將編碼設定集中在啟動檔案與資料庫連線類別中,關閉 Apache AddDefaultCharset,讓 PHP 完全掌控輸出編碼,即可從根源杜絕亂碼困擾。