はじめに
PHPで複雑なパターンの文字列置換を行いたいとき、preg_replace関数は非常に強力なツールです。単純なstr_replaceでは実現できない、柔軟な置換処理が可能になります。
この記事では、preg_replaceの基本から高度なテクニックまで、実用的なサンプルコードとともに詳しく解説します。
preg_replaceとは?
preg_replaceは、正規表現パターンにマッチする部分を指定した文字列で置換するPHP関数です。
str_replaceとの違い
| 機能 | str_replace | preg_replace |
|---|---|---|
| 置換方法 | 完全一致 | 正規表現パターン |
| 複雑なパターン | ❌ | ✅ |
| 後方参照 | ❌ | ✅ |
| パフォーマンス | 速い | やや遅い |
| 使いやすさ | 簡単 | 学習コストあり |
基本構文
preg_replace(
string|array $pattern, // 正規表現パターン
string|array $replacement, // 置換後の文字列
string|array $subject, // 置換対象の文字列
int $limit = -1, // 置換回数の上限(-1は無制限)
int &$count = null // 置換回数を格納する変数
): string|array|null
戻り値: 置換後の文字列(または配列)を返します。エラー時はnullを返します。
基本的な使用例
例1: シンプルな置換
<?php
$text = "電話番号: 090-1234-5678";
$pattern = '/\d{3}-\d{4}-\d{4}/';
$replacement = '[電話番号は非表示]';
$result = preg_replace($pattern, $replacement, $text);
echo $result . "\n";
// 出力: 電話番号: [電話番号は非表示]
?>
例2: 複数箇所を一度に置換
<?php
$text = "価格: $100, $200, $300";
$pattern = '/\$\d+/';
$replacement = '¥***';
$result = preg_replace($pattern, $replacement, $text);
echo $result . "\n";
// 出力: 価格: ¥***, ¥***, ¥***
?>
例3: 置換回数を制限
<?php
$text = "apple apple apple apple";
$pattern = '/apple/';
$replacement = 'orange';
$limit = 2; // 最初の2回だけ置換
$result = preg_replace($pattern, $replacement, $text, $limit);
echo $result . "\n";
// 出力: orange orange apple apple
?>
例4: 置換回数をカウント
<?php
$text = "test test test";
$pattern = '/test/';
$replacement = 'exam';
$count = 0;
$result = preg_replace($pattern, $replacement, $text, -1, $count);
echo $result . "\n";
echo "置換回数: {$count}回\n";
/*
出力:
exam exam exam
置換回数: 3回
*/
?>
後方参照を使った高度な置換
後方参照を使うと、マッチした部分を置換後の文字列で再利用できます。
基本的な後方参照
<?php
// 日付形式を変換: 2024/10/25 → 2024年10月25日
$text = "今日は2024/10/25です。";
$pattern = '/(\d{4})\/(\d{2})\/(\d{2})/';
$replacement = '$1年$2月$3日';
$result = preg_replace($pattern, $replacement, $text);
echo $result . "\n";
// 出力: 今日は2024年10月25日です。
?>
名前付きキャプチャグループ
<?php
// メールアドレスの一部を隠す
$text = "連絡先: john.doe@example.com";
$pattern = '/(?P<user>\w+)\.(?P<domain>\w+)@(?P<host>[\w\.]+)/';
$replacement = '${user}.***@${host}';
$result = preg_replace($pattern, $replacement, $text);
echo $result . "\n";
// 出力: 連絡先: john.***@example.com
?>
実践的な使用例
例1: HTMLタグの除去
<?php
function stripHtmlTags($html) {
// すべてのHTMLタグを除去
$pattern = '/<[^>]*>/';
$replacement = '';
return preg_replace($pattern, $replacement, $html);
}
$html = "<p>これは<strong>テスト</strong>です。</p>";
echo stripHtmlTags($html) . "\n";
// 出力: これはテストです。
?>
例2: 電話番号のフォーマット統一
<?php
function formatPhoneNumber($text) {
// ハイフンなしの番号をハイフンありに変換
$pattern = '/(\d{3})(\d{4})(\d{4})/';
$replacement = '$1-$2-$3';
return preg_replace($pattern, $replacement, $text);
}
$text = "電話: 09012345678";
echo formatPhoneNumber($text) . "\n";
// 出力: 電話: 090-1234-5678
?>
例3: URLの自動リンク化
<?php
function autoLinkUrls($text) {
$pattern = '/(https?:\/\/[^\s]+)/';
$replacement = '<a href="$1" target="_blank">$1</a>';
return preg_replace($pattern, $replacement, $text);
}
$text = "サイト https://example.com をご覧ください。";
echo autoLinkUrls($text) . "\n";
// 出力: サイト <a href="https://example.com" target="_blank">https://example.com</a> をご覧ください。
?>
例4: 空白文字の正規化
<?php
function normalizeWhitespace($text) {
// 連続する空白を1つに
$text = preg_replace('/\s+/', ' ', $text);
// 前後の空白を削除
$text = preg_replace('/^\s+|\s+$/', '', $text);
return $text;
}
$text = " これは テスト です。 ";
echo "「" . normalizeWhitespace($text) . "」\n";
// 出力: 「これは テスト です。」
?>
例5: Markdown風の太字変換
<?php
function convertBold($text) {
// **テキスト** を <strong>テキスト</strong> に変換
$pattern = '/\*\*([^*]+)\*\*/';
$replacement = '<strong>$1</strong>';
return preg_replace($pattern, $replacement, $text);
}
$text = "これは**重要**な**メッセージ**です。";
echo convertBold($text) . "\n";
// 出力: これは<strong>重要</strong>な<strong>メッセージ</strong>です。
?>
例6: センシティブ情報のマスキング
<?php
function maskCreditCard($text) {
// クレジットカード番号の中間をマスク
$pattern = '/(\d{4})-(\d{4})-(\d{4})-(\d{4})/';
$replacement = '$1-****-****-$4';
return preg_replace($pattern, $replacement, $text);
}
$text = "カード番号: 1234-5678-9012-3456";
echo maskCreditCard($text) . "\n";
// 出力: カード番号: 1234-****-****-3456
?>
複数パターンの一括置換
配列を使って複数のパターンを一度に処理できます。
<?php
$text = "私はリンゴとバナナとオレンジが好きです。";
$patterns = [
'/リンゴ/',
'/バナナ/',
'/オレンジ/'
];
$replacements = [
'apple',
'banana',
'orange'
];
$result = preg_replace($patterns, $replacements, $text);
echo $result . "\n";
// 出力: 私はappleとbananaとorangeが好きです。
?>
置換時の注意点とコールバック
動的な置換が必要な場合: preg_replace_callback
複雑な変換ロジックが必要な場合は、preg_replace_callbackを使います。
<?php
// マッチした数字を2倍にする
$text = "価格: 100円、200円、300円";
$pattern = '/(\d+)円/';
$result = preg_replace_callback($pattern, function($matches) {
$number = (int)$matches[1];
$doubled = $number * 2;
return $doubled . '円';
}, $text);
echo $result . "\n";
// 出力: 価格: 200円、400円、600円
?>
ケース変換の例
<?php
// 単語の先頭を大文字に
$text = "hello world from php";
$pattern = '/\b\w/';
$result = preg_replace_callback($pattern, function($matches) {
return strtoupper($matches[0]);
}, $text);
echo $result . "\n";
// 出力: Hello World From Php
?>
タイムスタンプの変換
<?php
// Unix時刻を日本語形式に
$text = "投稿日時: 1729814400";
$pattern = '/\d{10}/';
$result = preg_replace_callback($pattern, function($matches) {
$timestamp = (int)$matches[0];
return date('Y年m月d日 H:i', $timestamp);
}, $text);
echo $result . "\n";
// 出力: 投稿日時: 2024年10月25日 00:00
?>
よくある間違いと解決法
間違い1: デリミタのエスケープ忘れ
// ❌ 間違い
$pattern = '/https://example.com/'; // エラー!
// ✅ 正しい方法1: デリミタをエスケープ
$pattern = '/https:\/\/example\.com/';
// ✅ 正しい方法2: 別のデリミタを使用
$pattern = '#https://example\.com#';
間違い2: 特殊文字のエスケープ忘れ
// ❌ 間違い: $は特殊文字
$pattern = '/$100/';
// ✅ 正しい: エスケープまたはpreg_quoteを使用
$pattern = '/\$100/';
// または
$escaped = preg_quote('$100', '/');
$pattern = "/$escaped/";
間違い3: 後方参照の番号ミス
// ❌ 間違い: キャプチャグループは2つしかない
$pattern = '/(\d+)-(\d+)/';
$replacement = '$1-$2-$3'; // $3は存在しない
// ✅ 正しい
$replacement = '$1-$2';
パフォーマンスの最適化
ヒント1: 単純な置換はstr_replaceを使う
// 正規表現が不要な場合はstr_replaceの方が高速
// ❌ 遅い
$result = preg_replace('/apple/', 'orange', $text);
// ✅ 速い
$result = str_replace('apple', 'orange', $text);
ヒント2: パターンをコンパイルして再利用
// 同じパターンを何度も使う場合、一度定義する
$pattern = '/\d+/';
foreach ($texts as $text) {
$result = preg_replace($pattern, 'NUM', $text);
}
ヒント3: 不要なキャプチャグループは避ける
// ❌ 不要なキャプチャ
$pattern = '/(\d+)px/';
// ✅ 非キャプチャグループ(より高速)
$pattern = '/(?:\d+)px/';
// または後方参照が不要なら
$pattern = '/\d+px/';
セキュリティ上の注意点
ユーザー入力をパターンに使う場合
<?php
// ❌ 危険: ユーザー入力を直接使用
$userInput = $_POST['search'];
$pattern = "/$userInput/";
// ✅ 安全: preg_quoteを使用
$userInput = $_POST['search'];
$escaped = preg_quote($userInput, '/');
$pattern = "/$escaped/";
$result = preg_replace($pattern, $replacement, $text);
?>
ReDoS攻撃の防止
<?php
// 複雑すぎるパターンはバックトラックで処理時間が爆発的に増える
// タイムアウトを設定するなどの対策を
ini_set('pcre.backtrack_limit', 1000000);
ini_set('pcre.recursion_limit', 100000);
?>
便利なパターン集
パターン1: 改行コードの統一
<?php
// すべての改行を\nに統一
$text = preg_replace('/\r\n|\r|\n/', "\n", $text);
?>
パターン2: 数字をカンマ区切りに
<?php
function addCommas($text) {
return preg_replace_callback('/\d+/', function($matches) {
return number_format((int)$matches[0]);
}, $text);
}
echo addCommas("価格は1000000円です") . "\n";
// 出力: 価格は1,000,000円です
?>
パターン3: メールアドレスの一部隠し
<?php
function hideEmail($text) {
$pattern = '/([a-z0-9_.-]+)@([a-z0-9.-]+)/i';
$replacement = function($matches) {
$user = $matches[1];
$domain = $matches[2];
$hiddenUser = substr($user, 0, 2) . '***';
return $hiddenUser . '@' . $domain;
};
return preg_replace_callback($pattern, $replacement, $text);
}
echo hideEmail("連絡先: john.doe@example.com") . "\n";
// 出力: 連絡先: jo***@example.com
?>
まとめ
preg_replaceは、PHPで強力な文字列置換を実現する必須関数です。
重要ポイント:
- 正規表現で柔軟なパターンマッチングが可能
- 後方参照で置換文字列にマッチ内容を再利用できる
- 複雑な処理は
preg_replace_callbackを使う - ユーザー入力を扱う際は
preg_quoteでエスケープ - 単純な置換は
str_replaceの方が高速 - パフォーマンスとセキュリティに注意
使い分けの目安:
- str_replace: 単純な文字列置換
- preg_replace: パターンマッチング、後方参照が必要
- preg_replace_callback: 動的な置換ロジックが必要
正規表現の知識を深めて、効率的な文字列処理を実現しましょう!
参考リンク
この記事が役立ったら、ぜひシェアしてください!
