1. ホーム
  2. PHP

【PHP】preg_match_all の使い方|正規表現で複数のマッチをまとめて取得する方法

Share

文字列の中からパターンに一致する部分を すべて 取り出したいことはよくあります。たとえば本文中のすべての URL を集めたり、投稿からハッシュタグを一覧にしたり、といった処理です。PHP でこれを担うのが preg_match_all です。よく似た preg_match は「最初に見つかった1件」しか返しませんが、preg_match_all は一致した箇所を残らず配列に集めてくれます。この記事では両者の違いから、結果が入る $matches の構造、フラグによる並び順の変化、戻り値、そして日本語(マルチバイト)を扱うときの注意点までを、動くコード例つきで解説します。

preg_match との違い

preg_match は対象の文字列を先頭から探し、パターンに最初に一致した1件を見つけた時点で処理を終えます。それに対して preg_match_all は文字列全体を走査し、一致する箇所をすべて拾い上げます。「1件だけで十分か」「全部まとめて欲しいか」で使い分けると考えると分かりやすいです。

次のコードは、同じ文字列・同じパターンに対して両者がどう振る舞うかを比べたものです。数字の並びを \d+ で探しています。

example.php
<?php
$text = '在庫は 3 個、明日 12 個、来週 48 個 入荷します';

// preg_match は「最初の1件」だけ
preg_match('/\d+/', $text, $one);
print_r($one);
// Array ( [0] => 3 )

// preg_match_all は「一致した全件」
preg_match_all('/\d+/', $text, $all);
print_r($all);
// Array ( [0] => Array ( [0] => 3 [1] => 12 [2] => 48 ) )

preg_match の結果は 3 だけですが、preg_match_all では 31248 がすべて配列に入っています。この「入れ子の配列」が preg_match_all を使ううえで最初に理解しておきたいポイントです。

基本の使い方と $matches の構造

preg_match_allpreg_match_all($pattern, $subject, $matches) の形で呼び出します。第3引数の $matches は参照渡しで、ここに一致結果が格納されます。戻り値には一致した件数(見つからなければ 0、エラー時は false)が入ります。

キャプチャグループ(丸括弧 ( ))を含まないパターンの場合、$matches[0] に「パターン全体に一致した文字列」の配列が入ります。まずはこの一番シンプルな形を見てみましょう。

example.php
<?php
$text = 'apple, banana, cherry';

$count = preg_match_all('/[a-z]+/', $text, $matches);

echo $count;          // 3(一致した件数)
print_r($matches[0]); // Array ( [0] => apple [1] => banana [2] => cherry )

戻り値の $count が一致件数(ここでは 3)、$matches[0] が一致した文字列の一覧です。キャプチャグループを使わないうちは、ほぼ $matches[0] だけを見ればよいと覚えておくとよいでしょう。

キャプチャグループを使って部分を取り出す

パターンに丸括弧でキャプチャグループを書くと、その部分だけを別途取り出せます。$matches[0] には全体の一致が、$matches[1] には1番目のグループ、$matches[2] には2番目のグループ…というように、グループ番号に対応した配列が並びます。

次は「キー=値」の形をした文字列から、キーと値を分けて取り出す例です。

example.php
<?php
$text = 'name=Tanaka; age=28; city=Tokyo';

// (\w+) が1番目、(\w+) が2番目のグループ
preg_match_all('/(\w+)=(\w+)/', $text, $matches);

print_r($matches[1]); // Array ( [0] => name [1] => age  [2] => city )
print_r($matches[2]); // Array ( [0] => Tanaka [1] => 28 [2] => Tokyo )

// 同じ位置どうしが対応している
foreach ($matches[1] as $i => $key) {
    echo "{$key} : {$matches[2][$i]}\n";
}
// name : Tanaka
// age : 28
// city : Tokyo

$matches[1]$matches[2] は同じ添字どうしが対応しています。つまり $matches[1][0]name$matches[2][0]Tanaka がペア、という具合です。この並び方が既定の動作で、次に説明するフラグと深く関わっています。

PREG_PATTERN_ORDER と PREG_SET_ORDER の違い

preg_match_all の第4引数には、結果の並べ方を指定するフラグを渡せます。よく使うのはこの2つで、指定しないときは PREG_PATTERN_ORDER が既定になります。

フラグ並び方
PREG_PATTERN_ORDER(既定)グループ番号ごとにまとめる。$matches[0] に全体一致の配列、$matches[1] に1番目のグループの配列…と並ぶ
PREG_SET_ORDER一致1件ごとにまとめる。$matches[0] が1件目の一致セット、$matches[1] が2件目…となり、各要素に [0](全体)・[1](1番目のグループ)…が入る

言葉だけだと分かりにくいので、同じパターンを両フラグで実行して構造の違いを見てみましょう。

example.php
<?php
$text = '2026-07-10, 2026-12-25';

// PREG_SET_ORDER:一致1件ごとにまとまる
preg_match_all('/(\d{4})-(\d{2})-(\d{2})/', $text, $matches, PREG_SET_ORDER);

print_r($matches);
/*
Array
(
    [0] => Array ( [0] => 2026-07-10 [1] => 2026 [2] => 07 [3] => 10 )
    [1] => Array ( [0] => 2026-12-25 [1] => 2026 [2] => 12 [3] => 25 )
)
*/

// 1件ずつ扱いやすい
foreach ($matches as $date) {
    echo "{$date[1]}年{$date[2]}月{$date[3]}日\n";
}
// 2026年07月10日
// 2026年12月25日

PREG_PATTERN_ORDER は「年だけを全部」「月だけを全部」といった列単位の取り出しに向いています。一方 PREG_SET_ORDER は、上の例のように「一致した1件ごとに年・月・日をまとめて処理したい」ときに読みやすくなります。foreach で1件ずつ回すなら PREG_SET_ORDER が扱いやすい、と覚えておくとよいでしょう。

戻り値の扱い方

preg_match_all の戻り値は一致した件数です。1件も一致しなければ 0、正規表現の書き方が不正などでエラーが起きた場合は false を返します。0false はどちらも条件式では偽になりますが、意味は「見つからなかった」か「処理そのものが失敗した」かで異なります。両者を区別したいときは === で厳密に比較します。

example.php
<?php
$text = 'ここに数字はありません';

$count = preg_match_all('/\d+/', $text, $matches);

if ($count === false) {
    echo '正規表現の実行に失敗しました';
} elseif ($count === 0) {
    echo '一致する箇所はありませんでした';
} else {
    echo "{$count} 件見つかりました";
}
// 一致する箇所はありませんでした

一致がなかった場合、$matches には空の配列が入ります($matches[0] も空配列)。そのため、件数を見ずに $matches[0] をそのまま foreach で回しても安全に空回りします。

実践例:本文からURLやハッシュタグを抜き出す

ここまでの内容を、よくある場面に当てはめてみます。まずはテキストに含まれる URL をすべて集める例です。https または http で始まる連続した文字列を拾っています。

example.php
<?php
$text = <<<TEXT
公式サイトは https://example.com です。
資料は http://example.org/docs も参考にしてください。
TEXT;

preg_match_all('#https?://[^\s]+#', $text, $matches);

print_r($matches[0]);
// Array
// (
//     [0] => https://example.com
//     [1] => http://example.org/docs
// )

ここではパターンの区切り文字にスラッシュではなく # を使っています。URL には / が含まれるため、区切り文字を # にしておくと \/ のエスケープが不要になり、パターンが読みやすくなります。[^\s]+ は「空白以外の文字が続く限り」という意味で、URL の末尾までを拾っています。

次はハッシュタグの抽出です。# に続く文字をキャプチャグループで取り出すと、記号を除いたタグ名だけを一覧にできます。

example.php
<?php
$text = '今日は #PHP と #正規表現 を勉強した #毎日更新';

// # に続く「単語構成文字」をグループで取得(u 修飾子で日本語対応)
preg_match_all('/#(\w+)/u', $text, $matches);

print_r($matches[1]);
// Array ( [0] => PHP [1] => 正規表現 [2] => 毎日更新 )

$matches[0] には #PHP のように記号付きの全体一致が、$matches[1] には PHP のようにタグ名だけが入ります。パターン末尾の u が、日本語を含むハッシュタグを正しく扱うための鍵になっています。次の節でくわしく見ていきます。

日本語を扱うときは u 修飾子を付ける

PHP の正規表現(PCRE)は、既定では文字列をバイト単位で扱います。UTF-8 の日本語は1文字が複数バイトで構成されるため、u 修飾子を付けずに .\w、文字数指定などを使うと、文字の途中で区切ってしまい文字化けや意図しない一致の原因になります。パターン末尾に u(PCRE_UTF8 修飾子)を付けると、文字列を UTF-8 として1文字単位で正しく解釈します。

example.php
<?php
$text = 'あいうえお';

// u なし:1文字をバイト単位で見てしまう
preg_match_all('/./', $text, $m1);
echo count($m1[0]); // 15(3バイト × 5文字)

// u あり:文字単位で正しく数える
preg_match_all('/./u', $text, $m2);
echo count($m2[0]); // 5

u を付けないと「あ」の3バイトがそれぞれ別の一致として数えられ、件数が 15 になってしまいます。u を付ければ 5 と、見た目どおりの文字数になります。日本語を含む文字列を preg_match_all で処理するときは、基本的に u を付けると覚えておくと安全です。なお、対象の文字列自体が UTF-8 で保存されていることも前提になります。

まとめ

preg_match_all は、文字列中でパターンに一致する箇所をすべて配列に集める関数です。1件だけ欲しいなら preg_match、全部欲しいなら preg_match_all と使い分けます。結果は第3引数の $matches に入り、キャプチャグループを使わなければ $matches[0] に一致文字列の一覧が並びます。第4引数のフラグは、列単位でまとめる PREG_PATTERN_ORDER(既定)と、一致1件ごとにまとめる PREG_SET_ORDER があり、foreach で1件ずつ処理するなら後者が扱いやすくなります。戻り値は一致件数で、失敗時は false、日本語を含むときは u 修飾子を忘れないようにしましょう。

参考ページ