ThinkSNS+ 是如何计算字符显示长度的(使用 Laravel 自定义验证规则)

好久没分享了,今天我们来聊一下可能很对人都会头疼的东西。
没错,显示长度,需求是这样子的,在字符的显示上,两个英文单词才占一个中文或者其他语言的显示长度。如下:

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 项目程序设置跨域需求。
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 15

str_word_count 不是返回字符串中单词的使用情况吗? http://php.net/manual/zh/function.str-word...

6年前 评论
medz

@hacklee 这句话的意思是返回单字节单词预设的请看,我在前面把所有单字节的字母都剔除了,这个函数就会返回每个多子节字符族的数量。

6年前 评论

@medz 是我理解题意错误吗?我测试 str_word_count 对英文外的字符不生效,传入 $value='ab哈'$double 计算为0,此验证最终的 $length = 1

6年前 评论
medz

@hacklee 看上面的图

6年前 评论
medz

@hacklee

file

file
明白了嚒?

6年前 评论
medz

@hacklee 你复制下面的代码运行:

<?php

$str = 'ab哈hello嘿嘿'; // 显示宽度应该是 6.5

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

var_dump('单字节显示长度:'.$single);

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

var_dump('多子节个数: '.$double);

$length = $single + $double;

var_dump('总共显示长度:' . $length);
6年前 评论

@medz 我这边显示的宽度是3.5 ,说白了就是你代码 str_word_count(preg_replace('([a-zA-Z0-9_])', '', $value)); 在我这边运行得到都是0。归根到底应该就是环境不一样,运行结果有区别罗。

file

6年前 评论
medz

@hacklee 非常感谢您的测试结果,我正在关注这个问题,请问你的php环境是上面?我们的程序要求是php7及以上版本,nts版本的php,windows、Linux、macOS测试这边都是回返回多字节个数的。

6年前 评论

@medz 我这边三个环境测试,str_word_count 都是只对英文单词有效。

  • CentOS 环境

file

  • Windows7 Laragon 集成环境

file

  • 第三方在线环境 (5.3-7.0)均有问题

https://www.shucunwang.com/RunCode/php7/

6年前 评论
medz

@hacklee 感谢,这个问题我持续关注下。

6年前 评论

@medz 第四个环境 homestead-box-2.0 也是有问题

file

6年前 评论
medz

@hacklee 好的,我抽时间关注下这个问题。我不太确定,因为我这边几个环境都会成功。

6年前 评论

方法不错,关注中,期待通用性都能成功。

6年前 评论
medz

@hacklee
@yaobaliu
继:我朝特有需求之《英文字符占 0.5 个,中文字符占 1 个》
其实是我想多了,通用性在这里解决了~感谢两位。

6年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
创始人 @ Odore Inc.
文章
33
粉丝
202
喜欢
532
收藏
198
排名:23
访问:24.7 万
私信
所有博文
社区赞助商