直接使用 substr 截取 UTF-8 英文混合字串時,常會切斷多位元組字元導致亂碼。本文提供一個 usubstr 函數,透過前後各檢查 6 個位元組的 UTF-8 編碼規則,智慧補全被切斷的字元,確保截取結果不含殘缺字元,並自動附加省略號。
/**
* 完整單詞的 UTF-8 字串截取
*
* @param string $str 待截取字串
* @param int $start 起始位置 (支援負數)
* @param int|null $length 截取長度,null 表示截取到結尾
* @return string 截取後的字串,超出原長度時自動加 '...'
*/
public static function usubstr($str, $start, $length = null) {
// 1. 先正常截取一次
$res = substr($str, $start, $length);
$strlen = strlen($str);
// 2. 計算前後各 6 位元組區段,用於檢測 UTF-8 邊界
if ($start >= 0) {
// 正數 start:向後取 6 字節 (next),向前取 6 字節 (prev)
$next_start = $start + $length;
$next_len = ($next_start + 6 <= $strlen) ? 6 : $strlen - $next_start;
$next_segm = substr($str, $next_start, $next_len);
$prev_start = ($start - 6 > 0) ? $start - 6 : 0;
$prev_segm = substr($str, $prev_start, $start - $prev_start);
} else {
// 負數 start:轉換為正數索引後同樣處理
$next_start = $strlen + $start + $length;
$next_len = ($next_start + 6 <= $strlen) ? 6 : $strlen - $next_start;
$next_segm = substr($str, $next_start, $next_len);
$start = $strlen + $start;
$prev_start = ($start - 6 > 0) ? $start - 6 : 0;
$prev_segm = substr($str, $prev_start, $start - $prev_start);
}
// 3. 檢查後方 6 字節是否以不完整 UTF-8 開頭,若是則補全
if (preg_match('@^([\x80-\xBF]{0,5})[\xC0-\xFD]?@', $next_segm, $bytes)) {
if (!empty($bytes[1])) {
$res .= $bytes[1];
}
}
// 4. 檢查前方 6 字節是否以不完整 UTF-8 結尾,若是則前綴補全
$ord0 = ord($res[0]);
if (128 <= $ord0 && 191 >= $ord0) { // 首字節為延續字節 (10xxxxxx)
if (preg_match('@[\xC0-\xFD][\x80-\xBF]{0,5}$@', $prev_segm, $bytes)) {
if (!empty($bytes[0])) {
$res = $bytes[0] . $res;
}
}
}
// 5. 若結果短於原字串,加上省略號
if (strlen($res) < $strlen) {
$res .= '...';
}
return $res;
}
| 字節數 | 首字節範圍 | 延續字節範圍 |
|---|---|---|
| 1 (ASCII) | 0x00-0x7F | - |
| 2 | 0xC0-0xDF | 0x80-0xBF |
| 3 | 0xE0-0xEF | 0x80-0xBF |
| 4 | 0xF0-0xF7 | 0x80-0xBF |
$str = "Hello 世界 PHP 編程"; // 從位置 6 開始截取 10 個位元組 echo usubstr($str, 6, 10); // 輸出: "世界 PHP..." (不會出現亂碼或半個中文字)
mb_substr 配合 mb_internal_encoding('UTF-8')// 內建多位元組安全截取,按字元數計算 $str = "Hello 世界 PHP 編程"; echo mb_substr($str, 6, 10, 'UTF-8'); // 輸出: "世界 PHP 編" echo mb_strimwidth($str, 6, 20, '...', 'UTF-8'); // 含省略號
若專案環境支援 mbstring 擴展,優先使用內建函數;否則本 usubstr 是無擴展依賴的優秀備選。