[PHP]preg_grep関数とは?配列から正規表現でフィルタリングする方法を完全解説

PHP

はじめに

配列の中から特定のパターンにマッチする要素だけを抽出したい、そんな場面は開発中によくありますよね。PHPには、そんな時に便利なpreg_grep関数があります。

この関数を使えば、複雑な条件でも正規表現を使って簡潔に配列をフィルタリングできます。今回はpreg_grepの使い方を初心者の方にも分かりやすく、実例豊富に解説していきます。

preg_grep関数とは?

preg_grepは、配列の各要素を正規表現パターンでチェックし、マッチする要素だけを含む配列を返すPHP関数です。

基本構文

preg_grep(string $pattern, array $array, int $flags = 0): array|false

パラメータ:

  • $pattern – 検索する正規表現パターン(PCRE形式)
  • $array – 検索対象の配列
  • $flags – オプションフラグ(PREG_GREP_INVERTで反転マッチ)

戻り値:

  • マッチした要素の配列(元のキーは保持される)
  • エラー時はfalse

基本的な使い方

シンプルな例

<?php

$fruits = [
    'apple',
    'banana',
    'apricot',
    'cherry',
    'avocado',
    'grape'
];

// 'a'で始まる果物を抽出
$result = preg_grep('/^a/', $fruits);
print_r($result);
/* 出力:
Array
(
    [0] => apple
    [2] => apricot
    [4] => avocado
)
*/

// 'rry'で終わる果物を抽出
$result2 = preg_grep('/rry$/', $fruits);
print_r($result2);
/* 出力:
Array
(
    [3] => cherry
)
*/

重要ポイント:キーは保持される

<?php

$data = [
    10 => 'test@example.com',
    20 => 'invalid email',
    30 => 'user@test.jp'
];

$emails = preg_grep('/@/', $data);
print_r($emails);
/* 出力:
Array
(
    [10] => test@example.com
    [30] => user@test.jp
)
// 元のキー(10, 30)が保持されている
*/

PREG_GREP_INVERTフラグ:反転マッチ

PREG_GREP_INVERTフラグを使うと、パターンにマッチしない要素を抽出できます。

<?php

$strings = [
    'Hello123',
    'World',
    'Test456',
    'Example',
    'Code789'
];

// 数字を含む文字列を抽出
$withNumbers = preg_grep('/\d/', $strings);
print_r($withNumbers);
/* 出力:
Array
(
    [0] => Hello123
    [2] => Test456
    [4] => Code789
)
*/

// 数字を含まない文字列を抽出(反転)
$withoutNumbers = preg_grep('/\d/', $strings, PREG_GREP_INVERT);
print_r($withoutNumbers);
/* 出力:
Array
(
    [1] => World
    [3] => Example
)
*/

実践的な使用例

1. メールアドレスのバリデーションとフィルタリング

<?php

$contacts = [
    'admin@example.com',
    '無効なメール',
    'user@test.jp',
    '090-1234-5678',
    'support@company.co.jp',
    'not-an-email'
];

// 有効なメールアドレスだけを抽出
$pattern = '/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/';
$validEmails = preg_grep($pattern, $contacts);

print_r($validEmails);
/* 出力:
Array
(
    [0] => admin@example.com
    [2] => user@test.jp
    [4] => support@company.co.jp
)
*/

echo "有効なメールアドレス数: " . count($validEmails) . "\n";

2. ログファイルのフィルタリング

<?php

$logLines = [
    '[2025-01-15 10:30:45] INFO: Application started',
    '[2025-01-15 10:31:12] ERROR: Database connection failed',
    '[2025-01-15 10:31:30] DEBUG: Query executed',
    '[2025-01-15 10:32:00] ERROR: File not found',
    '[2025-01-15 10:32:15] WARNING: Memory usage high',
    '[2025-01-15 10:33:00] INFO: User logged in'
];

// ERRORレベルのログだけを抽出
$errors = preg_grep('/\[ERROR\]/', $logLines);
print_r($errors);
/* 出力:
Array
(
    [1] => [2025-01-15 10:31:12] ERROR: Database connection failed
    [3] => [2025-01-15 10:32:00] ERROR: File not found
)
*/

// ERROR以外のログを抽出(反転マッチ)
$nonErrors = preg_grep('/\[ERROR\]/', $logLines, PREG_GREP_INVERT);
print_r($nonErrors);

3. ファイル名のフィルタリング

<?php

$files = [
    'document.pdf',
    'image.jpg',
    'script.php',
    'style.css',
    'photo.png',
    'README.md',
    'config.json',
    'video.mp4'
];

// 画像ファイルだけを抽出
$images = preg_grep('/\.(jpg|jpeg|png|gif|webp)$/i', $files);
print_r($images);
/* 出力:
Array
(
    [1] => image.jpg
    [4] => photo.png
)
*/

// ドキュメントファイルを抽出
$documents = preg_grep('/\.(pdf|doc|docx|txt|md)$/i', $files);
print_r($documents);
/* 出力:
Array
(
    [0] => document.pdf
    [5] => README.md
)
*/

4. IPアドレスの検証

<?php

$addresses = [
    '192.168.1.1',
    'localhost',
    '10.0.0.1',
    'invalid-ip',
    '256.256.256.256',  // 無効なIP
    '172.16.0.1',
    'example.com'
];

// IPv4アドレスっぽいものを抽出(簡易版)
$pattern = '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/';
$ipAddresses = preg_grep($pattern, $addresses);
print_r($ipAddresses);
/* 出力:
Array
(
    [0] => 192.168.1.1
    [2] => 10.0.0.1
    [4] => 256.256.256.256
    [6] => 172.16.0.1
)
*/

// より厳密なIPv4バリデーション
function getValidIPv4($addresses) {
    $pattern = '/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/';
    return preg_grep($pattern, $addresses);
}

$validIPs = getValidIPv4($addresses);
print_r($validIPs);

5. URLのフィルタリング

<?php

$urls = [
    'https://example.com',
    'http://test.com/page',
    'ftp://files.example.com',
    'not a url',
    'https://secure-site.jp/path/to/resource',
    'javascript:alert(1)',  // 危険なURL
    'mailto:info@example.com'
];

// HTTPSのURLだけを抽出
$secureUrls = preg_grep('/^https:\/\//i', $urls);
print_r($secureUrls);
/* 出力:
Array
(
    [0] => https://example.com
    [4] => https://secure-site.jp/path/to/resource
)
*/

// HTTPまたはHTTPSのURLを抽出
$webUrls = preg_grep('/^https?:\/\//i', $urls);
print_r($webUrls);

6. 日本語を含む文字列の抽出

<?php

$texts = [
    'Hello World',
    'こんにちは世界',
    'Test123',
    '日本語テキスト',
    'English only',
    'MixedテキストMixed'
];

// 日本語(ひらがな、カタカナ、漢字)を含むものを抽出
$pattern = '/[ぁ-んァ-ヶー一-龠]/u';
$japaneseTexts = preg_grep($pattern, $texts);
print_r($japaneseTexts);
/* 出力:
Array
(
    [1] => こんにちは世界
    [3] => 日本語テキスト
    [5] => MixedテキストMixed
)
*/

// 英語のみの文字列を抽出(日本語を含まない)
$englishOnly = preg_grep($pattern, $texts, PREG_GREP_INVERT);
print_r($englishOnly);

7. 電話番号の抽出

<?php

$contacts = [
    '090-1234-5678',
    'test@example.com',
    '03-1234-5678',
    '無効な番号',
    '080-9876-5432',
    'http://example.com',
    '0120-123-456'
];

// 日本の電話番号形式を抽出
$pattern = '/^0\d{1,4}-\d{1,4}-\d{4}$/';
$phoneNumbers = preg_grep($pattern, $contacts);
print_r($phoneNumbers);
/* 出力:
Array
(
    [0] => 090-1234-5678
    [2] => 03-1234-5678
    [4] => 080-9876-5432
    [6] => 0120-123-456
)
*/

array_filterとの比較

preg_greparray_filterと似ていますが、正規表現に特化しています。

<?php

$numbers = ['10', '20', 'abc', '30', 'def', '40'];

// preg_grepを使用(正規表現で数値のみ)
$numeric1 = preg_grep('/^\d+$/', $numbers);

// array_filterを使用(コールバック関数)
$numeric2 = array_filter($numbers, function($value) {
    return is_numeric($value);
});

// preg_grepの方が簡潔!
// array_filterは複雑な条件に向いている

print_r($numeric1);
print_r($numeric2);

使い分けの基準:

  • パターンマッチが主目的preg_grep
  • 複雑なロジックが必要array_filter
  • パフォーマンス重視(単純条件) → array_filter

連想配列での使用

<?php

$users = [
    'admin' => 'admin@example.com',
    'user1' => 'invalid',
    'user2' => 'user2@test.com',
    'guest' => 'not-an-email',
    'user3' => 'user3@company.jp'
];

// 有効なメールアドレスを持つユーザーを抽出
$pattern = '/@/';
$validUsers = preg_grep($pattern, $users);
print_r($validUsers);
/* 出力:
Array
(
    [admin] => admin@example.com
    [user2] => user2@test.com
    [user3] => user3@company.jp
)
// キー(ユーザー名)も保持される
*/

実用的なヘルパー関数

1. 複数パターンでのフィルタリング

<?php

function pregGrepMultiple(array $patterns, array $array) {
    $result = [];
    foreach ($patterns as $pattern) {
        $matches = preg_grep($pattern, $array);
        $result = array_merge($result, $matches);
    }
    return array_unique($result);
}

$files = [
    'image.jpg',
    'document.pdf',
    'photo.png',
    'script.php',
    'video.mp4'
];

$mediaFiles = pregGrepMultiple([
    '/\.(jpg|png|gif)$/i',  // 画像
    '/\.(mp4|avi|mov)$/i'   // 動画
], $files);

print_r($mediaFiles);

2. 安全なpreg_grep実行

<?php

function safePregGrep($pattern, $array, $flags = 0) {
    $result = @preg_grep($pattern, $array, $flags);
    
    if ($result === false || preg_last_error() !== PREG_NO_ERROR) {
        $errors = [
            PREG_INTERNAL_ERROR => '内部エラー',
            PREG_BACKTRACK_LIMIT_ERROR => 'バックトラック制限超過',
            PREG_RECURSION_LIMIT_ERROR => '再帰制限超過',
            PREG_BAD_UTF8_ERROR => '不正なUTF-8',
            PREG_BAD_UTF8_OFFSET_ERROR => '不正なUTF-8オフセット'
        ];
        
        $errorMsg = $errors[preg_last_error()] ?? '不明なエラー';
        throw new Exception("preg_grepエラー: {$errorMsg}");
    }
    
    return $result;
}

// 使用例
try {
    $result = safePregGrep('/pattern/', $array);
} catch (Exception $e) {
    error_log($e->getMessage());
    $result = [];
}

3. キーを再インデックス化する

<?php

function pregGrepReindex($pattern, $array, $flags = 0) {
    $result = preg_grep($pattern, $array, $flags);
    return array_values($result);  // キーを0から振り直す
}

$data = [
    5 => 'apple',
    10 => 'banana',
    15 => 'apricot'
];

$filtered = pregGrepReindex('/^a/', $data);
print_r($filtered);
/* 出力:
Array
(
    [0] => apple
    [1] => apricot
)
// キーが0から始まる
*/

パフォーマンスの最適化

大量データの処理

<?php

// 100万件のデータがある場合
$largeArray = array_fill(0, 1000000, 'test data');

// preg_grepは一度で処理
$start = microtime(true);
$result = preg_grep('/pattern/', $largeArray);
$time1 = microtime(true) - $start;

// foreachでの処理(比較用)
$start = microtime(true);
$result2 = [];
foreach ($largeArray as $key => $value) {
    if (preg_match('/pattern/', $value)) {
        $result2[$key] = $value;
    }
}
$time2 = microtime(true) - $start;

echo "preg_grep: {$time1}秒\n";
echo "foreach: {$time2}秒\n";
// preg_grepの方が通常高速

よくあるミスと対策

1. デリミタの忘れ

<?php

$data = ['test', 'example'];

// ❌ 間違い:デリミタがない
// $result = preg_grep('test', $data);  // エラー!

// ✅ 正しい:デリミタを付ける
$result = preg_grep('/test/', $data);

2. エスケープ忘れ

<?php

$files = ['test.txt', 'example.pdf'];

// ❌ 間違い:.は任意の1文字にマッチ
$result = preg_grep('/.txt/', $files);  // 'atxt'などもマッチ

// ✅ 正しい:.をエスケープ
$result = preg_grep('/\.txt$/', $files);

3. 大文字小文字の考慮

<?php

$words = ['Apple', 'banana', 'CHERRY'];

// 大文字小文字を区別
$result1 = preg_grep('/apple/', $words);  // マッチなし

// 大文字小文字を区別しない(iフラグ)
$result2 = preg_grep('/apple/i', $words);  // Appleがマッチ

まとめ

preg_grepは配列のフィルタリングに特化した強力な関数です。

覚えておくべきポイント:

  1. 正規表現で配列をフィルタリング:パターンマッチで簡潔に記述
  2. キーは保持される:元の配列のキーがそのまま残る
  3. PREG_GREP_INVERT:反転マッチでマッチしない要素を抽出
  4. 様々な用途:メール、URL、ファイル名、ログなど多様な場面で活用

他の関数との使い分け:

  • パターンマッチングpreg_grep
  • 置換も同時にpreg_filter
  • 複雑なロジックarray_filter

この関数をマスターすれば、配列操作が劇的に効率化されます。ぜひ実際のプロジェクトで活用してみてください!

参考リンク

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