在 ThinkSNS+ 中,如何利用 Laravel 表单验证来验证用户名的(我朝独有需求,两个字母占一个汉字。。。)

2017-06-13 10:03:33 +08:00
 medz

需求

先说下需求吧,这是一个挺操蛋的需求,大多数 PHPer 接到真需求就不想满足它,嗯,国内的 PM 经常会提的,就是用户的用户名或者发表文章的内容或者标题,两个单字节字符才能算一个长度单位。

例如:用户名要求最长 12 位,如果输入英文,可以输入 24 位。开心吧?在 PHP 中 strlen 是按照「字节」计算的,而 mb_strlen 是完整完整字符族算的,都无法满足。

场景

因为 ThinkSNS+ 是使用 Laravel 开发的,所以在注册、登录等操作的时候验证用户名字段都应该使用「表单验证」这就涉及到规则的拓展,很明显,又长度限制和字符范围限制,所以应该拓展两个规则,分别是:

但是长度不只是在用户名场景使用,所以最后修改为「显示长度规则」

AppServiceProviderboot 方法中增加规则。

用户名允许字符规则
Validator::extend('username', function ($attribute, $value) {
    return preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $value);
});

这是我们定的用户名规则,只是不允许非数字开头,也不允许出现特殊字符。

显示长度规则
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 = mb_strlen(preg_replace('([a-zA-Z0-9_])', '', $value));

    $length = $single + $double;

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

你可能觉额长度这么复杂?不,其实不复杂,使用上:

$rules = [
	'foo' => 'display_length:12', // 最长 12 显示长度
    'bar' => 'display_length:4,12', // 显示长度在 4 - 12 之间
];
长度计算方法规则解析

其实一开始想了很多方法,都不理想,例如「转 GBK 」、「(strlen + mb_strlen) / 4 」等,转 GBK 的确也能正确的计算出来,到那时通过测试后发现,转码所消耗的性能比这个方法要更耗性能。字符方法只对中文两万多个常用汉字有用,而且我们希望兼容全球的所有语言以及 emoji。

最后我们想出来了将单字节字符提取出来单独计算,而多子节字符按照正常字符族计算不就好了吗?

首先提出单字节计算:

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

这样之后,每一个单字节字符都按照 0.5 的长度进行计算了。

最后将单字节删除用 mb_strlen 计算除字符族:

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

这样我们就满足了「两个英文=一个中文」的需求了。


上面的打码都是来自于我们团队正在开发的国内开源产品「 ThinkSNS+」,开源不易,大家帮忙点一个 Star 呗!!!

GitHub:https://github.com/zhiyicx/thinksns-plus

ThinkSNS+ 是基于 Latavel 而开发,里面涉及了很多 Laravel 知识点,个人觉得也可以作为 PHPer 学习使用 Laravel 开发应用的一个范例。

4186 次点击
所在节点    PHP
19 条回复
luoyou1014
2017-06-13 11:03:53 +08:00
咦,我记得 ThinkSNS 不是用 ThinkPHP 开发的吗?
ThinkSNS+ 是升级后用 Laravel 重构了还是另外一个开源项目?
sun019
2017-06-13 11:16:46 +08:00
以前一直在用 ThinkSNS 哈哈,后面转 Laravel 了,感谢
leafans
2017-06-13 13:15:01 +08:00
mb_strwidth(),或 mb_strimwidth(),1 个汉字=2 个英文
medz
2017-06-13 14:18:52 +08:00
@luoyou1014 其实不算重构,就是因为 ThinkPHP 已经不适用现在的方向了,所以我们全新开发的,但是后续会增加 ThinkSNS 升级到 ThinkSNS+
medz
2017-06-13 14:24:24 +08:00
@leafans
| 字符 | 宽度 |
|----|----|
|U+0000 - U+0019 | 0 |
|U+0020 - U+1FFF | 1 |
|U+2000 - U+FF60 | 2 |
|U+FF61 - U+FF9F | 1 |
|U+FFA0 - | 2

这是 PHP 官方文档给出的范围表。
medz
2017-06-13 14:25:39 +08:00
@leafans

```shell
php -r "var_dump(mb_strwidth('12 哈😄'));"
```
输出值是 5
medz
2017-06-13 14:27:19 +08:00
@leafans

`12 哈😄` 按照这种需求计算出来应该是 3
jiangzhuo
2017-06-13 14:37:23 +08:00
还好吧,我见过按照宋体渲染出来的宽度作为长度的需求。
willywu001
2017-06-13 16:26:48 +08:00
github 上 安装连接打不开了。怎么回事
KomeijiSatori
2017-06-13 17:49:30 +08:00
echo iconv_strlen("喵喵喵","UTF-8"); //3
hst001
2017-06-13 18:44:19 +08:00
占宽很常见的需求吧,知道编码很容易计算
changwei
2017-06-13 20:22:09 +08:00
反正我就是一直用的 iconv 转 gbk 来实现这种计算,话说这得多大的用户量和并发才能体现出这一点性能差距啊。。。
leafans
2017-06-13 22:22:03 +08:00
@medz 特殊情况 特殊处理。。。 - - |||
$value = '12 哈😄';
preg_match_all('/[\x{1f600}-\x{1f64f}]/u', $value, $result);
echo (count($result[0]) + mb_strwidth($value)) / 2;
leafans
2017-06-13 22:36:29 +08:00
@medz 又试了下,mb_strwidth() 对大部分看上去比较宽的特殊符号按 1 个算,适用范围不广。
medz
2017-06-14 11:20:50 +08:00
@changwei 转 gbk 确实最简单的方法,不过我这边不用主要就是因为字符集转换在大量用户的情况下远比正则低。
medz
2017-06-14 11:21:25 +08:00
@leafans 是滴,就是官方文档也给出了范围,所以才选择造轮子。
medz
2017-06-14 13:48:01 +08:00
@hst001 你的想法其实就是知道编码,转 gbk 咯~但是帖子里我也提到了,不用转码计算,是因为字符集转换比正则更耗性能。
hst001
2017-06-14 18:21:25 +08:00
@medz #17 转字符集是?我的意思一个对字符串遍历一下就可以了
medz
2017-06-14 22:21:58 +08:00
@hst001 如果你按照字节码遍历,相当于还是造了一个 mb_strlen 函数出来,php 目前造出来的轮子,远没有底层 c 拓展造出来的轮子性能高,所以,你用 php 遍历字节码,性能也会比正则+mb_strlen 低的~

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/368003

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX