如何把Emoji表情保存到数据库

了解 Emoji

绘文字(英语:Emoji,日语:絵文字/えもじ emoji)是使用在网页和聊天中的形意符号,最初是日本在无线通信中所使用的视觉情感符号(图画文字),绘意指图形,文字则是图形的隐喻,可用来代表多种表情,如笑脸表示笑、蛋糕表示食物等。 —— 来自 维基百科《绘文字》词条

存储宽字符可能有几种方法:

  1. 改变数据表的编码方式存储宽字符;
  2. 存储其 Unicode 编码,取出数据时将其转换成宽字符;
  3. 为宽字符设置伪码,例如:使用 :emoji: 的形式存储,取出后替换为图片。

Unicode 区块

再解决问题之前,我们先来学习以下关于 Emoji 编码滴问题,为了搞明白我将会让 “ 😃😄 ” 这俩字符暴露他的 Unicode 和 UTF-8 编码【不懂这俩编码滴请看 字符编码笔记:ASCII,Unicode和UTF-8】:

  • 暴露那俩字符的 Unicode 编码:

因为其实如果网页上用的 UTF-8 编码,就算用 Unicode 编码转换工具吼,都只能暴露出 UTF-8 编码而已,所以我们通过查维基百科的那个表格啊,就知道了上面俩字符的 Unicode 分别是 U+1f603U+1f604 啦。蓝后,根据阮大大的文,我们可以造出把 UTF-8 编码转成 Unicode 编码的工具,以下程序默认输入一串正确可执行的 UTF-8 字符串:

function utf8_unicode ($str) {
    $ret = ''; $len = strlen($str);
    for ($i = 0; $i < $len; ++$i) {
        $byte = ord($str[$i]); $code = 0;
        if ((0b11111000 & $byte) === 0b11110000) {
            $code_start = 3; $code += $byte & 0b111;
        } else if ((0b11110000 & $byte) === 0b11100000) {
            $code_start = 2; $code += $byte & 0b1111;
        } else if ((0b11100000 & $byte) === 0b11000000) {
            $code_start = 1; $code += $byte & 0b11111;
        } else {
            $code_start = 0; $code = 0b1111111 & $byte;
        }
        while ($code_start-- > 0) {
            ++$i; $byte = ord($str[$i]);
            $code <<= 6; $code += ($byte & 0b111111);
        }
        $ret .= 'U+'.dechex($code);
    }

    return $ret;
}

输入 “123 Abc😃😄大家好!” 可以得到 U+31U+32U+33U+20U+41U+62U+63U+1f603U+1f604U+5927U+5bb6U+597dU+ff01,中间的 U+1f603 U+1f604 确实就是 “ 😃😄 ” 的 Unicode(查 维基 的表)。

  • 把 Unicode 转回来

接下来介绍咋转回来,默认输入一段 U+xxxU+xxxx 的内容,输出真正的字符串:

function unicode_utf8 ($str) {
    $ret = ''; $arr = array_filter(explode('U+', $str));
    foreach ($arr as $unicode) {
        $byte = intval(hexdec($unicode));
        if ($byte >= 0x10000) { // && $byte <= 0x10ffff
            $char = chr(0b10000000 + ($byte & 0b111111));$byte >>= 6;
            $char = chr(0b10000000 + ($byte & 0b111111)).$char;$byte >>= 6;
            $char = chr(0b10000000 + ($byte & 0b111111)).$char;$byte >>= 6;
            $char = chr(0b11110000 + ($byte & 0b111)).$char;
        } else if ($byte >= 0x800 && $byte <= 0xFFFF) {
            $char = chr(0b10000000 + ($byte & 0b111111));$byte >>= 6;
            $char = chr(0b10000000 + ($byte & 0b111111)).$char;$byte >>= 6;
            $char = chr(0b11100000 + ($byte & 0b1111)).$char;
        } else if ($byte >= 0x80 && $byte <= 0x7FF) {
            $char = chr(0b10000000 + ($byte & 0b111111));$byte >>= 6;
            $char = chr(0b11000000 + ($byte & 0b11111)).$char;
        } else {
            $char = chr($byte & 0b1111111);
        }
        $ret .= $char;
    }

    return $ret;
}

把这串 U+31U+32U+33U+20U+41U+62U+63U+1f603U+1f604U+5927U+5bb6U+597dU+ff01 输入进去,就会得到 “123 Abc😃😄大家好!” 啦,成功啦!

那么咋存

上面总结 3 种方式:

  • 改数据库编码方式

欸其实我跟你们讲,百度到的结果就是改成 utf8 mb4 这个编码,按照 清官谈mysql中utf8和utf8mb4区别 一文中的说法,mb4 意思就是 most bytes 4,所以能够存储宽字符了:

ALTER TABLE `test_table` 
DEFAULT CHARACTER SET=utf8mb4;// COLLATE=utf8mb4_general_ci 顺便可以改排序;

注意:utf8mb4 在 MySQL 5.5.3 以上版本才有哦。

  • 把字符串宽字符搞成 utf8 编码存

百度到的版本其实是把字符串转成 json 格式暴露宽字符的编码,然后正则匹配 (\\\u[ed][0-9a-f]{3}) 也就是在区间 0xD000 - 0xEFFF 的字符加反斜杠(\),代码如下:

function emoji_encode($str)
{
    $text = json_encode($str);
    $text = preg_replace_callback("/(\\\u[ed][0-9a-f]{3})/i", function ($s) {
        return addslashes($s[0]);
    }, $text);

    return json_decode($text);
}

然后把得到的串转回 json 存起来,我觉的略不妥当,同理在取出来的时候把反斜杠去掉再转字符串:

function emoji_decode($str)
{
    $text = json_encode($str);
    $text = preg_replace_callback('/\\\\\\\\/i', function ($str) {
        return '\\';
    }, $text);

    return json_decode($text);
}

其实只需要考虑存不进去的 4 字节字符就行了。

  • 替换掉 emoji 变成占位符

这个方法需要维护一个存着所有 emoji 表情编码的数组然后替换成 :smile: 这样的形式,然后再存到数据库中,然后取出来的时候再给替换回去,这个事儿可以交给前端也可以交给后端来着,但是前端的话就会产生一个很大的 js 文件,当然后端的话也得存起来搞事,所以似乎都不太方便吧,而且要保证涵盖所有表情啊,而且遇到别的 4 字节字符的时候就也会出错吧,这里给出一个库 muan/emoji,供大家使用。

总结

说了这么多,我觉得最好的办法就是把数据库改成 UTF8 mb4 的存储方式,但这意味着存储空间消耗更大,可能也会遇到其他的坑,如果大家有更好的办法,可以一起商讨。

发表评论

电子邮件地址不会被公开。 必填项已用*标注