俗にいう半角英数字は 1 byte、ひらがなカタカナ漢字の様な日本語は 2 byte、🧑🤝🧑👨👩👧👦といった絵文字は多くは 4byte ですが、複数の絵文字を組み合わせた絵文字(👨と👩を組み合わせて👨👩など。👨👩はbyte上で👨と👩と結合用のbyteを持ちます)や 2 byte も多くあります。詳しく調べれば規則がわかるやもしれませんが傍からみるとほぼ不定です。そして困ったことにコンピュータ上で文字は byte で管理されているのに対し、人間は文字を目で見た数で認識します。要するコンピュータ上で認識される文字列長と人間が視覚的に認識する文字列長の違いがあるということです。
これを JavaScirpt 上で動かすと次のような挙動が起きます。
👩👩👧👧 は見た目上は一文字ですが長さ 11 であり、謎の 11 文字に分割でき、👩👩👧👧 の1から3文字目は👩となります。JavaScript 上でこれを解決するには
sindresorhus/string-length: Get the real length of a string – by correctly counting astral symbols and ignoring ansi escape codes
sindresorhus/string-width: Get the visual width of a string – the number of columns required to display it
といったライブラリが有効です。これらのライブラリは文字列の長さを byte 数でなく見た目の幅や文字数で数えることができます。
PHP 上では次の様になり、
$emoji = '👩👩👧👧'; echo $emoji. "\n"; echo strlen($emoji) . "\n";// 25 echo mb_strlen($emoji) . "\n";// 7 var_dump(str_split($emoji)); // 謎の 25 文字配列 var_dump(mb_str_split($emoji)); // 絵文字 4 つと長さ 3 とされる見た目から文字列 3 つの 7 文字配列 preg_match('/.{1,3}/', $emoji, $match); var_dump($match);// 謎の 3 文字
Online PHP editor | output for Qme05
MySQL 8.0 の utf8mb4_bin では次の様になります。
SELECT CHAR_LENGTH('👩👩👧👧'); # 7 CREATE TABLE tmp ( tmp_col varchar(5) ); INSERT INTO tmp VALUES ('👩👩👧👧'); # [22001][1406] Data truncation: Data too long for column 'tmp_col' at row 1
どの言語でも素のままで見た目通りに扱うことはできません。特に深刻なのが MySQL で、バリデーションで”n 文字以下にしてください。”と返してもユーザは”いや n 文字以下で送っているじゃないか”と疑問を持ってしまいます。このあたり正直良い対策を知らないしうまく思いつかないのですが、とりあえず素直に絵文字があると文字数をうまく数えられない旨もメッセージにつける、”n byte 以下で~”とメッセージを変える(web 技術者等のプログラマー同士のサービスなら良さそう)、バリデーションより十分に大きなサイズのデータ領域をとる、といった方法があります。
追記;他サービスを見ているとよく画像と画像を表現する文字列に置き換えているのでその方策もよさそうです。