PHPのpreg_*
系関数で使う正規表現は、区切り文字の後ろにいろいろなフラグを付けることができます。
その中で私が用途をすぐ理解できなかったu
フラグについて書きます。
tl;dr
-
u
フラグなしだと、マルチバイト文字のマッチが思った通りにできない可能性がある -
u
フラグによって\d
や\w
の挙動が変わる
注:以降の記述では、扱う文字列はすべてUTF-8であることを前提とします。
preg正規表現のu
フラグ
なにはともあれ公式マニュアルを確認します。
u (PCRE_UTF8)
この修正子は、Perl 非互換な PCRE の機能を有効にします。パターンと対象文字列は、 UTF-8 として処理されます。 無効な対象文字列を preg_* 関数に渡しても、何もマッチしません。 無効なパターンを渡すと、E_WARNING レベルのエラーが発生します。 5オクテットおよび6オクテットの UTF-8 シーケンスは無効とみなされます。
👨「つまりマルチバイト文字を含む正規表現にはu
をつけなきゃいけないんだな」
👨「u
フラグ完全に理解した」
uフラグがなくても動く??
では実際に動かしてみます。
<?php
function test(string $regexp, string $subject)
{
preg_match($regexp, $subject, $match);
var_dump($match[0]);
}
test('/^あ/', 'あいう'); // uなし、マッチしない?
test('/^あ/u', 'あいう'); // uあり、マッチするはず
実行結果
string(3) "あ"
string(3) "あ"
👨「???」
👨「u
フラグなんもわからん」
正しく動かないパターン
u
フラグはあってもなくても一緒?と思うのはまだ早いです。
test('/^[あい]/', 'あいう');
test('/^[あい]/u', 'あいう');
実行結果
string(1) ""
string(3) "あ"
解説
PHPの文字列というのは、実際には文字列というよりバイト列です。
なので、バイナリファイルもfile_get_contents
でそのまま文字列(バイト列)として読み込むことができます。
"あ"
という文字列は、PHPインタプリタでは"\xE3\x81\x82"
というバイト列として認識します。
/^[あい]/
という正規表現は実際には/^[\xE3\x81\x82\xE3\x81\x84]/
、つまり先頭にある\xE3
,\x81
,\x82
,\x84
いずれかのバイトにマッチします。
実行結果のstring(1) ""
の正体は、文字列"あいう"
の先頭1バイトなのです。
ということで、こんなコードは意図しない動きになります。
$input = 'い';
if (preg_match('/^[あ]/', $input)) {
echo "マッチしました!";
} else {
echo "マッチ失敗……";
}
実行結果
マッチしました!
文字クラスだけでなく、繰り返しの+
,*
なども同じ理由で問題になります。
つまり/あ+/
が「あ」の繰り返しではなく、末尾のバイトの繰り返しになってしまいます。
また、.
はu
なしだと1バイトにマッチしてしまいます。
u
フラグの注意点(2023/07/23 追記)
じゃあ常にu
フラグをつければいいか、というとそうとも限りません。
\d
が全角数字にもマッチするようになってしまいます。
https://3v4l.org/vBbh0#v8.2.7
同様に、\w
も全角数字・全角アルファベットにマッチするようになります。
これを避けるためには、[0-9]
のような指定を行う必要があります。
まとめ
-
u
フラグがないと、文字単位ではなくバイト単位で処理される - ただし、場合によっては
u
があってもなくても同じ挙動をするように見えることもある -
\d
など一部の正規表現の挙動が変わってしまう マルチバイト文字を扱うなら常にu
を付ける、と思っておけば良さそう