ThinkSNS+ 是如何計算字元顯示長度的(使用 Laravel 自定義驗證規則)

medz發表於2017-05-23

好久沒分享了,今天我們來聊一下可能很對人都會頭疼的東西。
沒錯,顯示長度,需求是這樣子的,在字元的顯示上,兩個英文單詞才佔一箇中文或者其他語言的顯示長度。如下:

ab
哈
?

上面排的是兩個英文字母,一個漢字,一個 Emoji 。你會發現,在顯示上佔的寬度是一致的。一些設計上為了好看也要求有這樣的處理。

例如,我們的使用者名稱需求是 最多12個非單位元組字元或者24個單位元組字元的需求也可以混合排的需求,我們寫後端不得不處理這樣的驗證了。

需求規則是 /^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/

在 ThinlSNS+ 中,為了能把這部分驗證公用,所以選擇使用 自定義驗證規則。算了,先說下計算的實現思路吧!

首先,就算是 mb_strlen 也沒法準確的獲取 多位元組字元和單子節字元混合在一起的長度,網上有個說法,漢字佔三個子節,英文陣列半形符號佔一個子節,所以:

(mb_strlen($str) + strleng($str)) / 2

用這個方法可以得到單位元組佔0.5多位元組佔1的計算。但是以中文為例,只有兩萬個漢字才是這種情況,還有六萬多漢字是四個,其次,emoji 也是四個位元組。根本無法準確的計算。

後在在無意間發現一個奇怪的東西 str_word_count 這個函式計算非英文單詞外是除了符號例如中文就是按照漢字個數算的,emoji也是同理。發現這個後就好辦了。我們吧使用者名稱中的 [a-aA-Z0-9_] 剔除掉單獨計算不就是我們要的驗證長度了嗎?

所以,首先我們用:

preg_match_all('/[a-zA-Z0-9_]/', $value, $single);
$single = count($single[0]) / 2;

方式單獨計算出單位元組字元的顯示長度,再用:

$double = str_word_count(preg_replace('([a-zA-Z0-9_])', '', $value));

方式計算出多位元組的長度,最後:

$length = $single + $double;

就得出了顯示長度,實現了,最後封裝成驗證規則:

Validator::extend('display_length', function ($attribute, $value, array $parameters) {
                        if (empty($parameters)) {
                throw new \InvalidArgumentException('Parameters must be passed');
            }

            $min = 0;
            if (count($parameters) === 1) {
                list($max) = $parameters;
            } elseif (count($parameters) >= 2) {
                list($min, $max) = $parameters;
            }

            if (! isset($max) || $max < $min) {
                throw new \InvalidArgumentException('The parameters passed are incorrect');
            }

            // 計算單位元組.
            preg_match_all('/[a-zA-Z0-9_]/', $value, $single);
            $single = count($single[0]) / 2;

            // 多子節長度.
            $double = str_word_count(preg_replace('([a-zA-Z0-9_])', '', $value));

                        // 得出最終計算字元的長度
            $length = $single + $double;

            return $length >= $min && $length <= $max;
});

程式碼是原型程式碼,還沒有進行優化,之後我們只要按照下面的方式用:

$rules = [
    'inputKey' => 'display_length:5', // 表示 0 - 5 顯示長度
    ‘inputkey2’ => 'display_length:4,12' // 表示顯示長度為 4 - 12 
];

很好的解決了這個需求。

我們很樂意在開發基於 Laravel 的 ThinkSNS+ 產品中的技術解決分享給大家,也希望喜歡的朋友能給國內開源產品一點點的支援。
GitHub: https://github.com/zhiyicx/thinksns-plus
求關注求 Star ?

本作品採用《CC 協議》,轉載必須註明作者和本文連結

Seven 的程式碼太渣,歡迎關注我的新擴充包 medz/cors 解決 PHP 專案程式設定跨域需求。

相關文章