substr() 按**字節**截取,UTF-8 編碼下一個漢字佔 3 字節,直接截取必然導致半個字符殘留,瀏覽器渲染出現 亂碼。本文提供從內置函數到自定義兼容函數的完整解決方案。
substr 截在奇數位置即亂碼。substr 截在非 3 整數倍位置即亂碼。<?php
// 確保腳本編碼與聲明一致
declare(encoding='UTF-8'); // PHP 7.1+ 或文件保存為 UTF-8 無 BOM
mb_internal_encoding('UTF-8'); // 設置內部編碼
$str = "你好世界,Hello World!";
// 截取前 10 個字符 (含中英文)
$sub = mb_substr($str, 0, 10, 'UTF-8'); // 結果: "你好世界,Hello"
echo $sub;
?>
✅ **優點**:原生支持多字節、支持起始位置負數 (從末尾算起)、性能好。
⚠️ **注意**:生產環境務必在 `php.ini` 設置 `mbstring.internal_encoding = UTF-8` 或入口文件調用 `mb_internal_encoding('UTF-8')`。
<?php
$str = "這是一段很長的中文標題需要截取顯示";
// 寬度按字符寬度計算:中文算 2,英文算 1 (近似視覺寬度)
// 第 4 參數為省略標記,第 5 參數編碼
echo mb_strimwidth($str, 0, 20, '...', 'UTF-8');
// 輸出: "這是一段很長的中文..."
?>
<?php
/**
* 純 PHP 實現 UTF-8 字符串截取,不依賴 mbstring
* @param string $str 字符串
* @param int $start 起始字符位置 (0 起)
* @param int $length 截取字符數
* @return string
*/
function utf8_substr($str, $start, $length) {
if ($length <= 0) return '';
$bytes = strlen($str);
$chars = 0;
$startByte = 0;
$endByte = $bytes;
// 1. 定位起始字節位置
for ($i = 0; $i < $bytes && $chars < $start; $i++) {
$c = ord($str[$i]);
if ($c < 0x80) $i += 0; // 1 byte (ASCII)
elseif ($c < 0xE0) $i += 1; // 2 bytes
elseif ($c < 0xF0) $i += 2; // 3 bytes
else $i += 3; // 4 bytes (Emoji)
$chars++;
}
$startByte = $i;
if ($chars < $start) return ''; // 起始位置超出長度
// 2. 定位結束字節位置
$chars = 0;
for ($i = $startByte; $i < $bytes && $chars < $length; $i++) {
$c = ord($str[$i]);
if ($c < 0x80) $i += 0;
elseif ($c < 0xE0) $i += 1;
elseif ($c < 0xF0) $i += 2;
else $i += 3;
$chars++;
}
$endByte = $i;
return substr($str, $startByte, $endByte - $startByte);
}
// 測試
$str = "😀你好abc世界👍";
echo utf8_substr($str, 1, 4); // 輸出: "你好ab" (Emoji 算 1 字符)
?>
<?php
class StringHelper {
public static function cut($str, $length, $charset = 'UTF-8', $suffix = '...') {
$charset = strtoupper($charset);
if ($charset === 'UTF-8') {
if (function_exists('mb_substr')) {
$cut = mb_substr($str, 0, $length, 'UTF-8');
} else {
$cut = self::utf8Substr($str, 0, $length);
}
} elseif ($charset === 'GBK' || $charset === 'GB2312') {
// GBK: 逐字節判斷高位位元
$cut = '';
for ($i = 0, $len = strlen($str); $i < $len && mb_strlen($cut, 'GBK') < $length; $i++) {
if (ord($str[$i]) > 0x80) {
$cut .= $str[$i] . $str[++$i];
} else {
$cut .= $str[$i];
}
}
} else {
$cut = substr($str, 0, $length);
}
// 只有真正截斷了才加後綴
if (mb_strlen($cut, $charset) < mb_strlen($str, $charset)) {
$cut .= $suffix;
}
return $cut;
}
// ... 內部 utf8Substr 實現同上
}
// 使用
echo StringHelper::cut($gbkStr, 20, 'GBK');
echo StringHelper::cut($utf8Str, 20, 'UTF-8');
?>
<?php
// 1. 文章摘要生成 (去標籤 + 截取)
function makeExcerpt($html, $length = 200) {
$text = strip_tags($html);
$text = preg_replace('/\s+/', ' ', $text); // 合併空白
return StringHelper::cut($text, $length, 'UTF-8');
}
// 2. 列表標題截取 (保證不破壞 HTML 結構 - 需配合 HTML 截取庫如 htmLawed)
// 簡易版:純文本標題
function cutTitle($title, $len = 30) {
return StringHelper::cut($title, $len, 'UTF-8');
}
?>