PHP、WordPressでのテキスト置換などには正規表現を利用することができますが、正規表現の代表的なエラー、警告に以下のようなものがあります。
Warning: preg_replace(): Unknown modifier '?' in *.php on line *
この警告メッセージの発生原因と対処方法について解説します。
PHPの正規表現
phpでは、正規表現のパターンを区切り文字を使って囲む必要があります。この区切り文字は「デリミタ」と呼ばれ、好きな文字を利用することができます。
デリミタ(区切り文字)
多くの正規表現の例では、デリミタとして「/」が使われたものが取り上げられていますが、実際は英数字、バックスラッシュ、スペースなど以外であればほとんどの記号をデリミタとして利用することが可能です。
また、デリミタは開始と終了のペアで囲むことで使用されます。先ほどの「/」の例であれば/[0-9]{4,8}/
のように同じ文字を開始文字、終了文字に使用することができますが、カッコをデリミタに使用する場合、開始と終了のペアはカッコの組み合わせを指定する必要があります。
例えば、以下のような例が正しいデリミタの組み合わせとなります。
/[0-9]{4,8}/
#[0-9]{4,8}#
-[0-9]{4,8}-
[[0-9]{4,8}]
<[0-9]{4,8}>
モディファイア(修飾子)
また、phpの正規表現では、修飾子(モディファイア/パターン修飾子)と呼ばれる指定が可能です。
この修飾子は正規表現に加えて大文字小文字の区別や複数行の検索、空白の取り扱いなどの正規表現の動作を指定するためのもので、/[0-9]{4,8}/u
のようにデリミタの後に続けて記述します。
preg_match("/(.*?)/u", $html);
Unknown modifierが表示される条件
前述の通り、正規表現の指定は大きく「デリミタで区切られたパターン」と「モディファイア」の組み合わせで構成されています。
そして、Warning: preg_replace(): Unknown modifier '?'
が表示される理由には大きく2つの理由があります。
- 正規表現にそもそもデリミタが無い。
- デリミタと同じ文字をエスケープせずにパターンで使用している。
パターンをデリミタで区切らなかった場合、phpはエラーを返さず警告のみを返し、指定されたパターンを使って処理を続行します。そのため、単純にデリミタが読み取れなかった場合、phpの処理は継続されますが本来は正しいデリミタを指定しなければいけないことに注意が必要です。
また、デリミタと同じ文字をパターン内で使用する場合、デリミタのペアと区別するために同じ文字列は「\」をつかってエスケープをしなければいけません。
デリミタにはスラッシュ「/」がよく使われますが、URLなどをパターンに含める場合、以下のような例になります。
/https?:\/\//
これを/https?:///
のように記述してしまうと、/https?:/
までが正規表現パターン、//
の部分がモディファイアとして読み取られてしまい、正しく動作しません。
このような場合、デリミタを別の文字に変更することで記述をシンプルにすることも可能です。下記の例であれば、スラッシュをエスケープすることなく正しい正規表現として記述できます。
#https?://#
デリミタを忘れるといろいろな間違いが起こる
WordPressのカスタマイズなどを行う際、htmlタグを検索して置換するような例も多いかと思います。一方で、前述のデリミタの記述が不十分であるとphpが誤動作する要因となります。
例えば、見出しのタグ(h1~h6)を検索するような正規表現を使う場合、パターンの一例は<h([1-6])>(.*?)</h\1>
のようになりますが、これにデリミタを付け忘れると、1文字目の<
がデリミタの開始として判別されます。
preg_match("<h([1-6])>(.*?)</h\1>", $html);
このような記述はphpのエラーにはならず警告のみで処理が続行されますが、実際はデリミタの終了である>
の記号が前半にあるため、<h([1-6])>
の内側にあるh([1-6])
のみがパターンマッチ文字列として判断されてしまい、その>
以降の記述はすべてモディファイアとして解釈されます。
デリミタの使用が任意だと理解しているとこのようなケースでハマってしまうことがあるため、「正規表現にデリミタは必須」と思えておくのが良いと思います。
さらに、先ほどの正規表現にデリミタとして「/」を使う場合も注意が必要です。
preg_match("/<h([1-6])>(.*?)</h\1>/", $html);
このような表記をした場合、途中のスラッシュまでの<h([1-6])>(.*?)<
の記述がパターンマッチ文字列として解釈されてしまいます。
正規表現を正しく修正する方法
修正方法はシンプルでこれまでに説明した方法で簡単に対処できます。具体的には大きく以下の2つの方法があります。
1. デリミタと同じ文字をパターンマッチに使用する場合はエスケープする
preg_match("/<h([1-6])>(.*?)<\/h\1>/", $html);
こちらの方法では、パターンマッチ文字列の中にデリミタと同じ記号がたくさん使われている場合、作業が面倒になってしまいます。
デリミタはこのような時のために任意の文字列を指定できるようになっているため、より簡単な方法として次の対処方法がおススメです。
2. パターンマッチ文字列に使用している記号以外をデリミタにする
preg_match("~<h([1-6])>(.*?)</h\1>~", $html);
こちらであれば、内容も読み取りやすくシンプルになります。
コメント