ワリと需要のある処理だと思いますが、改めてググってみるとあまりマネして欲しくないコードが散見されたため、この記事を書いてみました。
-
検索結果上位のページのコードをコピペで使ってる人
-
正規表現の
\A と \z
や++
や\p{C}
という書き方を知らない人
などに参考にして頂ければ幸いです。
コード
function mbTrim($pString)
{
return preg_replace('/\A[\p{Cc}\p{Cf}\p{Z}]++|[\p{Cc}\p{Cf}\p{Z}]++\z/u', '', $pString);
}
解説
-
\A
と\z
- 文字列の始端 と 文字列の終端 を表すエスケープシーケンスです。
- 始端と終端を
^
と$
で記述しているものが多いですが、\A
と\z
を使うようにしましょう。1
-
最後の
/u
- UTF-8 として処理するよというパターン修飾子です。
-
\p{C}
と\p{Z}
- コントロール文字その他諸々 と 区切り文字や空白文字 を表すUnicode 文字プロパティです。
-
++
-
絶対最大量指定子 と呼ばれる(他にも様々な呼び方があります)もので、
+
に比べバックトラックを抑える働きがあります。
-
絶対最大量指定子 と呼ばれる(他にも様々な呼び方があります)もので、
要するに、**文字列の前後から、空白文字や改行の他 通常は不要と思われる制御文字なんかもまとめて全部削除しちゃえ!**という意味になります。
スペース 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"
)
- NULLバイト(
-
\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"
- 制御文字(
\\x0-\x1F
と\x7F
) + 半角スペース(\x20
) - ASCIIコード表
- 制御文字(
-
"\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
なお、この件に関しては、正規表現に通す前に文字列の長さをチェックしていないのも問題だと思います。
正規表現は大変便利なものですが、正規表現を使うのは最終手段と考え、まずは「正規表現に通す前にできる事はないか?」「正規表現を使わずに標準関数で処理できないか?」と検討してみる事が大事だと思います。