PHP 內建 substr 按位元組截取,對多位元組編碼 (UTF-8 中文佔 3 位元組、GB2312 佔 2 位元組) 會導致亂碼。mb_substr 雖支援多位元組,但需正確指定編碼。本文提供一個統一介面、自動編碼轉換、支援負索引、邊界保護 的通用截取函數。
<?php
/**
* 多編碼安全字串截取
*
* @param string $str 原始字串
* @param int $start 起始位置 (0-based,支援負數:-1 為倒數第 1 字元)
* @param int $length 截取長度 (預設 -1 表示截取到結尾)
* @param string $encoding 輸入/輸出編碼 (預設 'UTF-8',支援 'GB2312', 'GBK', 'BIG-5' 等)
* @param string $suffix 截斷後綴 (預設 '...')
* @return string 截取後字串
*/
function mb_cut(string $str, int $start = 0, int $length = -1, string $encoding = 'UTF-8', string $suffix = '...'): string {
// 1. 統一內部處理為 UTF-8
$internalEnc = 'UTF-8';
if (strtoupper($encoding) !== $internalEnc) {
$str = @mb_convert_encoding($str, $internalEnc, $encoding);
if ($str === false) {
// 編碼轉換失敗,回傳原字串避免資料損失
return $str;
}
}
// 2. 取得總字元數
$totalLen = mb_strlen($str, $internalEnc);
if ($totalLen === 0) return '';
// 3. 處理負數起始索引 (類似 Python/JS slice)
if ($start < 0) {
$start = max(0, $totalLen + $start);
}
// 4. 邊界檢查
if ($start >= $totalLen) return '';
// 5. 處理長度參數
if ($length < 0) {
$length = $totalLen - $start; // 截取到結尾
} elseif ($length === 0) {
return '';
}
// 6. 確保不超出邊界
$length = min($length, $totalLen - $start);
// 7. 執行截取
$result = mb_substr($str, $start, $length, $internalEnc);
// 8. 只有真正截斷時才加後綴
if ($start + $length < $totalLen) {
$result .= $suffix;
}
// 9. 轉回原始編碼
if (strtoupper($encoding) !== $internalEnc) {
$result = @mb_convert_encoding($result, $encoding, $internalEnc);
}
return $result;
}
// 別名函數:常見用法簡化
function cut_str(string $str, int $len, string $encoding = 'UTF-8', string $suffix = '...'): string {
return mb_cut($str, 0, $len, $encoding, $suffix);
}
?>
<?php
header('Content-Type: text/html; charset=UTF-8');
$utf8 = "你好世界 Hello World";
$gbk = mb_convert_encoding($utf8, 'GBK', 'UTF-8');
$big5 = mb_convert_encoding($utf8, 'BIG-5', 'UTF-8');
echo "=== UTF-8 測試 ===\n";
echo "原文: $utf8\n";
echo "前6字: " . mb_cut($utf8, 0, 6) . "\n"; // 你好世界 H...
echo "倒數4字: " . mb_cut($utf8, -4) . "\n"; // o World
echo "第3-5字: " . mb_cut($utf8, 3, 3) . "\n"; // 世界
echo "\n=== GBK 測試 (指定編碼) ===\n";
echo "原文(GBK): " . bin2hex($gbk) . "\n";
echo "前4字: " . bin2hex(mb_cut($gbk, 0, 4, 'GBK')) . "\n"; // 你好世...
echo "\n=== 邊界保護 ===\n";
echo "長度不足: '" . mb_cut("Hi", 0, 10) . "'\n"; // Hi (無後綴)
echo "起始超界: '" . mb_cut("Hi", 5, 3) . "'\n"; // '' (空字串)
echo "負索引超界: '" . mb_cut("Hi", -10, 3) . "'\n"; // Hi (自動修正為 0)
echo "\n=== 實際場景:文章摘要 ===\n";
$article = "PHP 是一種廣泛使用的開源腳本語言,特別適合 Web 開發並可嵌入 HTML 中。";
echo "摘要(30字): " . cut_str($article, 30) . "\n";
// 輸出: PHP 是一種廣泛使用的開源腳本語言,特別適合 Web 開發並可嵌入 HTML...
?>
頻繁呼叫 mb_convert_encoding 有效能開銷。高頻場景建議:
mb_detect_encoding 快取結果。| 函數 | 編碼感知 | 多位元組安全 | 效能 | 建議場景 |
|---|---|---|---|---|
substr | 否 | ❌ (斷碼) | 極快 | 純 ASCII / 二進制資料 |
mb_substr | 需指定 | ✅ | 中 | 已知編碼、單一編碼環境 |
mb_cut (本文) | 自動轉換 | ✅ | 中 (含轉碼) | 混合編碼輸入、舊系統整合、快速開發 |
grapheme_substr (intl) | UTF-8 only | ✅ (字素簇) | 較慢 | Emoji、複合字元 (如 旗幟、膚色修飾符) 正確計數 |
最佳實踐: 新專案強制全鏈路 UTF-8 (資料庫
utf8mb4、PHPdefault_charset、HTML<meta charset="utf-8">、HTTP Header)。只有在整合舊系統 (GBK/Big5) 時才需要跨編碼處理,且應在邊界層 (Controller/Service 入口) 統一轉碼,核心業務邏輯層僅處理 UTF-8。