PR

PHPで高度な検索をする方法:正規表現を使ってみよう!

PHP

フォームから入力された情報のチェックなど、正規表現を使うことでいろいろな文字列をパターン化して処理することができます。しかし、記号の意味がわからなければ正規表現を正しく理解することはできません。そこで、正規表現で使われる記号の意味と関数の使い方を解説します。

PHPの正規表現とは

PHPの正規表現とは、文字列の中にあるパターンを表現する記法です。例えば、東京都、東京駅、東京ドーム、東京ディズニーランドなどの「東京で始まる」文字列や、郵便番号のように決まった形式で数字の部分だけが違う文字列は無数にあります。

これらの文字の並びをパターン化し、シンプルに記述するための方法を正規表現といいます。

正規表現でできること

PHPの正規表現では、さまざまな文字列パターンを表現することができます。正規表現でパターン化された文字列をPHPの正規表現関数の中で使うことによって、ある文字列の中からそのパターンに一致した文字列があるかどうかを判別したり、置き換えたりすることができるようになります。

基本的な正規表現

PHPの正規表現のパターンは、「通常の文字(リテラル)」と、「.」「[」「^」「$」などの「メタ文字」と呼ばれる特殊な役割を与えられた記号を組み合わせて記述します。

例えば、上記の「東京で始まる」であれば『^東京.*』となり、郵便番号であれば『^[0-9]{3}-[0-9]{4}$』となります。

メタ文字(特殊文字)

PHPの正規表現で使う主なメタ文字は以下のとおりです。

基本的なメタ文字

最も基本的なメタ文字で、使用頻度の高いものの一覧です。

メタ文字意味
.改行(\n、\r)以外のあらゆる一文字。「.(ドット)」を複数並べることによって、何の文字かを問わずに並べた数の文字列を表現
^行の先頭
$行の末尾
〇〇〇|▲▲▲「|」の左右の文字列や正規表現のどちらか(〇〇〇と▲▲▲のいずれか)
\●バックスラッシュ(\)直後のメタ文字●を通常の文字として認識(エスケープ)させる
[ABCDEFG][ ]内で指定した文字のどれか1文字
[A-Z]、[a-z]、[0-9]、[ぁ-ん]、[ァ-ヴ]など「-」で文字や数字の範囲を指定
[^ABCDEFG][ ]内で指定した文字以外の1文字
(正規表現)( )内の正規表現をグループ化
{n}直前のパターンをn回繰り返し

量指定子(繰り返しを表すメタ文字)

「量指定子」は直前のパターンの繰り返しを表すときに使います。通常は可能な限り最長の一致を探しますが、「?」をつけることで最短のマッチングも可能になります。

量指定子(最長一致)最短一致意味
**?直前のパターンが0回以上繰り返し出現。つまり、現れない場合も含まれます
++?直前のパターンが1回以上繰り返し出現
???直前のパターンが0回か1回出現
{n}直前のパターンがn回繰り返し出現
{min,}{min,}?直前のパターンがmin回以上繰り返し出現
{,max}{,max}?直前のパターンがmax回以下繰り返し出現
{min,max}{min,max}?直前のパターンがmin回~max回繰り返し出現

位置指定子(位置を表すメタ文字)

「位置指定子」は文字の位置を表すときに使います。

メタ文字意味
^行の先頭(シングルラインモード)、文章の先頭(マルチラインモード)
$行の末尾(シングルラインモード)、文章の末尾(マルチラインモード)
\b単語の先頭か末尾
\B単語の先頭、末尾以外
\A検索対象の文字列の先頭
\Z検索対象の文字列の末尾と、末尾の改行の前
\z検索対象の文字列の末尾
\Gマッチングの開始位置

「^」と「$」を使う際に気をつけるべきこと

完全一致を検索する目的で「^」と「$」を使った正規表現を記述すると、行の末尾に改行が含まれていた場合のマッチングを見逃してしまいます。厳正にチェックを行いたい場合には「\A」と「\z」を使うようにしましょう。

エスケープシーケンス

バックスラッシュは通常特殊文字をエスケープするために使いますが、バックスラッシュで始まる特殊文字もあります。

エスケープシーケンス意味
\aアラーム
\c●Ctrl + ●(●はA~Zの半角英字)
\n改行コード
\r改行コード
\Rすべての改行コード(「\n|\r|\n\r」と同義)
\tタブ
\v垂直タブ
\f改ページ
\s空白文字(「 |\t|\n|\r|\f」と同義)
\S空白文字以外のすべて
\dすべての半角数字(「[0-9]」と同義)
\D半角数字以外すべて(「[^0-9]」と同義)
\wすべての半角英数字とアンダースコア(「[a-zA-Z0-9_]」と同義)
\W半角英数字とアンダースコア以外すべて(「[^a-zA-Z0-9_]」と同義)
\l半角英小文字すべて
\L半角英小文字以外すべて(英大文字、数字、全角文字などすべて)
\u半角英大文字すべて
\U半角英大文字以外すべて(英小文字、数字、全角文字などすべて)
\0一致した文字列全体

先読み・後読みの言明(Assertion)

任意の正規表現を、位置指定に利用する場合に使います。

記述方法名称意味
〇〇〇(?=●●●)肯定先読み後方の文字列が●●●の場合の〇〇〇にだけ一致
(?<=●●●)〇〇〇肯定後読み前方の文字列が●●●の場合の〇〇〇にだけ一致
〇〇〇(?!●●●)否定先読み後方の文字列が●●●でない場合の〇〇〇にだけ一致
(?<!●●●)〇〇〇否定後読み前方の文字列が●●●でない場合の〇〇〇にだけ一致

正規表現の記述例

PHPの正規表現の記述例をいくつかご紹介します。

郵便番号・携帯電話番号

数字とハイフンを組み合わせます。

//郵便番号
[0-9]{3}-[0-9]{4}
\d{3}-\d{4}

//携帯電話番号
0[789]0-?[0-9]{4}-?[0-9]{4}
0[789]0-?\d{4}-?\d{4}

改行

改行コードはOSごとに違うので、すべてを検索できるようにします。
\r|\n\r\|\n

半角スペース・全角スペース

半角スペースと全角スペースは、正規表現の中でもそのまま書くことができます。
//半角スペースと全角スペースをそのまま文字として記述
[  ]

URLの「www.」の有り無しの両方をマッチ

デリミタが「/(スラッシュ)」以外であればスラッシュのエスケープは不要ですが、デリミタがスラッシュの場合は以下のようにスラッシュのエスケープも必要です。

https:\/\/(www\.)?sample\.ne.jp

数字で終わるURLパス

WordPressのような「/category/123456」と数字で終わるURLのパスを検索する場合の例です。

\/category/[0-9]+

PHPでの正規表現の使い方

PHPで正規表現のマッチングを行う関数としては、preg_match()関数とmb_ereg_match()関数、mb_ereg()関数 があります。ちなみに、ereg()関数はPHP5.3.0で非推奨となり、PHP7.0.0で削除されました。

また、正規表現を使って文字列の置換を行う場合は、preg_replace()関数やmb_ereg_replace()関数を使います。ひとつ注意しないといけないのは、str_replace()関数と比べると処理が遅いので、str_replace()関数で処理できる場合はpreg_replace()関数やmb_ereg_replace()関数を使うのは控えたほうがいいです。

なお、文字エンコードが「UTF-8」以外の日本語を扱う場合は、mb_ereg_match()関数、mb_ereg()関数、mb_ereg_replace()関数を使わなければいけません。

正規表現のパターン

PHPでの正規表現のパターンは文字列として記述し、「’」(シングルクォート)で囲みます。なお、ダブルクォート「”」を使うと変数が展開されたりなど意図しない動作をすることがあるので注意してください。

また、正規表現のパターンは通常スラッシュ「/」などのデリミタで囲います。

'/正規表現のパターン/'

デリミタ

PHPの正規表現ではなんらかの半角文字でその両端を囲い、その囲み文字を「デリミタ」といいます。デリミタには、スラッシュ (/)、 ハッシュ記号 (#) 、チルダ (~) などがよく使われます。

/foo bar/
#^[^0-9]$#
+php+
%[a-zA-Z0-9_-]%

パターン修飾子

PHPの正規表現では、「abc」を大文字・小文字を問わずにマッチングするかどうかを調べる場合、以下のような「パターン修飾子」と呼ばれるオプションをつけることがあります。

'/abc/i'
主なパターン修飾子意味
i大文字と小文字を区別しない
mマルチラインモードで検索
s「.(ドット)」が改行を含むすべての文字にマッチ
xパターンの空白文字を完全に無視
u対象文字列をUTF-8として扱う

そのほかのパターン修飾子については、PHPのドキュメンテーションをご参照ください。

1:preg_match

preg_match()では、検索対象の文字列の中から、正規表現で指定したパターンとマッチした文字列を検索することができます。パターンと一致すれば1を返し、マッチしなければ0を返します。また、エラーが発生した場合にはfalseを返します。

preg_matchの使い方

preg_match() の構文は次のとおりです。[ ]内はオプションで、省略することができます。
preg_match ( $pattern, $subject [, &$matches [, $flags [, $offset ]]] )

(1)patternに、正規表現のパターンを記述します。
(2)subjectに、検索したい文字列を指定します。
(3)matchesには、検索結果が配列として代入されます。
(4)flagsに「PREG_OFFSET_CAPTURE」を指定すると、マッチした文字列の出現した位置をバイト数で取得できます。
(5)検索開始位置を先頭からではなく指定した位置からにしたい場合は、offsetを指定します。

具体例1)文字列の中に数字があるかどうか

簡単な例として、文字列の中に数字があるかどうかを判別します。

<?php
$subject = 'abc123def';
if(preg_match('/[0-9]/', $subject)) {
echo '含まれる';
} else {
echo '含まれない';
}
?>

結果は「含まれる」です。

ちなみに、マッチした数字は「1」です。

具体例2)日本語の文字列を検索

日本語のマルチバイト文字を検索する場合の例です。

<?php
$text = '隣の家では秋田犬と柴犬を飼っています。';
preg_match('/.{2}(?=犬)/u', $text, $data);
echo $data[0];
?>

結果は「秋田」となります。

なお、日本語を扱う場合にはパターン修飾子uをつけないと「��」のような意図しない結果になる場合があるので注意が必要です。

2:preg_match_all

preg_mach()では最初にマッチしたものしか第三引数の配列(matches)に格納されません。マッチしたすべての文字列を取得したい場合には、preg_match_all()を使います。戻り値は、パターンがマッチした総数を返します。

preg_match_allの使い方

preg_mach_all()の構文は次のとおりです。
preg_match_all($pattern, $subject [,&$matches [,$flags [,$offset ]]])

preg_match()と同じような使い方ですが、matchesに返されるのが2次元配列という点に注意が必要です。

(1)pattern、subject、offsetの使い方はpreg_mach()と同じです。
(2)matchesには、検索結果がflagsで指定した形式の多次元配列で代入されます。
(3)flagsには「PREG_PATTERN_ORDER(デフォルト)」、「PREG_SET_ORDER」、「PREG_OFFSET_CAPTURE」が指定できます。

具体例)文字列の中に数字があるかどうか

preg_match()と同じことをした場合に、配列に返される値がどうなるかをみてみます。

<?php
$subject = 'abc123def';
preg_match_all('/[0-9]/', $subject, $matches);
var_dump($matches);
?>
結果は
array(1) {
[0]=>
array(3) {
[0]=> string(1) "1"
[1]=> string(1) "2"
[2]=> string(1) "3"
}
}
}

となり、結果がすべて配列で入っているのが確認できます。

3:preg_replace

preg_replace() は、正規表現で検索した文字列を別の文字列に置き換えたいときに使います。パターンがマッチした場合は置き換え後の文字列を返し、マッチしなければ元の文字列をそのまま返します。なお、エラーが発生した場合はNULLを返します。

なお、preg_replace()関数は、PHP7.0.0から「/e」修飾子のサポートが終了しています。

preg_replaceの使い方

preg_replace()の構文は次のとおりです。
preg_replace($pattern, $replacement, $subject [, $limit [, &$count]])

(1)patternに、正規表現のパターンを記述します。
(2)replacementに、置き換える内容を記述します。
(3)subjectに、検索したい文字列を指定します。
(4)limitで、各パターンで置き換えを行う最大回数を指定します。デフォルトは -1 (制限なし)になっています。
(5)countには置き換え回数が変数として渡されます。

具体例)文字列の中にあるURLにリンクをつける

文字列の中から正規表現でURLを探し出して、リンクタグをつける例です。

<?php
$str = "URLは https://www.sample.co.jp/php/example.html です。";
echo preg_replace('/(https?|ftp)(:\/\/[-_.!~*\'()a-zA-Z0-9;\/?:\@&=+\$,%#]+)/', '<a href="$1$2">$1$2</a>', $str);
?>

PHPがサポートしている正規表現

PHPには、次の3種類の正規表現があります。
(1)Perl互換の正規表現 (PCRE)
(2)mbstringの正規表現 (mberegex)
(3)POSIX拡張正規表現

PCREとPOSIX

PHPで正規表現を行う場合には、PCREを利用するべきです。

PCRE

PCREの正規表現はPOSIXより機能・速度が優れています。というのも、POSIXの正規表現関数が一番左側にある最も長くマッチするパターンを探すのに対し、PCREの正規表現関数ではマッチするパターンが最初に見つかった時点で処理を終了するからです。

POSIX

POSIXはバイナリセーフではなく、日本語も扱えません。また、PHP5.3で非推奨となり、PHP7.0で削除されました。

POSIXとPCREの正規表現関数比較

動作POSIXPCRE
検索ereg()preg_match()
検索eregi()preg_match()
検索sql_regcase()preg_match()
置換ereg_replace()preg_replace()
置換eregi_replace()preg_replace()
分割split()preg_split()
分割spliti()preg_split()

文字コードの問題

PCREで日本語を処理する場合は、文字エンコーディングをUTF-8 にし、パターン修飾子uを付ける必要があります。

しかし、PCREではUTF-8しか扱えないため、古いWebサイトなどで文字エンコーディングにShift_JISやEUC-JPが使われている場合には使うことができません。そういった場合には、mb_ereg系のmbregexを利用します。

PCRE正規表現構文について

PCREでの正規表現パターンの記述方法をもう一度整理します。PHPには正規表現リテラルがなく、パターンは文字列として記述し、そのパターンは「デリミタ」で囲う必要があります。

また、パターンの記述方法は先ほど紹介したメタ文字を使ってパターン化された文字列で表現します。その際、エスケープする必要がある文字はエスケープするのを忘れないようにしてください。

正規表現を身につけて高度な検索や置換をしよう

正規表現にはさまざまなルールがあり、それを組み合わせることによって複雑な検索条件をパターン化することができます。そして、preg_match()関数などと合わせることで検索や置き換えが簡単にできるようになるので、とても便利です。

記号ばかりで最初は難しく感じられますが、使いこなすことができるようになれば便利な機能なので、しっかりと身につけておきましょう。

タイトルとURLをコピーしました