【PHP8.4】全角スペースをtrimできるようになるぞー

なんとなく[RFC](https://wiki.php.net/rfc)を見ていたら[Multibyte for trim function mb_trim, mb_ltrim and mb_rtrim](https://wiki.php.net/rfc/mb_trim)という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の削除が考えられます。

```php
mb_ltrim($string, "\u{FEFF}\u{FFFE}");
```

## Proposal

`mb_trim`関数を追加します。

```php
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

[実装チケット](https://github.com/php/php-src/issues/9216)


## Voting

投票期間は2023/11/02～2023/11/17、投票の2/3の賛成で可決されます。
この投票期間、RFCに書かれてない。

2023/11/13時点では賛成10反対0の賛成多数であり、おそらく受理されます。


## Implementation

[プルリクエスト](https://github.com/php/php-src/pull/12459)


# 感想

[trim](https://www.php.net/manual/ja/function.trim.php)の第二引数に全角スペース渡せばいいじゃない。

```php
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`にマルチバイト文字を渡せないという条件の罠、[関数のドキュメント](https://www.php.net/manual/ja/function.trim.php)には全く記載がないんですよね。
注意喚起の記事などもほとんど見当たりません。
知ってる人にとっては当然だと思うかもしれませんが、そうでもない人もたくさん使っている言語がPHPです。
これ引っかかってる人それなりにいるのではなかろうか。


ところで[RFC](https://wiki.php.net/rfc/mb_trim)では`mb_ltrim`と`mb_rtrim`には第三引数`$encoding`を渡すことができるのに、`mb_trim`に渡すことができないようになっています。
なんでだろうと思ったら[プルリク](https://github.com/php/php-src/pull/12459)には入ってました。
RFCの更新忘れのようです。
確認していませんが、もしかしたら他にも抜け漏れがあるかもしれませんね。
気付いたらプルリクでも送ってあげればきっと喜ばれることでしょう。
私は権限ないのでできませんが。
この記事では修正しています。

RFCでは`mb_trim`が実装されるタイミングはPHP8.xとしか書かれていませんが、PHP8.3は[既にフィーチャーフリーズしている](https://qiita.com/rana_kualu/items/91111443105a75a55e2f)ので、おそらくPHP8.4での導入となるでしょう。
楽しみですね。

実装者は[@youkidearitai](https://twitter.com/youkidearitai)です。
ここ最近PHP本体のソースコード、特に[mbstring](https://tekitoh-memdhoi.info/history)に多くの貢献をしているすごい人です。
今後の活躍にも期待が持てますね。