[PHP]preg_replace関数の使い方完全ガイド!正規表現で文字列を置換する方法

PHP

はじめに

PHPで複雑なパターンの文字列置換を行いたいとき、preg_replace関数は非常に強力なツールです。単純なstr_replaceでは実現できない、柔軟な置換処理が可能になります。

この記事では、preg_replaceの基本から高度なテクニックまで、実用的なサンプルコードとともに詳しく解説します。

preg_replaceとは?

preg_replaceは、正規表現パターンにマッチする部分を指定した文字列で置換するPHP関数です。

str_replaceとの違い

機能str_replacepreg_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: 動的な置換ロジックが必要

正規表現の知識を深めて、効率的な文字列処理を実現しましょう!

参考リンク


この記事が役立ったら、ぜひシェアしてください!

タイトルとURLをコピーしました