先说下需求吧,这是一个挺操蛋的需求,大多数 PHPer 接到真需求就不想满足它,嗯,国内的 PM 经常会提的,就是用户的用户名或者发表文章的内容或者标题,两个单字节字符才能算一个长度单位。
例如:用户名要求最长 12 位,如果输入英文,可以输入 24 位。开心吧?在 PHP 中 strlen
是按照「字节」计算的,而 mb_strlen
是完整完整字符族算的,都无法满足。
因为 ThinkSNS+ 是使用 Laravel 开发的,所以在注册、登录等操作的时候验证用户名字段都应该使用「表单验证」这就涉及到规则的拓展,很明显,又长度限制和字符范围限制,所以应该拓展两个规则,分别是:
但是长度不只是在用户名场景使用,所以最后修改为「显示长度规则」
在 AppServiceProvider
的 boot
方法中增加规则。
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 呗!!!
ThinkSNS+ 是基于 Latavel 而开发,里面涉及了很多 Laravel 知识点,个人觉得也可以作为 PHPer 学习使用 Laravel 开发应用的一个范例。
1
luoyou1014 2017-06-13 11:03:53 +08:00
咦,我记得 ThinkSNS 不是用 ThinkPHP 开发的吗?
ThinkSNS+ 是升级后用 Laravel 重构了还是另外一个开源项目? |
2
sun019 2017-06-13 11:16:46 +08:00
以前一直在用 ThinkSNS 哈哈,后面转 Laravel 了,感谢
|
3
leafans 2017-06-13 13:15:01 +08:00
mb_strwidth(),或 mb_strimwidth(),1 个汉字=2 个英文
|
4
medz OP @luoyou1014 其实不算重构,就是因为 ThinkPHP 已经不适用现在的方向了,所以我们全新开发的,但是后续会增加 ThinkSNS 升级到 ThinkSNS+
|
5
medz OP @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 官方文档给出的范围表。 |
6
medz OP |
8
jiangzhuo 2017-06-13 14:37:23 +08:00
还好吧,我见过按照宋体渲染出来的宽度作为长度的需求。
|
9
willywu001 2017-06-13 16:26:48 +08:00
github 上 安装连接打不开了。怎么回事
|
10
KomeijiSatori 2017-06-13 17:49:30 +08:00
echo iconv_strlen("喵喵喵","UTF-8"); //3
|
11
hst001 2017-06-13 18:44:19 +08:00
占宽很常见的需求吧,知道编码很容易计算
|
12
changwei 2017-06-13 20:22:09 +08:00 via Android
反正我就是一直用的 iconv 转 gbk 来实现这种计算,话说这得多大的用户量和并发才能体现出这一点性能差距啊。。。
|
13
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; |