はじめに
配列の中から特定のパターンにマッチする要素だけを抽出したい、そんな場面は開発中によくありますよね。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_grep
はarray_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
は配列のフィルタリングに特化した強力な関数です。
覚えておくべきポイント:
- 正規表現で配列をフィルタリング:パターンマッチで簡潔に記述
- キーは保持される:元の配列のキーがそのまま残る
- PREG_GREP_INVERT:反転マッチでマッチしない要素を抽出
- 様々な用途:メール、URL、ファイル名、ログなど多様な場面で活用
他の関数との使い分け:
- パターンマッチング →
preg_grep
- 置換も同時に →
preg_filter
- 複雑なロジック →
array_filter
この関数をマスターすれば、配列操作が劇的に効率化されます。ぜひ実際のプロジェクトで活用してみてください!