[PHP]preg_quote関数の使い方完全ガイド!正規表現の特殊文字を安全にエスケープする方法

PHP

はじめに

PHPで正規表現を使う際、ユーザー入力や動的な文字列を正規表現パターンに含めようとして、予期しないエラーに遭遇したことはありませんか?そんなときに役立つのがpreg_quote関数です。

この記事では、正規表現初心者の方でも理解できるよう、preg_quoteの基本から実践的な使い方まで詳しく解説します。

preg_quoteとは?

preg_quoteは、文字列内の正規表現特殊文字を自動的にエスケープしてくれる関数です。これにより、動的な文字列を安全に正規表現パターンとして使用できるようになります。

なぜ必要なのか?

正規表現には特別な意味を持つ文字(メタ文字)があります:

. \ + * ? [ ^ ] $ ( ) { } = ! < > | : - #

これらの文字をそのまま正規表現で使うと、意図しない動作やエラーが発生します。

基本構文

preg_quote(
    string $str,              // エスケープする文字列
    ?string $delimiter = null // デリミタ文字(オプション)
): string

戻り値: エスケープされた文字列を返します。

基本的な使用例

例1: 特殊文字のエスケープ

<?php
// エスケープなし(危険!)
$search = "price: $100.50";
$pattern = "/$search/";  // エラーになる可能性

// preg_quoteを使用(安全!)
$search = "price: $100.50";
$pattern = "/" . preg_quote($search) . "/";

echo $pattern . "\n";
// 出力: /price: \$100\.50/

$text = "The price: $100.50 is reasonable.";
if (preg_match($pattern, $text)) {
    echo "マッチしました!\n";
}
?>

例2: デリミタの指定

デリミタ自体もエスケープが必要な場合があります。第2引数でデリミタを指定すると、それもエスケープされます。

<?php
$search = "https://example.com/page";
$delimiter = '/';

// デリミタを指定しない場合
$escaped1 = preg_quote($search);
echo $escaped1 . "\n";
// 出力: https://example\.com/page (スラッシュはエスケープされない)

// デリミタを指定した場合
$escaped2 = preg_quote($search, '/');
echo $escaped2 . "\n";
// 出力: https:\/\/example\.com\/page (スラッシュもエスケープされる)

$pattern = "/" . $escaped2 . "/";
$text = "Visit https://example.com/page for more info.";
if (preg_match($pattern, $text)) {
    echo "URLが見つかりました!\n";
}
?>

どの文字がエスケープされるのか?

preg_quoteは以下の正規表現メタ文字をエスケープします:

<?php
$special = ". \\ + * ? [ ^ ] $ ( ) { } = ! < > | : - #";
$escaped = preg_quote($special);

echo "元の文字列: $special\n";
echo "エスケープ後: $escaped\n";
/*
出力:
元の文字列: . \ + * ? [ ^ ] $ ( ) { } = ! < > | : - #
エスケープ後: \. \\ \+ \* \? \[ \^ \] \$ \( \) \{ \} \= \! \< \> \| \: \- \#
*/
?>

実践的な使用例

例1: ユーザー入力での検索機能

ユーザーが入力した文字列で検索する際、特殊文字が含まれていても安全に処理できます。

<?php
function searchInText($userInput, $text) {
    // ユーザー入力を安全にエスケープ
    $escaped = preg_quote($userInput, '/');
    $pattern = "/$escaped/i";  // iフラグで大文字小文字を区別しない
    
    if (preg_match($pattern, $text, $matches)) {
        return "「{$matches[0]}」が見つかりました!";
    } else {
        return "見つかりませんでした。";
    }
}

// 特殊文字を含む検索
echo searchInText("C++", "I love C++ programming.") . "\n";
// 出力: 「C++」が見つかりました!

echo searchInText("$100", "Price is $100") . "\n";
// 出力: 「$100」が見つかりました!

echo searchInText("[重要]", "メール件名: [重要]会議のお知らせ") . "\n";
// 出力: 「[重要]」が見つかりました!
?>

例2: 動的な正規表現パターンの構築

複数のキーワードを含む動的な正規表現を作成する場合:

<?php
$keywords = ["C++", "$price", "[注意]", "file.txt"];

// 各キーワードをエスケープ
$escapedKeywords = array_map(function($keyword) {
    return preg_quote($keyword, '/');
}, $keywords);

// OR条件の正規表現を作成
$pattern = '/' . implode('|', $escapedKeywords) . '/';
echo "パターン: $pattern\n";
// 出力: /C\+\+|\$price|\[注意\]|file\.txt/

$text = "Check the file.txt for $price information.";
preg_match_all($pattern, $text, $matches);

echo "見つかったキーワード:\n";
print_r($matches[0]);
/*
出力:
見つかったキーワード:
Array
(
    [0] => file.txt
    [1] => $price
)
*/
?>

例3: ファイル名やパスの検索

ファイルパスには多くの特殊文字が含まれるため、preg_quoteが必須です。

<?php
function findFilePath($searchPath, $logText) {
    $escaped = preg_quote($searchPath, '/');
    $pattern = "/" . $escaped . "/";
    
    if (preg_match($pattern, $logText)) {
        return "パス「{$searchPath}」がログに含まれています。";
    } else {
        return "パスが見つかりませんでした。";
    }
}

$log = "Error in file: C:\\Users\\Admin\\Documents\\report.txt at line 42";

echo findFilePath("C:\\Users\\Admin\\Documents\\report.txt", $log) . "\n";
// 出力: パス「C:\Users\Admin\Documents\report.txt」がログに含まれています。
?>

例4: HTMLタグの検索と置換

<?php
function replaceTag($tag, $replacement, $html) {
    // タグをエスケープ
    $escaped = preg_quote($tag, '/');
    $pattern = "/$escaped/";
    
    return preg_replace($pattern, $replacement, $html);
}

$html = "<div>Content</div> <span>More content</span>";
$result = replaceTag("<div>", "<section>", $html);

echo $result . "\n";
// 出力: <section>Content</div> <span>More content</span>
?>

preg_quoteを使わない場合の危険性

例: エラーが発生するケース

<?php
// ❌ 危険: preg_quoteを使わない
$userInput = "How much? $100+";  // 特殊文字が含まれる
$pattern = "/$userInput/";

// これは正規表現としてエラーまたは予期しない動作をする
// $100は変数として解釈され、+は「1回以上の繰り返し」として解釈される

// ✅ 安全: preg_quoteを使う
$escaped = preg_quote($userInput, '/');
$pattern = "/$escaped/";

$text = "Question: How much? $100+ for premium";
if (preg_match($pattern, $text)) {
    echo "正しくマッチしました!\n";
}
?>

セキュリティの観点

ユーザー入力を直接正規表現に使用すると、ReDoS(Regular Expression Denial of Service)攻撃のリスクがあります。preg_quoteを使うことで、この種の攻撃を防げます。

<?php
// 悪意のある入力例
$maliciousInput = "(a+)+b";  // バックトラックを引き起こす可能性

// preg_quoteを使えば安全
$safe = preg_quote($maliciousInput, '/');
// 出力: \(a\+\)\+b (メタ文字がエスケープされ、単なる文字列として扱われる)
?>

よくある使用パターン

パターン1: 前方一致・後方一致の検索

<?php
$keyword = "test.php";
$escaped = preg_quote($keyword, '/');

// 前方一致(行頭)
$pattern = "/^$escaped/";

// 後方一致(行末)
$pattern = "/$escaped$/";

// 単語境界を使用
$pattern = "/\b$escaped\b/";
?>

パターン2: 大文字小文字を区別しない検索

<?php
$search = "C++";
$escaped = preg_quote($search, '/');
$pattern = "/$escaped/i";  // iフラグで大文字小文字を無視

$text = "I love c++ and C++ programming!";
preg_match_all($pattern, $text, $matches);
print_r($matches[0]);
/*
Array
(
    [0] => c++
    [1] => C++
)
*/
?>

パターン3: 複数の文字列を一度に検索

<?php
$terms = ["PHP", "C++", "$variable", "file.txt"];
$escaped = array_map(fn($t) => preg_quote($t, '/'), $terms);
$pattern = '/\b(' . implode('|', $escaped) . ')\b/i';

$text = "Learn PHP and C++ to handle file.txt with $variable";
preg_match_all($pattern, $text, $matches);

echo "見つかった用語: " . implode(", ", $matches[0]) . "\n";
// 出力: 見つかった用語: PHP, C++, file.txt, $variable
?>

注意点とベストプラクティス

1. デリミタは常に指定する

パターンで使うデリミタを第2引数に指定することを習慣化しましょう。

// ✅ 推奨
$escaped = preg_quote($str, '/');

// △ デリミタがない場合は省略可能だが、統一性のため指定が望ましい
$escaped = preg_quote($str);

2. エスケープ後にさらに正規表現を追加できる

<?php
$filename = "report.txt";
$escaped = preg_quote($filename, '/');

// エスケープした文字列の前後に正規表現を追加
$pattern = "/\b$escaped\b/";  // 単語境界を追加
$pattern = "/$escaped\s+\d+/";  // スペースと数字を追加
?>

3. UTF-8文字列の扱い

日本語などのマルチバイト文字は特殊文字ではないため、エスケープされません。

<?php
$japanese = "こんにちは。値段は¥1000です。";
$escaped = preg_quote($japanese, '/');

echo $escaped . "\n";
// 出力: こんにちは。値段は¥1000です。
// (日本語はそのまま、句点もエスケープ不要)
?>

4. エスケープ不要な場面

正規表現を使わない文字列関数が使える場合は、そちらを優先しましょう。

// 単純な文字列検索なら正規表現は不要
if (str_contains($text, $search)) {  // PHP 8.0+
    // ...
}

// 文字列置換も同様
$result = str_replace($search, $replace, $text);

まとめ

preg_quoteは、動的な文字列を正規表現で安全に扱うための必須関数です。

重要ポイント:

  • ユーザー入力を正規表現に使う際は必ずpreg_quoteを使う
  • デリミタを第2引数に指定する習慣をつける
  • セキュリティ対策(ReDoS攻撃の防止)にも有効
  • 全ての正規表現メタ文字を自動でエスケープ
  • 単純な文字列検索ならstr_containsなどの方が効率的

正規表現は強力ですが、適切にエスケープしないと危険です。preg_quoteを使って安全なコードを書きましょう!

参考リンク


セキュリティを意識したPHPコーディングで、安全なWebアプリケーションを構築しましょう!

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