なんとなくRFCを見ていたらMultibyte for trim function mb_trim, mb_ltrim and mb_rtrimというRFCが投票に入っていました。
というわけで、以下はこのRFCの紹介です。
PHP RFC: Multibyte for trim function mb_trim, mb_ltrim and mb_rtrim
Introduction
PHPには、マルチバイトのtrim関数がありません。
preg_replace("/^\s+|\s+$/u", '', $string)
で概ね想定した挙動になりますが、関数を予め用意しておくことでコードの可読性とわかりやすさを向上させることができるでしょう。
また、トリッキーになりがちなこの処理を標準化することができます。
この機能は多くのPHP開発者にとって有用であり、mbstringモジュールはこれで完成にまた一歩近づきます。
ユースケースのひとつとして、mb_ltrim
によるBOMの削除が考えられます。
mb_ltrim($string, "\u{FEFF}\u{FFFE}");
Proposal
mb_trim
関数を追加します。
function mb_trim(string $string, string $characters = " \f\n\r\t\v\x00\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{200A}\u{2028}\u{2029}\u{202F}\u{205F}\u{3000}\u{0085}\u{180E}", ?string $encoding = null): string {}
function mb_ltrim(string $string, string $characters = " \f\n\r\t\v\x00\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{200A}\u{2028}\u{2029}\u{202F}\u{205F}\u{3000}\u{0085}\u{180E}", ?string $encoding = null): string {}
function mb_rtrim(string $string, string $characters = " \f\n\r\t\v\x00\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{200A}\u{2028}\u{2029}\u{202F}\u{205F}\u{3000}\u{0085}\u{180E}", ?string $encoding = null): string {}
以下の文字がtrimされます。
・trim
関数と同じ。
U+0020 半角スペース
U+0009 \t
U+000A \n
U+000B \v
U+000D \r
・あまり一般的でないせいか、trim
関数では削除されなかった改頁。
U+000C \f
・trim
関数では削除されるけど正規表現\s
では削除されない。
U+0000 \0
・区切り文字カテゴリ。
U+0020 SPACE
U+00A0 NO-BREAK SPACE
U+1680 OGHAM SPACE MARK
U+2000 EN QUAD
U+2001 EM QUAD
U+2002 EN SPACE
U+2003 EM SPACE
U+2004 THREE-PER-EM SPACE
U+2005 FOUR-PER-EM SPACE
U+2006 SIX-PER-EM SPACE
U+2007 FIGURE SPACE
U+2008 PUNCTUATION SPACE
U+2009 THIN SPACE
U+200A HAIR SPACE
U+2028 LINE SEPARATOR
U+2029 PARAGRAPH SEPARATOR
U+202F NARROW NO-BREAK SPACE
U+205F MEDIUM MATHEMATICAL SPACE
U+3000 IDEOGRAPHIC SPACE
・その他、正規表現\s
に含まれる。
U+0085 NEXT LINE (NEL)
U+180E MONGOLIAN VOWEL SEPARATOR
なお、trim
関数には存在する範囲指定..
には対応しません。
理由としてはUNICODEの文字幅が非常に広く、検索が困難であり、また文字コードによってマッピングが異なるためです。
たとえばひらがなは、UTF-8では[あ-ゞ]
、EUC-JPでは[あ-ゝゞ]
、Shift_JISでは[あ-ん]
となります。
Backward Incompatible Changes
ユーザランドで同名の関数を定義していた場合は動かなくなります。
Proposed PHP Version(s)
PHP8.x
RFC Impact
RFCの影響。
To SAPIs
SAPIに上記関数が追加されます。
To Existing Extensions
mbstringエクステンションに上記関数が追加されます。
To Opcache
Opcacheの変更はありません。
New Constants
新しい定数はありません。
php.ini Defaults
ini設定はありません。
Open Issues
Voting
投票期間は2023/11/02~2023/11/17、投票の2/3の賛成で可決されます。
この投票期間、RFCに書かれてない。
2023/11/13時点では賛成10反対0の賛成多数であり、おそらく受理されます。
Implementation
感想
trimの第二引数に全角スペース渡せばいいじゃない。
var_dump(trim(' 山', ' ')); // '山'
var_dump(trim(' あ', ' ')); // ''
' 山'はきちんと想定どおりにtrimされますが、' あ'のほうはどういうわけか'あ'も消滅し、空文字になってしまいました。
なんだこれバグか?
この''、実際は空文字ではなく2バイトある謎の''です。
trimの第二引数$characters
は全角に対応していません。
第二引数に渡した' 'は、全角スペースではなく'\xe3\x80\x80'と解釈されてしまいます。
また'あ'は'\xe3\x81\x82'です。
結果としてtrim(' あ', ' ')
はtrim('\xe3\x80\x80\xe3\x81\x82', '\xe3\x80\x80')
となって結果は'\x81\x82'
となりますが、そんな文字は存在しないので空文字に見えるというわけです。
この第二引数$characters
にマルチバイト文字を渡せないという条件の罠、関数のドキュメントには全く記載がないんですよね。
注意喚起の記事などもほとんど見当たりません。
知ってる人にとっては当然だと思うかもしれませんが、そうでもない人もたくさん使っている言語がPHPです。
これ引っかかってる人それなりにいるのではなかろうか。
ところでRFCではmb_ltrim
とmb_rtrim
には第三引数$encoding
を渡すことができるのに、mb_trim
に渡すことができないようになっています。
なんでだろうと思ったらプルリクには入ってました。
RFCの更新忘れのようです。
確認していませんが、もしかしたら他にも抜け漏れがあるかもしれませんね。
気付いたらプルリクでも送ってあげればきっと喜ばれることでしょう。
私は権限ないのでできませんが。
この記事では修正しています。
RFCではmb_trim
が実装されるタイミングはPHP8.xとしか書かれていませんが、PHP8.3は既にフィーチャーフリーズしているので、おそらくPHP8.4での導入となるでしょう。
楽しみですね。
実装者は@youkidearitaiです。
ここ最近PHP本体のソースコード、特にmbstringに多くの貢献をしているすごい人です。
今後の活躍にも期待が持てますね。