PHPの正規表現を使い分ける:preg_match・preg_match_all・preg_replaceと入力検証
作成日:2026.08.28
PHPのpreg_match、preg_match_all、preg_replaceを使い、一致した値の取り出し、複数箇所の抽出、文字列置換を行う方法を解説します。u修飾子やmb_check_encodingにも触れ、メールアドレスの形式検証では、正規表現やfilter_var()だけで到達性・所有確認まではできないことを説明します。
目次
PHPで正規表現を使うと、文字列の形式確認や、文章の中から必要な値を取り出す処理を実装できます。
今回は、preg_match、preg_match_all、preg_replaceを使って、パターンに一致した文字列の一部を取り出す処理(キャプチャ)、複数箇所の抽出、文字列置換、入力値の形式確認を行います。
メールアドレスの検証も扱いますが、正規表現やPHPの形式検証だけで、メールアドレスを「完全に検証」できるわけではありません。形式の確認、メールを受信できるかどうか、入力者がそのアドレスを所有しているかどうかは、それぞれ別の問題として考えます。
前提
この記事のコードは、PHP 8.3.15で動作を確認しています。PHPの正規表現関数はPCREを利用しており、PHPのバージョンによって利用できる機能やエラーメッセージが異なる場合があります。
正規表現は、区切り文字でパターンを囲んで記述します。例えば、次のパターンでは、文字列の先頭から末尾までがuser-、8桁の日付、3桁の番号という形式になっているかを確認します。
$pattern = '/\Auser-\d{8}-\d{3}\z/';
\Aは対象文字列全体の先頭、\zは対象文字列全体の末尾を表します。文字列全体が決められた形式かを確認したい場合は、パターンをこの2つで囲むと意図が明確です。
^と$も先頭と末尾を表しますが、m修飾子を付けると、改行の直後と直前もそれぞれ先頭・末尾として扱います。そのため、複数行の文章を1行ずつ確認するときは^...$とmを組み合わせ、対象文字列全体を1つの値として確認するときは\A...\zを使う、というように目的で使い分けます。
preg_matchで一致確認とキャプチャを行う
preg_matchは、対象文字列にパターンが一致するかを確認する関数です。キャプチャグループを指定すると、一致した部分を後から取り出せます。
$subject = 'user-20260828-001';
$pattern = '/\Auser-(?<date>\d{8})-(?<number>\d{3})\z/';
$result = preg_match($pattern, $subject, $matches);
if ($result === 1) {
echo $matches['date']; // 20260828
echo $matches['number']; // 001
} elseif ($result === 0) {
echo '形式が一致しません';
} else {
throw new RuntimeException('正規表現の実行に失敗しました');
}
(?<date>...)のような書き方が名前付きキャプチャです。名前を付けない場合は、$matches[1]、$matches[2]のように番号で取り出します。後からパターンを変更する可能性がある場合は、番号の対応関係を追いかけなくてよい名前付きキャプチャの方が読みやすくなります。
preg_matchの戻り値は、次の3種類です。
1: パターンに一致した0: パターンに一致しなかったfalse: 正規表現のコンパイルや実行に失敗した
そのため、戻り値を単にif ($result)で判定するより、=== 1や=== falseで区別する方が安全です。不正なパターンは利用者入力から組み立てず、開発者が修正すべきエラーとして扱います。
preg_match_allで複数の値を抽出する
対象文字列の中から、同じ形式の値をすべて取り出したい場合はpreg_match_allを使います。例えば、文章内にある[[...]]形式のキーを抽出してみます。
$text = '竜王戦[[title01]]と名人戦[[title02]]を確認します。';
$pattern = '/\[\[(?<key>[a-zA-Z0-9_-]+)\]\]/u';
$count = preg_match_all($pattern, $text, $matches);
if ($count === false) {
throw new RuntimeException('正規表現の実行に失敗しました');
}
echo $count; // 2
foreach ($matches['key'] as $key) {
echo $key . PHP_EOL;
}
// title01
// title02
戻り値は、一致した件数です。キャプチャした値は、名前付きキャプチャの場合は$matches['key']に配列として入ります。
以前、貪欲マッチと非貪欲マッチについて紹介しました。複数の囲み文字を抽出する場合、.*を使うと最初の開始文字から最後の終了文字までをまとめて取得してしまうことがあります。今回の例では、キーとして許可する文字を[a-zA-Z0-9_-]+に限定しているため、必要な範囲だけを抽出できます。
日本語などのUTF-8文字列を扱う場合は、パターンの末尾にu修飾子を付けることがあります。uを付けると、パターンと対象文字列をUTF-8として扱います。入力文字列が正しいUTF-8であることも、あわせて確認してください。
文字列がUTF-8として正しいかを確認するには、mb_check_encodingを使えます。この関数は文字列を別の文字コードへ変換するのではなく、指定した文字コードとして解釈できるかをtrueまたはfalseで返します。利用するには、mbstring拡張が有効になっている必要があります。
$text = (string) ($_POST['text'] ?? '');
if (!mb_check_encoding($text, 'UTF-8')) {
echo '入力文字列の文字コードを確認してください';
exit;
}
$pattern = '/\[\[(?<key>[a-zA-Z0-9_-]+)\]\]/u';
$count = preg_match_all($pattern, $text, $matches);
この例では、文字列の確認後にu修飾子付きの正規表現を実行しています。mb_check_encodingでUTF-8と判定できても、文字列の内容がアプリケーションの入力条件に合っているとは限らないため、文字数や形式の検証は別に行います。
preg_replaceで文字列を置換する
正規表現に一致した部分を置き換える場合は、preg_replaceを使います。次の例では、利用者IDの中央部分をマスクしています。
$text = '利用者ID: user-20260828-001';
$pattern = '/(user-)\d{8}(-\d{3})/';
$masked = preg_replace($pattern, '$1********$2', $text);
if ($masked === null) {
throw new RuntimeException('文字列の置換に失敗しました');
}
echo $masked;
// 利用者ID: user-********-001
置換文字列の$1と$2は、パターンの1番目と2番目のキャプチャを表します。置換対象が複数ある場合は、対象文字列全体が置き換わるため、パターンが広すぎないかを確認してから使います。
個人情報や認証情報をマスクする場合は、マスク前の値をログへ出力しないことも重要です。正規表現で置換した後の値だけを表示する設計にしても、別のログや例外メッセージから元の値が漏れる可能性があります。
メールアドレスの形式を確認する
メールアドレスの検証では、複雑な正規表現を自作する前に、PHPのfilter_varを利用する方法を検討できます。FILTER_VALIDATE_EMAILを指定すると、PHPが定義するメールアドレス形式として検証できます。
$email = trim((string) ($_POST['email'] ?? ''));
if ($email === '') {
echo 'メールアドレスを入力してください';
} elseif (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
echo 'メールアドレスの形式を確認してください';
} else {
echo '形式上は利用できるメールアドレスです';
}
filter_varは、検証に成功すると値を返し、失敗するとfalseを返します。文字列によっては、戻り値をそのまま真偽値として扱うより、=== falseで判定する方が意図を明確にできます。
ここで確認できるのは、あくまで形式です。次の内容までは、この処理だけでは分かりません。
- ドメインのDNS設定が正しいか
- メールボックスが実際に存在するか
- メールを受信できる状態か
- 入力した利用者が、そのメールアドレスを所有しているか
所有確認のために確認メールを送信する処理などは、形式検証とは別の機能です。この記事では、そこまでの実装は扱いません。
メールアドレスに独自の制約を追加する場合
会社のドメインだけを受け付けるなど、アプリケーション固有の制約を設ける場合は、形式検証と独自条件を分けて確認します。
$email = trim((string) ($_POST['email'] ?? ''));
$is_valid_format = filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
$domain = strtolower(strrchr($email, '@') ?: '');
$is_allowed_domain = $domain === '@example.test';
if (!$is_valid_format || !$is_allowed_domain) {
echo '利用できないメールアドレスです';
}
このように、一般的な形式の確認と、サービス固有の許可条件を別々に書くと、正規表現へ過剰な役割を持たせずに済みます。特定のドメインを許可するだけなら、メールアドレス全体に複雑な正規表現を適用する必要はありません。
入力検証として使うときの注意点
クライアント側の検証だけに頼らない
HTMLのinput type="email"やJavaScriptの検証は、入力時の案内としては便利です。しかし、ブラウザから送信される値は変更できるため、サーバー側のPHPでも検証します。
動的なパターンにはpreg_quoteを使う
利用者入力を正規表現のパターンへ埋め込む場合、入力値をそのまま連結してはいけません。文字列として検索したい値なら、正規表現のメタ文字をエスケープします。
$keyword = (string) ($_GET['keyword'] ?? '');
$pattern = '/' . preg_quote($keyword, '/') . '/u';
$matched = preg_match($pattern, $text) === 1;
用途によっては、正規表現を使わずstr_containsなどの文字列関数で十分な場合もあります。PHP公式マニュアルでも、単純な文字列の包含確認だけならpreg_matchではなくstrposを検討するよう案内されています。
入力サイズとパターンの複雑さを制限する
利用者が入力した長い文字列へ、複雑な正規表現を適用すると、処理時間やメモリ使用量が増える場合があります。入力サイズの上限を設け、入れ子になった量指定子など、バックトラッキングが過剰になりやすいパターンを避けます。
正規表現のエラーを利用者へそのまま表示するのではなく、画面には一般的なエラーだけを表示し、詳細は開発者が確認できるログへ記録します。preg_last_error()やpreg_last_error_msg()を使う場合も、メールアドレスなどの入力値そのものをログへ残さないようにします。
まとめ
PHPで正規表現を使うときは、目的に応じて関数を使い分けます。
preg_match: 一致確認とキャプチャpreg_match_all: 複数箇所の抽出preg_replace: 一致箇所の置換
戻り値は、成功・不一致・エラーを区別して確認します。入力値をパターンへ埋め込む場合はpreg_quoteを使い、入力サイズとパターンの複雑さにも注意します。
メールアドレスについては、正規表現で完全な妥当性を判定しようとせず、PHPの形式検証やアプリケーション固有の条件を組み合わせます。形式が正しいことと、実際に受信できること、入力者が所有していることは別の確認です。
参考資料
奈良市を拠点に、27年以上の経験を持つフリーランスWebエンジニア、阿部辰也です。
これまで、ECサイトのバックエンド開発や業務効率化システム、公共施設の予約システムなど、多彩なプロジェクトを手がけ、企業様や制作会社様のパートナーとして信頼を築いてまいりました。
【制作会社・企業様向けサポート】
Webシステムの開発やサイト改善でお困りの際は、どうぞお気軽にご相談ください。小さな疑問から大規模プロジェクトまで、最適なご提案を心を込めてさせていただきます。
ぜひ、プロフィールやWeb制作会社様向け業務案内、一般企業様向け業務案内もご覧くださいね。
perlとPHPで使える、フォーム入力のエラーチェックに役立つ正規表現集
2009.03.28
フォームからのデータ送信はWebアプリケーションの基礎ですが、ユーザーが入力する内容が想定通りであるかを確認するためのエラーチェックは重要です。この記事では、perlとPHPでフォームデータをバリデートするための正規表現を具体例とともに紹介します。
Laravel 13をApache・XAMPPで動かす:VirtualHost・.htaccess・public_htmlの配置方法
2026.08.27
Laravel 13のプロジェクトをWindowsのXAMPP Apacheで動かすために、VirtualHostでpublicをDocumentRootに指定する方法、.htaccessでpublicへ振り分ける方法、public_htmlへpublicの中身を配置してindex.phpの参照先を変更する方法を解説します。.envやstorageなどを公開しないための注意点、Viteのビルド成果物、ApacheとLaravelのログを使ったトラブルシューティングも整理します。
Laravel 13+Vue.jsで登録・編集フォームを非同期化する:fetchとaxiosを比較
2026.08.26
Laravel 13のBlade画面にVue.jsを組み込み、membersテーブルのnameとemailを登録・編集するフォームを作ります。Vue.jsからLaravelのWebルートへJSONを送信し、CSRFトークン、バリデーションエラー、送信中・通信失敗の状態を扱います。リクエスト送信にはfetchとaxiosを使う方法を比較し、追加パッケージの有無やエラー処理の違いを整理します。
Laravelの開発サーバーを同一LANのスマホから確認する方法
2026.08.24
Laravelの開発サーバーをPCのLAN内IPアドレスで起動し、同じWi-Fiに接続したスマートフォンから画面を確認する手順を説明します。開発PCのIPアドレス確認、php artisan serveの待受設定、npm run build後のCSS・JavaScript確認、Windows Defender FirewallやWi-Fiルーターによって接続できない場合の切り分けを扱います。