PHPerが知っておくべき正規表現の仕様
正規表現のはじめと終わりの文字(デリミタの/などは除く)として、よく^
$
の組み合わせや、\A
\z
のような組み合わせを見ると思います。
これらの組み合わせをなんとなく使ってないでしょうか?
正規表現には、単一行モードと複数行モードというものがあります。
PHPの正規表現では、それぞれm
とs
で切り替えが可能です。
^
$
や、\A
\z
は、この2つのモードと関わってきます。
^
$
の組み合わせは、複数行モードで力を発揮します。
下記にある、$match_string
は、\n
によって改行され、複数行になっています。
ですが、PHPはデフォルトが単一行モードなので、下記のような文字列では^
$
の組み合わせではマッチしません。
$pattern = '/^[0-9]+$/';
$match_string = "1111\nhoge\n2222";
var_dump(preg_match($pattern, $match_string)); // => false
複数行モードを指定するとマッチします。
$pattern = '/^[0-9]+$/m';
$match_string = "1111\nhoge\n2222";
var_dump(preg_match($pattern, $match_string)); // => true
これは、^ と $ が 複数行モードでは、行の先頭と行の終わりと指すためです。
"1111"
と"hoge"
と"2222"
はそれぞれ1行として個別に検査され、どれかにマッチすれば結果は true となります。
つまり、以下のような文字列もマッチするというわけです。
$match_string = "hoge\n2222";
$match_string = "1111\nhoge";
では、\Aと\zの組み合わせの場合はどうかというと、
下記のように複数行モードを指定したとしても、上記の文字列パターンではどれもマッチしません。(複数行モードは意味がありません)
$pattern = '/\A[0-9]+\z/m';
$match_string = "1111\nhoge\n2222"; // => false
$match_string = "hoge\n2222"; // => false
$match_string = "1111\nhoge"; // => false
これは、\Aと\zの組み合わせが、検索対象の文字列の先頭と文字列の末尾を指すためです。
つまり、下記のように文字列の先頭から末尾まで、一環してパターンにマッチしていないと、マッチしないというわけです。
$pattern = "12345678";
var_dump(preg_match($pattern, $match_string)); // => true
PHPはデフォルトが単一行モードなら、どちらを使っても良いではないか、となりそうですが、そうではありません。
実は、$
は単一行モードでは、検索対象の終わりあるいは「終端の改行文字の前」という仕様になっています。
つまり、単一行モードであろうと、下記のような検索対象の場合、完全一致ではないとしてマッチしないことを期待しても、マッチしてしまいます。
// "1111"はマッチしてほしい
// "1111\n"はマッチしてほしくない
$match_string = "1111\n";
var_dump(preg_match($pattern, $match_string)); // => true
\A
\z
では、上記のようなパターンはマッチしないので、期待した結果になります。
// "1111"はマッチしてほしい
// "1111\n"はマッチしてほしくない
$pattern = '/\A[0-9]+\z/';
var_dump(preg_match($pattern, $match_string)); // => false
このように、^
と$
の組み合わせ(特に$
が危険を含んでいる)では、予想外の結果を得る場合があります。
以上の理由から、意図的ではない場合に ^
と$
の組み合わせで完全一致の正規表現を行なうのは好ましくないと言えるので、\A
と\z
の組み合わせを使いましょう。
Discussion