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

PHP 中文截取函數深度解析:多字符集兼容、HTML 標籤保護與性能優化

     編輯:PHP 相關知識

為什麼 PHP 截取中文字符串需要專門的函數?

PHP 內建的 `substr()`、`mb_substr()` 按字節或字符截取,但在中文場景下常面臨三大痛點:

  1. 編碼混雜:UTF-8 中文佔 3 字節,GBK 佔 2 字節,直接用 `substr` 极易產生亂碼(半個漢字)。
  2. 視覺寬度不一:全角字符(中文、標點)視覺寬度約為半角(英文、數字)的 2 倍,單純按字符數截取無法對齊版面。
  3. HTML 標籤破壞:截取包含 HTML 的富文本時,未閉合標籤會導致頁面布局崩潰、XSS 風險。

方案一:`mb_substr` + `mb_strlen` —— 現代標準做法(推薦)

PHP 7.2+ 環境強烈建議啟用 `mbstring` 擴展,直接使用多字節字符串函數:

<?php
/**
 * 安全截取 UTF-8 字符串(按字符數)
 * @param string $str 輸入字符串
 * @param int $length 截取長度(字符數)
 * @param string $suffix 截斷後綴
 * @param bool $stripTags 是否去除 HTML 標籤
 * @return string
 */
function mbStrCut(string $str, int $length, string $suffix = '...', bool $stripTags = true): string
{
    // 1. 可選:去除 HTML 標籤,避免標籤破壞
    if ($stripTags) {
        $str = strip_tags($str);
    }
    
    // 2. 統一內部編碼為 UTF-8
    mb_internal_encoding('UTF-8');
    
    // 3. 若長度足夠,直接返回
    if (mb_strlen($str) <= $length) {
        return $str;
    }
    
    // 4. 截取並添加後綴
    return mb_substr($str, 0, $length) . $suffix;
}

// 測試
$text = "PHP 8.3 引入了 readonly 類屬性、隨機擴展等新特性,大幅提升開發體驗。";
echo mbStrCut($text, 20); // 輸出:PHP 8.3 引入了 readonly 類屬性、隨機...
echo mbStrCut($text, 20, '【閱讀全文】', false); // 保留標籤(若輸入含標籤)

方案二:手動解析 UTF-8 字節 —— 理解底層原理(教學用)

當無法使用 `mbstring` 時(極少數舊環境),需手動解析 UTF-8 編碼規則:

<?php
/**
 * 純 PHP 實現 UTF-8 字符串截取(無 mbstring 依賴)
 * 原理:根據 UTF-8 首字節高位判斷字符字節數
 * 0xxxxxxx (1字節) | 110xxxxx 10xxxxxx (2字節) | 1110xxxx 10xxxxxx 10xxxxxx (3字節) | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx (4字節)
 */
function utf8StrCut(string $str, int $length, string $suffix = '...'): string
{
    $len = strlen($str);
    if ($len <= $length) return $str; // 字節長度已小於目標,直接返回(近似)
    
    $charCount = 0;
    $bytePos = 0;
    
    while ($bytePos < $len && $charCount < $length) {
        $byte = ord($str[$bytePos]);
        
        if ($byte < 0x80) {           // 1 字節 (ASCII)
            $bytePos += 1;
        } elseif (($byte & 0xE0) === 0xC0) { // 2 字節
            $bytePos += 2;
        } elseif (($byte & 0xF0) === 0xE0) { // 3 字節 (中文常見)
            $bytePos += 3;
        } elseif (($byte & 0xF8) === 0xF0) { // 4 字節 (Emoji 等)
            $bytePos += 4;
        } else {
            $bytePos += 1; // 非法字節,跳過
        }
        $charCount++;
    }
    
    // 截取字節並添加後綴
    return substr($str, 0, $bytePos) . $suffix;
}

方案三:處理 HTML 富文本截取 —— 防止標籤破壞

使用 `DOMDocument` 解析 HTML,按文本節點截取並自動閉合標籤:

<?php
/**
 * HTML 安全截取:保留標籤結構,自動閉合未閉合標籤
 * @param string $html 輸入 HTML
 * @param int $maxLength 最大文本字符數(不含標籤)
 * @param string $suffix 截斷後綴
 * @return string 合法 HTML 片段
 */
function htmlStrCut(string $html, int $maxLength, string $suffix = '...'): string
{
    $dom = new DOMDocument();
    // 解決 DOMDocument 不識別 HTML5 實體、編碼問題
    $html = mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8');
    @$dom->loadHTML('
' . $html . '
', LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); $body = $dom->getElementsByTagName('div')->item(0); $textCount = 0; $truncated = false; // 深度優先遍歷文本節點 $walker = new DOMXPath($dom); $textNodes = $walker->query('//text()'); foreach ($textNodes as $textNode) { $text = $textNode->nodeValue; $remaining = $maxLength - $textCount; if ($remaining <= 0) { // 已達限制,移除後續所有節點 $textNode->nodeValue = ''; $truncated = true; continue; } if (mb_strlen($text) > $remaining) { // 當前文本節點需截斷 $textNode->nodeValue = mb_substr($text, 0, $remaining) . $suffix; $truncated = true; // 移除後續兄弟節點 $next = $textNode->nextSibling; while ($next) { $tmp = $next->nextSibling; $next->parentNode->removeChild($next); $next = $tmp; } break; } $textCount += mb_strlen($text); } // 輸出處理後的 HTML(僅 body 內容) $result = ''; foreach ($body->childNodes as $child) { $result .= $dom->saveHTML($child); } return $result ?: $html; // 異常回退原文 } // 測試 $html = '<p>這是一段<strong>包含加粗</strong>和<em>斜體</em>的富文本內容。</p>'; echo htmlStrCut($html, 15); // 輸出:<p>這是一段<strong>包含加粗</strong>和<em>斜體...</em></p>(標籤完整閉合)

方案四:視覺寬度感知截取 —— 解決中英混排對齊難題

中文字符視覺寬度 ≈ 2 × 英文字符。按「視覺寬度單位」截取:

<?php
/**
 * 按視覺寬度截取(中文計 2,英文/數字/標點計 1)
 * 適用於固定寬度容器的文本截斷對齊
 */
function visualWidthCut(string $str, int $maxWidth, string $suffix = '...'): string
{
    $width = 0;
    $suffixWidth = strlen($suffix); // 後綴按半角計算
    $result = '';
    
    // 使用 grapheme 分簇遍歷(支援 Emoji 組合字符)
    $clusters = grapheme_str_split($str);
    
    foreach ($clusters as $char) {
        $charWidth = (preg_match('/^[\x{4e00}-\x{9fff}\x{3000}-\x{303f}\xff00-\xffef]+$/u', $char)) ? 2 : 1;
        
        if ($width + $charWidth + $suffixWidth > $maxWidth) {
            break;
        }
        
        $result .= $char;
        $width += $charWidth;
    }
    
    return $result . ($width < $maxWidth ? '' : $suffix);
}

// 測試
$mixed = "Hello 世界 2024!";
echo visualWidthCut($mixed, 15); // 輸出:Hello 世界 2...(視覺寬度約 15)
// 解析:H(1)e(1)l(1)l(1)o(1) (1)世(2)界(2) (1)2(1)0(1)2(1)4(1)!(2) = 總寬度 18 > 15

性能基準測試與選型建議

方案適用場景性能(10萬次/秒級)依賴推薦指數
`mb_substr` + `mb_strlen`通用純文本截取,現代 PHP 專案★★★★★ 極快(C 層實現)`mbstring` 擴展(默認開啟)⭐⭐⭐⭐⭐
手動 UTF-8 解析極舊環境(PHP < 5.6 無 mbstring)★★☆☆☆ 慢(PHP 層循環)無⭐
`DOMDocument` HTML 截取富文本、CMS 摘要、郵件模板★★★☆☆ 中等(DOM 解析開銷)`libxml`、`dom` 擴展⭐⭐⭐⭐
視覺寬度截取固定寬度 UI、多語言對齊、PDF 生成★★★★☆ 快(正則 + grapheme)PHP 7.4+ (`grapheme_str_split`)⭐⭐⭐⭐

最佳實踐總結

  1. 優先使用 `mb_substr`/`mb_strlen`:確保 `php.ini` 設置 `mbstring.internal_encoding = UTF-8`,代碼中調用 `mb_internal_encoding('UTF-8')` 兜底。
  2. 富文本必須用 `DOMDocument`:切勿用正則或字節截取 HTML,極易產生 XSS 與布局崩潰。
  3. 統一封裝為工具類/Helper 函數:專案中僅暴露 `str_cut($str, $len, $options)` 統一入口,內部根據輸入類型(純文本/HTML/視覺寬度)分派策略。
  4. 前端配合 CSS `text-overflow: ellipsis`:純展示類截斷(如列表卡片標題)優先用 CSS 實現,零 JS 開銷、完美響應式、可選中複製完整文本。
  5. 測試邊界情況:空字符串、純 Emoji、混合換行符、極長無空格字符串、巢狀 HTML 標籤、自閉合標籤(``、`
    `)。

現代 PHP 專案建議:直接引入 symfony/string 組件,使用 u($str)->truncate($len, '...') 享受開箱即用的 Unicode 感知截取、單詞邊界保護、HTML 安全截取等企業級能力,避免重複造輪子。

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