👨👩👦👦
は25バイトありますが、これで1文字です。
さて、それでは文字列『絵文字👨👩👦👦を分割』を1文字ずつに分けてみましょう。
現在のPHP標準関数では、これを行うことができません。
mb_str_splitすると['絵', '文', '字', '👨', '', '👩', '', '👦', '', '👦', 'を', '分', '割']
になってしまいます。
間の空白なに?
これは書記素クラスタというもので、正直さっぱり理解できないんだけど、なんか複数の文字をくっつけて1文字にするという特殊な文字みたい。
そしてmb_str_splitはそんな変な文字には対応していないので、律儀に文字ごとに分割してしまうというわけです。
ちなみにstr_splitはマルチバイトすら対応していないので、👨👩👦👦
1文字が25文字に分割されます。
ということで正しく1文字ごとに分けられる関数grapheme_str_split
を追加しようというRFCが提出されました。
以下は該当のRFC、Grapheme cluster for str_split function: grapheme_str_splitの日本語訳です。
PHP RFC: Grapheme cluster for str_split function: grapheme_str_split
Introduction
PHPには、書記素クラスタ単位のstr_split関数が無いことに気が付きました。
従って、ICUに従った文字列分割関数、grapheme_str_split
が必要でしょう。
Intl拡張機能がこの関数をサポートすることにより、絵文字や異体字セレクタを正しく処理できるようになります。
grapheme_str_split
関数は書記素クラスタを正しく処理します。
$ sapi/cli/php -r 'var_dump(grapheme_str_split("🙇♂️"));'
array(1) {
[0]=>
string(13) "🙇♂️"
}
mb_str_split
はUnicodeコードポイント単位でのstr_split
であり、こちらのほうが有用な場合ももちろんあります。
$ sapi/cli/php -r 'var_dump(mb_str_split("🙇♂️"));'
array(4) {
[0]=>
string(4) "🙇"
[1]=>
string(3) "" // U+200D Zero Width Joinner
[2]=>
string(3) "♂"
[3]=>
string(3) "️" // U+FE0F VARIATION SELECTOR
}
これまで、書記素クラスタを正しく処理するにはPCRE関数に頼る必要がありました。
$ sapi/cli/php -r 'preg_match_all("/(\X)/u", "🙇♂️", $matches, PREG_OFFSET_CAPTURE); var_dump($matches[1]);'
array(1) {
[0]=>
array(2) {
[0]=>
string(13) "🙇♂️"
[1]=>
int(0)
}
}
他言語の例として、RubyはString#grapheme_clustersをサポートしています。
s = "\u0061\u0308-pqr-\u0062\u0308-xyz-\u0063\u0308" # => "ä-pqr-b̈-xyz-c̈"
s.grapheme_clusters
# => ["ä", "-", "p", "q", "r", "-", "b̈", "-", "x", "y", "z", "-", "c̈"]
Proposal
関数grapheme_str_split
を追加します。
function grapheme_str_split(string $string, int $length = 1): array|false {}
$string
はUTF-8のみをサポートします。
$length
は分割する文字列長です。
Backward Incompatible Changes
ユーザランドに同名の関数があると動かなくなります。
Proposed PHP Version(s)
PHP8.4
RFC Impact
To SAPIs
全てのPHP環境に追加されます。
To Existing Extensions
Intl拡張にgrapheme_str_split
が追加されます。
Future Scope
今のところなし。
Proposed Voting Choices
投票期間は2024/03/26~2024/04/10、投票の2/3の賛成で受理されます。
この期間メーリングリストだけ記載されていてRFCのほうには書かれてないんですよね。
本RFCは賛成19反対0の全員賛成で可決されました。
Implementation
https://github.com/php/php-src/pull/13580
感想
ということで今後は関数grapheme_str_split
を用いることで、['絵', '文', '字', '👨👩👦👦', 'を', '分', '割']
と正しく分けられるようになります。
文字コードの話はいくら読んでもあまりに魔境すぎて全くついていけそうな気がしません。
プルリクを見ると意外とあっさりしたかんじの実装に見えるのですが、これはPHPに元々組み込まれているGraphemeを使っているからのようです。
で、これを一から実装するとこんなかんじらしい。
正気か?