こんにちは、かつコーチです。
前回はファイルの読み書きを扱いました。
今回は、文字列のパターンマッチングに使う「正規表現」を解説します。
「なんだか記号だらけで難しそう」と敬遠されがちなテーマですが、入力チェックやデータの抽出など、実務での出番がとても多い機能です。
最初から全部覚えようとせず、よく使うパターンから少しずつ慣れていきましょう。
正規表現とは?
文字列のパターンを表現するルール
正規表現とは、「文字列がどんなパターンに一致するか」を記号の組み合わせで表現するルールのことです。
たとえば「メールアドレスの形式になっているか」「電話番号として正しい桁数か」といったチェックを、1行のパターンで表現できます。
PHPでは主に次の3つの関数を使います。
| 関数 | 用途 |
|---|---|
preg_match() | パターンに一致するか調べる(一致した部分の取得も可能) |
preg_match_all() | パターンに一致する箇所をすべて取得する |
preg_replace() | パターンに一致した部分を別の文字列に置き換える |
なぜ正規表現が必要なのか
「文字列のチェックなら strpos() や str_contains() で十分では?」と思うかもしれません。
たしかに「特定の単語が含まれているか」程度のチェックなら、それらの関数で十分です。
ただ、「先頭が数字3桁、次にハイフン、次に数字4桁」のような構造そのものをチェックしたい場合、単純な文字列関数では対応が難しくなります。
私が実務で最初に正規表現の必要性を感じたのは、郵便番号の入力チェックを実装したときでした。
strpos() を組み合わせてゴリゴリ書こうとして、条件分岐がどんどん複雑になり、「これは正規表現を使うべき場面だ」と気づいた経験があります。
preg_matchの基本
手順1:パターンに一致するか調べる
まずは、文字列が特定のパターンに一致するかどうかを調べる preg_match() です。
<?php
$zipcode = "123-4567";
// パターンは / /(デリミタ)で囲む
if (preg_match('/^\d{3}-\d{4}$/', $zipcode)) {
echo "正しい郵便番号の形式です";
} else {
echo "郵便番号の形式が正しくありません";
}
// 出力: 正しい郵便番号の形式です
?>
preg_match() は、パターンに一致すれば 1、一致しなければ 0 を返します。
パターンの中身を少し分解してみましょう。
| 記号 | 意味 |
|---|---|
^ | 文字列の先頭 |
\d | 数字1文字(0〜9) |
{3} | 直前の要素を3回繰り返す |
- | ハイフンそのもの |
$ | 文字列の末尾 |
^\d{3}-\d{4}$ で「先頭から末尾まで、数字3桁・ハイフン・数字4桁」という構造を1行で表現できています。
手順2:一致した部分を取り出す
preg_match() の第3引数に変数を渡すと、一致した部分を取得できます。
<?php
$text = "お問い合わせ番号:INQ-2026-0042 について";
if (preg_match('/INQ-(\d{4})-(\d{4})/', $text, $matches)) {
echo "全体:" . $matches[0] . PHP_EOL;
echo "年:" . $matches[1] . PHP_EOL;
echo "番号:" . $matches[2] . PHP_EOL;
}
// 出力:
// 全体:INQ-2026-0042
// 年:2026
// 番号:0042
?>
丸括弧 () で囲んだ部分は「グループ」として扱われ、$matches[1]、$matches[2] のように番号順に取り出せます。
$matches[0] には常に「パターン全体に一致した文字列」が入る点も覚えておきましょう。
よく使うパターンの書き方
メールアドレスの簡易チェック
<?php
$email = "example@katsu-coach.com";
$pattern = '/^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/';
if (preg_match($pattern, $email)) {
echo "メールアドレスの形式として妥当です";
} else {
echo "メールアドレスの形式が正しくありません";
}
?>
正規表現だけで完璧なメールアドレス検証を行うのは非常に難しいと言われていますが、簡易的な形式チェックとしてはこの程度で十分実用的です。
より厳密な検証が必要な場合は、PHPに標準で用意されている filter_var($email, FILTER_VALIDATE_EMAIL) を使う方法もあります。
半角数字・電話番号の抽出
<?php
$text = "お電話は 03-1234-5678 までお願いします";
if (preg_match('/\d{2,4}-\d{2,4}-\d{4}/', $text, $matches)) {
echo "電話番号:" . $matches[0];
}
// 出力: 電話番号:03-1234-5678
?>
{2,4} のように、繰り返し回数を「最小,最大」で範囲指定することもできます。
preg_replaceで置換する
手順1:基本的な置換
preg_replace() は、パターンに一致した部分を別の文字列に置き換えます。
<?php
$text = "TEL:03-1234-5678 FAX:03-8765-4321";
// ハイフンをすべて空文字に置換する
$result = preg_replace('/-/', '', $text);
echo $result;
// 出力: TEL:0312345678 FAX:0387654321
?>
手順2:グループを使った置換
置換後の文字列でも、preg_match() と同じようにグループを使えます。
<?php
$date = "2026/08/11";
// yyyy/mm/dd → yyyy年mm月dd日 に変換する
$result = preg_replace('/(\d{4})\/(\d{2})\/(\d{2})/', '$1年$2月$3日', $date);
echo $result;
// 出力: 2026年08月11日
?>
置換後の文字列内で $1、$2 のようにグループを参照できるので、パターンで抜き出した部分をそのまま組み立て直すことができます。
つまずきやすいポイント:$matches の添字を勘違いした話
インデックス0を上書きしてしまった失敗
私が実際にやってしまった失敗が、$matches の添字の勘違いです。
❌ Before:$matches[0] を「最初のグループ」だと思い込む
<?php
$text = "商品コード:ITEM-4821";
if (preg_match('/ITEM-(\d+)/', $text, $matches)) {
$itemCode = $matches[0]; // 「最初のグループ」のつもりで書いた
echo "コード:" . $itemCode;
}
// 出力: コード:ITEM-4821(欲しかったのは "4821" だけ)
?>
私は最初、「$matches[0] が最初のグループの中身」だと勘違いして、意図せず「ITEM-」の文字列まで含んだ値を変数に入れてしまい、そのままDBに保存しようとしてバリデーションエラーになった、という失敗をしたことがあります。
✅ After:$matches[0] は全体一致、グループは $matches[1] から
<?php
$text = "商品コード:ITEM-4821";
if (preg_match('/ITEM-(\d+)/', $text, $matches)) {
$itemCode = $matches[1]; // グループ(数字部分)だけを取得
echo "コード:" . $itemCode;
}
// 出力: コード:4821
?>
$matches[0] は必ず「パターン全体に一致した文字列」、丸括弧で囲んだグループは $matches[1] から始まる、というルールをしっかり覚えておくと、この手の勘違いを防げます。
応用:preg_match_allと置換のコールバック
一致する箇所をすべて取得する
パターンに一致する箇所が複数ある場合は preg_match_all() を使います。
<?php
$text = "在庫:りんご 5個、みかん 12個、バナナ 3個";
preg_match_all('/(\D+?) (\d+)個/', $text, $matches, PREG_SET_ORDER);
foreach ($matches as $match) {
echo trim($match[1]) . ":" . $match[2] . "個" . PHP_EOL;
}
// 出力:
// りんご:5個
// みかん:12個
// バナナ:3個
?>
PREG_SET_ORDER を指定すると、一致した組み合わせごとに配列がまとまるので、foreach で扱いやすくなります。
preg_replace_callbackで複雑な変換を行う
置換のたびに関数を実行したい場合は preg_replace_callback() が便利です。
<?php
$text = "価格は 1000円、送料は 500円です";
$result = preg_replace_callback('/(\d+)円/', function ($matches) {
$price = (int)$matches[1];
$withTax = (int)($price * 1.1); // 消費税10%を計算
return $withTax . "円(税込)";
}, $text);
echo $result;
// 出力: 価格は 1100円(税込)、送料は 550円(税込)です
?>
preg_replace() の置換文字列は固定パターンしか書けませんが、preg_replace_callback() なら「一致した値を使って計算する」といった動的な処理を差し込めます。
正規表現とロジックを組み合わせたいときは、この関数を思い出してください。
まとめ
この記事のポイント
preg_matchはパターンに一致するかを調べ、グループも取得できる$matches[0]は全体一致、グループは$matches[1]から始まるpreg_replaceはパターン一致部分の置換、グループは$1、$2で参照できる- 複数箇所の一致には
preg_match_all、動的な置換にはpreg_replace_callbackを使う - 完璧な検証をすべて正規表現でやろうとせず、
filter_varなど専用関数と使い分ける
次に読むべき記事
次回は、PHPで想定外のエラーが起きたときの対処法「try-catchによる例外処理」を解説します。
正規表現やファイル操作でエラーが起きたときにも役立つ内容なので、続けて読んでみてください。
→ 次の記事:PHPのエラーハンドリング:try-catchで例外処理を書く方法