Slide 1

Slide 1 text

mb_trimを作りました

Slide 2

Slide 2 text

自己紹介 てきめん ● https://tekitoh-memdhoi.info ● X: @youkidearitai ● https://github.com/youkideari tai ● PHP 8.4で複数の関数を作った – mb_trim – mb_ucfirst、mb_lcfirst – grapheme_str_split オレ

Slide 3

Slide 3 text

遡ること2022年 ● とあるFeature request(機 能追加のリクエスト)が来る ● それがマルチバイト対応の trim関数を作ってくれない かというもの ● しかし、実装してくれる人が 現れず…

Slide 4

Slide 4 text

そのときのてきめんの状況 ● 2022年8月はまだ8.1でmbstringが変わると認識 してなかった ● 2022年12月からmbstringのレビュー作業に加 わった ● mb_trimのfeature requestは認識してた

Slide 5

Slide 5 text

時は流れ ● 2023年9月、ぼくがこの Feature requestを拾う – このころのぼく は、mbstringのレビュー も一通り落ち着いて余力 があったことが大きな要 因

Slide 6

Slide 6 text

実装するしかない! ● もう自分でRFC書いた ほうがいいよと言われる ● 皆もmake sense(理に かなってる)と言ってる 以上、もうしない理由が なかった

Slide 7

Slide 7 text

RFCの草案をもらう ● 8ctopusさんが草案を書い てくれてた ● こちらを参考に、ちょっと修 正 – mb_ltrimとmb_rtrimを追 加 – $encodingを追加

Slide 8

Slide 8 text

PHP Internalsへ投稿 ● 8ctopusさんが internalsメーリングリ ストへメールを送れな かったことから、代理で 「ぼくもほしいのでどう 思う?」と送ってみた

Slide 9

Slide 9 text

PoCの作成 ● 概念実証コードを作成 し、プルリクエストにしま した – この時点では、Draft pull requestとしました

Slide 10

Slide 10 text

RFCを記述するWiki Karma Request ● RFCを記述するのが初めてだったので、まずアカウントを作り ます ● アカウントを作ったら、Internalsメーリングリストへ「RFC Karma Request」を投げます – タイトルはこのままで「〇〇したいです」を添えれば大体許可さ れます – 許可されたらWikiの編集権をもらえます

Slide 11

Slide 11 text

RFCの作成 ● RFCを作成していきます ● Wikiなので、空いてるURLに記述できます – 今回は https://wiki.php.net/rfc/mb_trim としました ● 先程の中身を記述していきます

Slide 12

Slide 12 text

RFCの考慮 ● RFCは他人にわかるようにいろいろなことを考えま す ● 一番考えたのは$encodingの部分でした – trim関数には「..」という、範囲を指定する表記法がある のですが、これを実現するのは不可能に思いました ● というのも、文字コードごとに範囲が違いますし、Unicodeが 広すぎるのも問題です

Slide 13

Slide 13 text

RFCをInternalsメーリングリストへ送信 ● メーリングリストへ送信して、議論します ● この状態になったら、RFCを「Under Discussion」 にします – Under Discussionを少なくとも2週間続けます ● ただ、議論のすべてに答えることが必要でその合意形成に1 年とか経ったりするものもたくさんあったりします

Slide 14

Slide 14 text

Votingに入る ● 投票フェーズに入ります ● 期間は2週間+αを置きます ● ここで2/3のYesを貰えれば可決(Accepted)となり ます ● mb_trimはここで可決をもらいました

Slide 15

Slide 15 text

そして実装へ ● 先程のプルリクエストを普通のプルリクエストにし ます ● レビューを受けます – ここが結構おもしろかった

Slide 16

Slide 16 text

めっちゃアツいレビュー ● 主にnielsdosさ ん、alexdowadさんか らレビューを受ける

Slide 17

Slide 17 text

128コードポイント以上の対応 ● $stringが128コードポ イント以上の処理に対 応してないよとレビュー を受ける – そういえばそうだった

Slide 18

Slide 18 text

解決策が思い浮かばない、寝る ● 色々と試行錯誤してた けどむずくて無理!JST だともう寝る時間だ寝 るってなっててnielsdos さんに教えを請うすが た – 似たような実装で解決

Slide 19

Slide 19 text

コードポイントって何 ● Unicodeにおけるコードポイントとは、U+xxxxの xxxxの部分で、0x0から0x10FFFFの16進数が入る – https://www.unicode.org/glossary/#code_point – 何らかの文字がここに割り当てられるのでこれを1文字 として数える事が多い

Slide 20

Slide 20 text

線形探索以外になんかあるの!? ● $charactersを1コードポイ ントずつ線形(O(N))に探索 してたらここはHashTable を使えばいいよと教えを請 う – 最初、どういうこと?ってなっ てnielsdosさんに聞いてる ぼく

Slide 21

Slide 21 text

HashTableでO(1)にする ● HashTableのキー部分に コードポイントをあてがうと いうテクニック(値はなん でもいい)でO(1)にすると いうテクニックを教えても らった – すげえ!ってなった

Slide 22

Slide 22 text

ハッシュテーブル(HashTable)ってなに ● キーにハッシュ関数を与えてあげて、一意 のアドレスに値を格納・取り出すデータ構 造 – ハッシュ関数というくらいですから、md5と かsha1とかでもいいのです ● 衝突を気にしなければO(1)で済ませられ る – 衝突が絡むとO(N)になったりするけど今 回は衝突を考える必要はなかった ● php-src では HashTable という構造体 が用意されている

Slide 23

Slide 23 text

さっきから言ってるO(N)とかってなに ● 計算量、オーダーのこと ● O(N)はN個あったら最悪N回計算し ないといけないことを示す – 0個では0回 – 線形探索がこれ ● O(1)は何個あっても1回で済む – 0個でも1回だけど – ハッシュテーブルはこっち

Slide 24

Slide 24 text

XORによるビット演算のテクニック ● 左側のtrimが終わった らフラグをfalseにして たけど、mode自体を XORすればフラグ消せ るよと教えられる – すげー!ってなりました

Slide 25

Slide 25 text

ついにapprovedをもらう ● nielsdosさんによるapproved をもらう – すごくお世話になりましたマジで ありがとうございます ● alexdowadさんに「squashし てもらっていい?」ということで git rebase -i とgit push – forceする – php-srcではsquashでひとまと めにしてます

Slide 26

Slide 26 text

Landed on master ● NEWSの追 加、UPGRADINGの追 加などをしてマージと いうかsquash commitされました

Slide 27

Slide 27 text

Optimizations for mb_trim #12803 ● nielsdosさんがmb_trim 関数のパフォーマンス調 整をしてくれて、 4文字 (コードポイント)までは線 形探索したほうが速いとわ かった – O(1)が0個でも1回計算し なきゃならないところもあり うる

Slide 28

Slide 28 text

Build failed at mbstring_arginfo.h on Windows(Visual C++) #13789 ● Windowsの日本語のVisual C++でビルドができないという 問題を(あの)廣川さんから メールでもらう – $charactersをUTF-8バイト列 でコンパイルしていたのが問 題 – コンパイル時のフラグにUTF- 8であることを加えることで対 処

Slide 29

Slide 29 text

mb_trim() inaccurate $characters default value #13815 ● mb_trim関数群で文字コードが UTF-8じゃないときにtrimしない というIssue – inaccurateが「正しくない」なの で、最初なんのことかわからな かった… – 結局、$charactersを決め打ちし てるのがよくないので、NULLが 正しいのでは?となる

Slide 30

Slide 30 text

Fix GH-13815: mb_trim() inaccurate $characters default value #13820 ● nielsdosさんによるプルリク エスト – $charactersをNULLにしま しょうというプルリクエスト – ただし、RFCの内容を書き換 えるためPHP Internalsで議 論する必要になり議論を行っ た

Slide 31

Slide 31 text

mb_trimのデフォルト値変えてもいい? ● PHP Internalsメーリングリストで、mb_trimのデフォルト 値を変えてもいいかと質問してみる ● 変えるしかないならそれでいいのではという意見 ● RFCは必要ないとの意見をもらう ● ぼくは色々ごちゃごちゃ動いてた – mb_trimのRFC書き換えて怒られた(?)り、戻したり

Slide 32

Slide 32 text

結果として書き換わりました ● $charactersはデフォルト値NULLとなり、NULLのとき にRFCで決まっていたコードポイントを削除するように なりました ● mb_trim($str, encoding: “Shift_JIS”); などのときに 正しく削除できるようになりました ● こういう設計ミスがあったときに参考にしてもらえれば

Slide 33

Slide 33 text

使い方 ● mb_[lr]?trim関数はいわゆる全角スペース(U+3000)もtrimできます – PCREで言うところの\sなどをサポートしています ● ユースケースの一つとして、mb_ltrim関数を使ってUTF-8 BOMを削除できます – mb_ltrim($str, “\u{FEFF}”); ● 例えばmb_substrでZero Width Joiner(ZWJ)を含む絵文字などでZWJが残ったと き、mb_rtrim関数を使ってZWJを削除できます – mb_substr(mb_rtrim(“ ”, “\u{200D}”), 0, 2); 🙇‍♂️ ● 複数の文字(コードポイント)にも対応してます – mb_trim($str, “ \n\r\u{200D}\u{FEFF}”); ● もちろん、他の文字エンコーディングにも対応しています – mb_trim($str, encoding: “shift_JIS”); ● マニュアルはPHP 8.4に合わせて書き出すと思います

Slide 34

Slide 34 text

まとめ ● mb_trim、mb_ltrim、mb_rtrim関数を作りました – どんなタイミングで作ったのかを説明しました ● PHP Internalsとのコミュニケーションについて説明しまし た ● レビューを経てめちゃくちゃすごく勉強になりました ● PHP 8.4で入るこれらの関数をお楽しみに

Slide 35

Slide 35 text

Appendix: mb_trimのアルゴリズム ● 1コードポイントごとに左から右へ処理をしていく ● 128コードポイントごとにto_wcharメソッドが呼び出される ● 左側にtrimする文字があればleftを1足す ● 左側のtrimする文字がなければ右側のtrimに移るため、フラグを rightへの処理にするため、XORでMB_RTRIMに変更 ● 右側にtrimする文字があればrightを1足す ● 右側にtrimする文字がなければrightを0にリセット ● 下記のような擬似コードでmb_substrする: – mb_substr($str, left, mb_strlen($str) – (right + left));