2026年9月7日
2026年9月7日
PHPの正規表現の使い方:preg_match・preg_replace・preg_splitの基本とつまずきやすい点
PHP で正規表現を使うときは、preg_match() や preg_replace() などの preg_ で始まる関数(PCRE 関数)を使います。パターンの書き方そのものより、「区切り文字と修飾子」「日本語を扱うときの u 修飾子」「失敗したときに false が返ること」の 3 点を押さえるとつまずきが大きく減ります。この記事では、よく使う関数の使い分けと戻り値の扱い、ユーザー入力を安全にパターンへ入れる方法、WordPress のコードで正規表現を使うときの注意までを、動くコード例とともに説明します。
PHPの正規表現関数の全体像
PHP の正規表現は PCRE(Perl 互換正規表現)ライブラリを使っています。PHP 7.3 からは内部のライブラリが PCRE2 に切り替わりましたが、関数の名前や基本的な使い方は変わっていません。古い記事に出てくる ereg() や eregi() は PHP 7.0 で削除済みなので、現在のコードでは使えません。
よく使う関数は次のとおりです。
preg_match():最初に一致した 1 か所を調べる。入力チェックや値の取り出しに使うpreg_match_all():一致したすべての箇所を取り出すpreg_replace():一致した部分を置換するpreg_replace_callback():一致した部分ごとに関数を呼んで置換内容を組み立てるpreg_split():一致した部分を区切りにして配列へ分割するpreg_quote():文字列中の正規表現の特殊文字をエスケープする
単純な「含まれているか」「置き換えたい」だけなら、str_contains()(PHP 8.0 以降)や str_replace() の方が速く、読み間違いも起きにくいです。正規表現は、パターンで表す必要があるときに使うと考えてください。
パターンの書き方:区切り文字と修飾子
PHP ではパターンを区切り文字で囲み、その後ろに修飾子を付けます。区切り文字は / が一般的ですが、URL やパスのように / を多く含むパターンでは # や ~ を使うとエスケープが減って読みやすくなります。
<?php
// 区切り文字 / と修飾子 i(大文字小文字を区別しない)
preg_match( '/^wordpress$/i', 'WordPress' ); // 1
// / を含むパターンは # で囲むと読みやすい
preg_match( '#^https://example\.com/blog/#', 'https://example.com/blog/post-1' ); // 1
主な修飾子の意味は次のとおりです。
i:大文字と小文字を区別しないm:^と$を各行の先頭・末尾にも一致させるs:.を改行にも一致させるx:パターン中の空白を無視し、#以降をコメントとして書けるようにするu:パターンと対象文字列を UTF-8 として扱う
パターンはシングルクォートで書くのが基本です。ダブルクォートだと \n や $ が PHP 側で先に解釈されてしまい、意図と違うパターンになることがあります。
preg_matchで判定と値の取り出しをする
preg_match() は、一致すれば 1、一致しなければ 0、パターンの誤りなどで処理に失敗すれば false を返します。if ( preg_match(...) ) と書くと 0 と false の区別がつかないため、失敗を見逃したくない処理では === 1 や === false で比べます。
第 3 引数に変数を渡すと、一致した内容が配列で入ります。名前付きグループ (?<名前>...) を使うと、番号ではなく名前で値を取り出せるので、後からパターンを変えても壊れにくくなります。
<?php
$input = '2026-10-18';
$result = preg_match( '/\A(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})\z/', $input, $m );
if ( false === $result ) {
// パターンの誤りや実行上限に達したときなど
error_log( 'preg_match failed: ' . preg_last_error_msg() );
} elseif ( 1 === $result ) {
// 形式は合っている。日付として正しいかは別に確認する
if ( checkdate( (int) $m['month'], (int) $m['day'], (int) $m['year'] ) ) {
echo '有効な日付です';
}
}
この例では行頭・行末に ^ と $ ではなく \A と \z を使っています。$ は文字列末尾の改行の直前にも一致するため、"2026-10-18\n" のような値も通ってしまいます。入力チェックで「文字列全体がこの形式か」を確かめたいときは \A と \z(または D 修飾子)を使うのが安全です。また、正規表現が確認できるのは形式だけです。日付なら checkdate()、数値の範囲なら比較演算子というように、意味のチェックは別に行います。
preg_last_error_msg() は PHP 8.0 で追加された関数です。それより古い環境では preg_last_error() が返す定数(PREG_BACKTRACK_LIMIT_ERROR など)で原因を判断します。
preg_match_allで一致箇所をすべて取り出す
複数の一致を取り出したいときは preg_match_all() を使います。戻り値は一致した件数です。結果の配列は、既定ではグループごとにまとめられた形(PREG_PATTERN_ORDER)になります。1 件ごとにまとめた形が扱いやすい場合は PREG_SET_ORDER を指定します。
<?php
$text = '関連タグ: #php #正規表現 #WordPress';
preg_match_all( '/#([\p{L}\p{N}_]+)/u', $text, $matches, PREG_SET_ORDER );
foreach ( $matches as $match ) {
echo $match[1], "\n"; // php / 正規表現 / WordPress
}
日本語を扱うときはu修飾子を付ける
日本語などのマルチバイト文字を含む文字列では u 修飾子を付けます。付けないとパターンも対象文字列もバイト単位で扱われ、. が 1 文字ではなく 1 バイトに一致したり、文字の途中で切れた置換結果になったりします。
u を付けると、\p{Han}(漢字)、\p{Hiragana}(ひらがな)、\p{Katakana}(カタカナ)のような Unicode の文字種も使えます。
<?php
$name = 'やまだ たろう';
// ひらがなと半角・全角スペースだけで構成されているか
if ( 1 === preg_match( '/\A[\p{Hiragana}ー ]+\z/u', $name ) ) {
echo 'ひらがなで入力されています';
}
注意点が 2 つあります。1 つ目は、u を付けた状態で対象文字列が UTF-8 として不正だと、関数が false を返すことです。外部から受け取ったデータを扱うときは、戻り値の false を確認するか、事前に mb_check_encoding( $str, 'UTF-8' ) で確かめます。2 つ目は、u を付けると \d や \w が ASCII 以外の文字(全角数字など)にも一致する場合があることです。半角数字だけを許可したい入力チェックでは、\d ではなく [0-9] と書くと意図がはっきりします。
preg_replaceとpreg_replace_callbackで置換する
preg_replace() では、置換文字列の中で $1 や ${1} のようにしてグループの内容を使えます。後ろに数字が続く場合は ${1} と書かないと、別のグループ番号と解釈されることがあります。
置換内容を計算で組み立てたいときは preg_replace_callback() を使います。preg_replace() の置換文字列に PHP のコードを書く e 修飾子は PHP 7.0 で削除されているので、その用途はすべてコールバックに置き換えます。
<?php
// 電話番号のハイフンを除く
$tel = preg_replace( '/[^0-9]/', '', '03-1234-5678' ); // 0312345678
// 「2026/10/18」形式を「2026年10月18日」に変換する
$text = '公開日: 2026/10/18、更新日: 2026/10/20';
$text = preg_replace_callback(
'#([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})#',
function ( array $m ): string {
return sprintf( '%d年%d月%d日', $m[1], $m[2], $m[3] );
},
$text
);
echo $text; // 公開日: 2026年10月18日、更新日: 2026年10月20日
preg_replace() も失敗すると null を返します。結果をそのままデータベースに保存する処理では、null のまま保存して内容が消えることがないよう確認を入れてください。
preg_splitで文字列を分割する
区切りが 1 種類ではない文字列は preg_split() で分割します。タグ入力欄のように「カンマ・読点・空白のどれで区切ってもよい」形式に便利です。PREG_SPLIT_NO_EMPTY を付けると、区切りが連続したときにできる空の要素を除けます。
<?php
$tags = preg_split( '/[\s,、]+/u', 'PHP, 正規表現、WordPress 入門', -1, PREG_SPLIT_NO_EMPTY );
// ['PHP', '正規表現', 'WordPress', '入門']
ユーザー入力をパターンに入れるときはpreg_quoteを使う
検索語など外部から受け取った文字列をパターンに組み込むときは、preg_quote() で特殊文字をエスケープします。第 2 引数には区切り文字を渡します。これを省くと、入力に . や ( が含まれたときに意図しないパターンになったり、パターンの誤りで処理が止まったりします。
<?php
$keyword = 'a.b(c)';
$pattern = '/' . preg_quote( $keyword, '/' ) . '/iu';
$count = preg_match_all( $pattern, $text );
また、(a+)+ のように繰り返しを入れ子にしたパターンは、一致しない入力に対して試行回数が爆発的に増えることがあります(バックトラックの暴走)。PHP は pcre.backtrack_limit の上限に達すると処理を打ち切って false を返しますが、その前に CPU を長く使います。外部入力に対して使うパターンは、入れ子の繰り返しを避け、入力の長さに上限を設けておくと安全です。
正規表現を使わない方がよい場面
次のような処理は、専用の関数を使った方が正確です。
- メールアドレスの検証:
filter_var( $email, FILTER_VALIDATE_EMAIL )。WordPress ではis_email() - URL の分解:
parse_url()。WordPress で出力する URL はesc_url()でエスケープする - HTML の解析や書き換え:
DOMDocument、または WordPress 6.2 以降のWP_HTML_Tag_Processor - JSON の読み書き:
json_decode()とjson_encode()
特に HTML を正規表現で書き換えると、属性の順番や改行、入れ子のタグで簡単に壊れます。
WordPressで正規表現を使うときの注意
WordPress のテーマやプラグインでは、the_content フィルターで本文を preg_replace() で加工するコードをよく見かけます。本文は投稿ごとに形が違うため、想定外の HTML で表示が崩れることがあります。加工する前に対象をショートコードやブロックの出力に限定できないか検討し、加工した結果を出力するときは wp_kses_post() などで許可するタグを絞ってください。
コードを追加する場所は、親テーマの functions.php ではなく子テーマかプラグイン(または mu-plugins)にします。正規表現の誤りは画面が真っ白になる原因にもなるので、本番に入れる前にステージング環境で、日本語・空文字・長い文字列など複数の入力で動きを確かめてください。
データベース内の文字列を正規表現で一括置換したい場合は、コードを書くより、置換前に結果を確認できるツールを使う方が安全です。いずれの方法でも、事前にデータベースのバックアップを取ってから作業します。
なお、ここで挙げた関数の追加・削除の時期は PHP 公式マニュアルの記載に基づいています(2026年10月現在)。古い PHP で動かす必要がある場合は、各関数のマニュアルにある変更履歴を確認してください。
まとめ
- PHP の正規表現は
preg_で始まる PCRE 関数を使う。ereg()系とe修飾子は削除済み preg_match()の戻り値は1・0・falseの 3 通り。失敗を見逃さないよう厳密に比べる- 入力チェックでは
^と$より\Aと\zを使い、形式の確認と意味の確認を分ける - 日本語を扱うときは
u修飾子を付け、半角数字だけを許すなら[0-9]と書く - 置換内容を計算したいときは
preg_replace_callback()、ユーザー入力はpreg_quote()でエスケープする - メール・URL・HTML・JSON は専用の関数を使い、WordPress のコードは子テーマかプラグインに書いてステージングで確認する