Edited at

【PHP】マルチバイト(全角スペース等)対応のtrim処理

ワリと需要のある処理だと思いますが、改めてググってみるとあまりマネして欲しくないコードが散見されたため、この記事を書いてみました。


  • 検索結果上位のページのコードをコピペで使ってる人


  • 正規表現の \A と \z++\p{C} という書き方を知らない人


などに参考にして頂ければ幸いです。



コード

function mbTrim($pString)

{
return preg_replace('/\A[\p{C}\p{Z}]++|[\p{C}\p{Z}]++\z/u', '', $pString);
}


解説

要するに、文字列の前後から、空白文字や改行の他 通常は不要と思われる制御文字なんかもまとめて全部削除しちゃえ!という意味になります。

スペース 1 つとっても、半角・全角以外にも色々とあります(Wikipedia)。それらを 1 つ 1 つ調べ上げるのは面倒なので、Unicode 文字プロパティを使って楽をしているという事です。



別のコード1

もう少しマイルドに、PHP 本来の trim() に全角スペースの処理を加えた程度のもので良いのであれば、以下のようなコードがお手軽だと思います。

$str = preg_replace('/\A[\x00\s]++|[\x00\s]++\z/u', '', $str);

もしくは

mb_regex_encoding('UTF-8');

$str = mb_ereg_replace('\A[\x00\s]++|[\x00\s]++\z', '', $str);



  • '\x00'


    • NULLバイト("\0"




  • \s



    • 空白文字 を表すエスケープシーケンスです。

    • 上記の書き方であれば、\s には全角スペースも含まれていますが、NULLバイトは含まれていない点に注意です。





別のコード2

以下のように、取り除きたい文字を丁寧に指定するのも、意図を汲み取りやすい書き方だと思います。

$str = preg_replace("/\A[\\x0-\x20\x7F\xC2\xA0\xE3\x80\x80]++|[\\x0-\x20\x7F\xC2\xA0\xE3\x80\x80]++\z/u", '', $str);



  • "\\x0-\x20\x7F"




  • "\xC2\xA0"


    • NO-BREAK SPACE(U+00A0)




  • "\xE3\x80\x80"


    • 全角スペース(U+3000)





あまりマネして欲しくないコード

ググった際に見つけた、あまりマネして欲しくないコードを解説してみます。


その1

$str = trim(mb_convert_kana($str, 's'));


解説

悪い意味で PHP らしいコードというか…パッと見で「何か気持ち悪い」と感じてしまうのは私だけでしょうか?

$str = '  あ い う え お  ';

$str = trim(mb_convert_kana($str, 's'));

を実行してみると分かると思いますが、このコードでは文字列の途中にある全角スペースまで半角スペースに置換されてしまいます。

手っ取り早い方法ではありますが、このような考え方はバグの原因になりますので、避けた方が良いと思います。


その2

$str = preg_replace('/^[  ]+/u', '', $str);

$str = preg_replace('/[  ]+$/u', '', $str);

…は流石にアレなので、選択肢を意味するメタ文字を使って…

$str = preg_replace('/^[  ]+|[  ]+$/u', '', $str);

…と、とりあえず1行にまとめてみます。


解説

trim() ではデフォルトでスペース以外の文字 "\t\n\r\0\x0B" の除去も行っていますが、このコードではそれらが考慮されていません。

また、文字クラス [ ] の中に全角スペースを直接書くというのもあまり感心はしないです。

SPACE(U+0020 "\x20")・NO-BREAK SPACE(U+00A0 "\xC2\xA0")・全角スペース(U+3000 "\xE3\x80\x80") 等の空白文字は、環境によっては見た目の区別がほとんど付かなくなりますので、この手の文字列処理は、別の方法で書く癖を付けておいた方が良いのではないかと思います。


  • 例:'[  ]' ではなく "[\x20\xE3\x80\x80]" と書く。



絶対最大量指定子 ++ を使っている理由

ReDoS という言葉はご存知でしょうか?

Regular Expressions Denial of Service attack の略で、簡単に言うと、正規表現の処理を悪用したアタックの事です。

有名なのが、Stack Overflow で発生したサービスダウンです。

上記ページ内にある通り、文字列の前後から空白文字を取り除くために ^[\s\u200c]+|[\s\u200c]+$ という正規表現を使っていたのが原因です。++ ではなく + を使っています。

この正規表現に対し、2 万文字もの空白を含む文字列が送られた結果、約 2 億回(20,000 + 19,999 + 19,998 + ...)近いバックトラックが発生した事が、サービスのダウンへと繋がりました。2

なお、この件に関しては、正規表現に通す前に文字列の長さをチェックしていないのも問題だと思います。

正規表現は大変便利なものですが、正規表現を使うのは最終手段と考え、まずは「正規表現に通す前にできる事はないか?」「正規表現を使わずに標準関数で処理できないか?」と検討してみる事が大事だと思います。





  1. ruby を使ってる人にはお馴染みでしょうが、PHP ではまだまだ浸透してないような気がします。 



  2. PHP では、preg_match() 系で使われている PCRE ライブラリと mb_ereg() 系で使われている Oniguruma ライブラリとが採用されていますが、最近のバージョンであれば、自動でバックトラック回数制限がかかるようになっているとは思います。(未検証)