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

PHP 正確反轉 UTF-8 字串:mbstring 與 Grapheme Cluster 完整解決方案

     編輯:PHP 相關知識

PHP 正確反轉 UTF-8 字串:mbstring 與 Grapheme Cluster 完整解決方案

字串反轉在單位元組編碼 (ASCII/ISO-8859) 中簡單易行,但 UTF-8 多位元組字元、Emoji (ZWJ 序列)、組合字元若按位元組或 Code Point 反轉會導致亂碼或語義破壞。本文提供三層級解決方案:mbstring Code Point 級、intl Grapheme Cluster 級、純 PHP 降級實現。

問題演示

$str = 'Hello世界👨‍👩‍👧‍👦'; // ASCII + 中文 + 家庭 Emoji (ZWJ 序列)

echo "原始: $str\n";
echo "strlen: " . strlen($str) . "\n";        // 位元組數
echo "mb_strlen: " . mb_strlen($str) . "\n";  // Code Point 數
echo "grapheme_strlen: " . grapheme_strlen($str) . "\n"; // 用戶感知字元數

// ❌ 位元組級反轉 (strrev) -> 亂碼
echo "strrev: " . strrev($str) . "\n";

// ❌ Code Point 級反轉 (mbstring) -> Emoji 內部順序錯亂
$arr = [];
for ($i = 0; $i < mb_strlen($str); $i++) $arr[] = mb_substr($str, $i, 1);
echo "mb_rev: " . implode(array_reverse($arr)) . "\n";

// ✅ Grapheme Cluster 級反轉 (intl) -> 正確
$clusters = grapheme_str_split($str); // PHP 8.0+
echo "grapheme_rev: " . implode(array_reverse($clusters)) . "\n";

方案一:mbstring Code Point 級反轉 (通用、無額外擴展)

/**
 * UTF-8 字串反轉 (Code Point 級)
 * 適用:一般多語言文字,不含複雜 Emoji/組合字元
 * 需求:mbstring 擴展 (PHP 核心內建)
 */
function mbStrRev(string $str, string $encoding = 'UTF-8'): string {
    if (!mb_check_encoding($str, $encoding)) {
        throw new InvalidArgumentException("Invalid $encoding string");
    }
    $len = mb_strlen($str, $encoding);
    $rev = '';
    for ($i = $len - 1; $i >= 0; $i--) {
        $rev .= mb_substr($str, $i, 1, $encoding);
    }
    return $rev;
}

// 測試
echo mbStrRev('Hello世界') . "\n"; // 界世olleH
echo mbStrRev('中文測試') . "\n";   // 試測文中

方案二:Grapheme Cluster 級反轉 (最正確、需 intl 擴展)

/**
 * UTF-8 字串反轉 (Grapheme Cluster 級)
 * 適用:所有 Unicode 文字,含 Emoji ZWJ 序列、組合字元、旗幟 Emoji
 * 需求:intl 擴展 (pecl install intl / apt install php-intl)
 * PHP 8.0+ 可用 grapheme_str_split()
 */
function graphemeStrRev(string $str): string {
    if (!extension_loaded('intl')) {
        throw new RuntimeException('intl extension required for grapheme operations');
    }
    // PHP 8.0+
    if (function_exists('grapheme_str_split')) {
        return implode(array_reverse(grapheme_str_split($str)));
    }
    // PHP 7.x 降級實現
    $clusters = [];
    $len = grapheme_strlen($str);
    for ($i = 0; $i < $len; $i++) {
        $clusters[] = grapheme_substr($str, $i, 1);
    }
    return implode(array_reverse($clusters));
}

// 測試
$emoji = '👨‍👩‍👧‍👦'; // U+1F468 U+200D U+1F469 U+200D U+1F467 U+200D U+1F466
echo graphemeStrRev("AB{$emoji}CD") . "\n"; // DC👨‍👩‍👧‍👦BA (Emoji 完整保持)
echo graphemeStrRev('e\u0301f') . "\n";     // fé (é = e + ́ 組合字元正確反轉)

方案三:純 PHP 降級實現 (無 mbstring/intl 環境)

/**
 * UTF-8 字串反轉 (純 PHP,按 Code Point)
 * 原理:解析 UTF-8 位元組序列判斷字元邊界
 * 注意:不處理組合字元/ZWJ,僅保證不產生斷碼
 */
function utf8StrRev(string $str): string {
    $bytes = strlen($str);
    $rev = '';
    for ($i = $bytes - 1; $i >= 0; ) {
        $byte = ord($str[$i]);
        // 判斷 UTF-8 字元首位元組模式
        if (($byte & 0x80) === 0x00) {        // 0xxxxxxx (ASCII)
            $charLen = 1;
        } elseif (($byte & 0xE0) === 0xC0) {  // 110xxxxx (2 bytes)
            $charLen = 2;
        } elseif (($byte & 0xF0) === 0xE0) {  // 1110xxxx (3 bytes)
            $charLen = 3;
        } elseif (($byte & 0xF8) === 0xF0) {  // 11110xxx (4 bytes)
            $charLen = 4;
        } else {
            // 非法位元組,視為單字節
            $charLen = 1;
        }
        $start = $i - $charLen + 1;
        $rev .= substr($str, $start, $charLen);
        $i = $start - 1;
    }
    return $rev;
}

// 測試
echo utf8StrRev('Hello世界') . "\n"; // 界世olleH

效能與適用性對比

方案擴展依賴正確性效能 (相對)適用場景
strrev無❌ 破壞多位元組1x (最快)純 ASCII / 二進制資料
utf8StrRev (純 PHP)無✅ 無斷碼 / ❌ 組合字元錯誤~5x無擴展環境、簡單多語言
mbStrRev (mbstring)mbstring✅ 無斷碼 / ❌ ZWJ/組合字元錯誤~3x一般業務、CJK 文字、簡單 Emoji
graphemeStrRev (intl)intl✅ 完全正確 (Unicode 標準)~4x國際化產品、Emoji 密集、無障礙要求

現代最佳實踐建議

  1. 專案啟用 intl 擴展:Dockerfile RUN docker-php-ext-install intl,佔用極小,功能極大 (日期、數字、貨幣、字串、IDN 等)。
  2. 封裝統一工具類:
    class UnicodeString {
        public static function reverse(string $str): string {
            if (extension_loaded('intl')) return graphemeStrRev($str);
            if (extension_loaded('mbstring')) return mbStrRev($str);
            return utf8StrRev($str);
        }
        public static function length(string $str): int {
            if (extension_loaded('intl')) return grapheme_strlen($str);
            if (extension_loaded('mbstring')) return mb_strlen($str);
            return strlen($str); // fallback
        }
        public static function substr(string $str, int $start, int $length = null): string {
            if (extension_loaded('mbstring')) return mb_substr($str, $start, $length, 'UTF-8');
            // 簡易 UTF-8 substr 實現...
        }
    }
  3. 靜態分析規則:PHPStan 規則禁止在非二進制上下文使用 strrev/strlen/substr。
  4. 測試覆蓋:單元測試必須包含:CJK、Emoji (單碼點、ZWJ序列、旗幟)、組合字元、混合 ASCII、空字串、單字元。

正確的字串反轉不只是算法題,更是國際化軟體品質的基石。選擇適合專案國際化程度的方案,並建立統一工具類,能避免無數「為什麼 Emoji 反轉變亂碼」的深夜除錯。

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