正規表現に関する問題です。
次の出力結果を答えてください。
preg_match('/^(?:(a)|(b)|(c))$/', 'b', $matches);
var_dump($matches);
※ パターン (?: ) を使うと、$matchesに代入されないグループを作成することが出来ます。
array(3) {
[0]=>
string(0) ""
[1]=>
string(1) "b"
[2]=>
string(0) ""
}
array(3) {
[0]=>
string(1) "b"
[1]=>
string(0) ""
[2]=>
string(1) "b"
[3]=>
string(0) ""
}
array(3) {
[0]=>
string(0) "b"
[1]=>
string(1) ""
[2]=>
string(0) "b"
}
(回答は↓)
まあ 1 は「違う」って多くの人が納得されると思います。
そうです、 $matches[0]
は パターン全体 を表すからです。
今回の場合、
a → $matches[1]
b → $matches[2]
c → $matches[3]
となります。
残ってくる選択肢が 2 と 3 。
さて正解は?
実は 3 なんです。
「b」がマッチした時点で (?:(a)|(b)|(c)) 内の評価が終わり、3番目の (c) のキャプチャは作成されない、という理由です。(意外と目から鱗だった方もいらっしゃるのではないでしょうか?)
この仕様を知っておけば、こういう処理も楽々1回の正規表現で書けちゃいますね。
$agent = isset($_SERVER['HTTP_USER_AGENT']) ? ($_SERVER['HTTP_USER_AGENT']) : '';
$pattern = '@'.
'^DoCoMo/[12]\\.0|'.
'(^(?:J-PHONE|Vodafone|MOT-[CV]980|SoftBank)/)|'.
'(^KDDI-|UP\\.Browser/)|'.
'(^PDXGW|DDIPOCKET;|WILLCOM;)'.
'@';
switch (true) {
case !preg_match($pattern, $agent, $matches):
echo 'その他';
break;
case !isset($matches[1]):
echo 'DoCoMo';
break;
case !isset($matches[2]):
echo 'Softbank';
break;
case !isset($matches[3]):
echo 'au';
break;
default:
echo 'WILLCOM';
}
このコードの作成にあたっては、[こちら](http://www.mt312.com/php/32/ ユーザーエージェント携帯判別)の情報を参考にしました。
ちなみに私は、
- 正規表現エンジンに対しての不要なエスケープは行わない
- バックスラッシュに関してはPHP言語としての問題になってくるので全てエスケープ
こういうポリシーのもとに書いています。
http://qiita.com/mpyw/items/8dd5378cb01c877e1f7b
さて今回はこのぐらいで。
・・・と思ったけど preg_match
以外の他の関数にも触れておきます。
-
preg_match_all
で PREG_SET_ORDER のとき preg_replace_callback
この2つに関しては先ほどの preg_match
と同じ認識でOKです。
ところが、
-
preg_match_all
で PREG_PATTERN_ORDER のとき
このときの挙動は異なります。
-
$matches[0][$i]
が存在していれば、
$matches[1][$i]
$matches[2][$i]
$matches[3][$i]
・・・は必ず存在する - 実際にマッチしたものが無ければそれには空文字列が代入される
一言でいえば、
親配列($matches)が持つ子配列の要素数は必ず等しくなる
ちなみに
preg_replace
これに関してはもっと制約が緩いです。
例えば ( ) が3つしかないのに、 $4
とか書いちゃっても問題なく空文字列として処理してくれます。