LoginSignup
18
1

【PHP8.4】絵文字👨‍👩‍👦‍👦を正しく分割できるようになる

Posted at

👨‍👩‍👦‍👦は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を使っているからのようです。

で、これを一から実装するとこんなかんじらしい。
正気か?

18
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
1