PHP preg_match_all() 函数(实战总结)

PHP preg_match_all() 函数:掌握正则匹配的“全能选手”

在 PHP 的字符串处理世界里,preg_match_all() 函数就像一位经验丰富的侦探,专门负责从一段文本中“抓出”所有符合特定模式的内容。如果你曾经需要从网页源码中提取所有链接、从日志文件中找出错误代码、或从用户输入中提取邮箱地址,那么这个函数就是你的最佳搭档。

相比 preg_match() 只能匹配第一个结果,preg_match_all() 的优势在于“全量捕获”——它能一次性找出所有匹配项,尤其适合处理结构化数据提取任务。本文将带你从零开始,深入理解这个函数的用法、参数结构、常见陷阱和实战技巧。


函数语法与参数详解

preg_match_all() 的基本语法如下:

int preg_match_all ( string $pattern , string $subject , array &$matches , int $flags = 0 , int $offset = 0 )

我们来逐个拆解参数含义:

  • $pattern:正则表达式模式,用斜杠 / 包裹,例如 /[a-z]+/ 表示匹配一个或多个小写字母。
  • $subject:要搜索的原始字符串,比如一段 HTML 或日志内容。
  • $matches:输出参数,函数执行后会将所有匹配结果存入这个数组中。
  • $flags:可选参数,控制匹配结果的返回格式,比如 PREG_SET_ORDERPREG_PATTERN_ORDER
  • $offset:可选参数,指定从字符串的哪个位置开始搜索。

💡 小贴士:正则表达式中的括号 () 用于分组,而 preg_match_all() 会根据分组自动组织结果数组的结构。


基础用法:从文本中提取所有单词

假设我们有一段英文句子,想找出其中所有的单词(只包含字母),可以这样写:

<?php
$text = "Hello world, this is a test sentence with some words.";

// 定义正则模式:匹配连续的字母(至少一个)
$pattern = '/[a-zA-Z]+/';

// 存储匹配结果的数组
$matches = [];

// 执行匹配
$result = preg_match_all($pattern, $text, $matches);

// 输出结果
echo "总共匹配到 " . $result . " 个单词\n";
print_r($matches[0]); // [0] 是第一组匹配结果

输出结果:

总共匹配到 9 个单词
Array
(
    [0] => Hello
    [1] => world
    [2] => this
    [3] => is
    [4] => a
    [5] => test
    [6] => sentence
    [7] => with
    [8] => some
    [9] => words
)

注释说明

  • /[a-zA-Z]+/[a-zA-Z] 表示任意字母,+ 表示至少一个,组合起来就是“连续字母”。
  • $matches[0]:因为没有使用分组,所以所有结果都存放在第一维数组中。
  • preg_match_all() 返回值是匹配次数,可用于判断是否找到内容。

分组匹配:提取结构化数据

当你的正则表达式中包含括号分组时,preg_match_all() 会返回多维数组。这在处理结构化信息时非常有用。

比如,我们有一段包含姓名和年龄的文本:

<?php
$data = "张三 25岁,李四 30岁,王五 28岁,赵六 33岁";

// 匹配姓名和年龄,用括号分组
$pattern = '/([a-zA-Z]+)\s+(\d+)岁/';

// 存储结果
$matches = [];

// 执行匹配
$result = preg_match_all($pattern, $data, $matches);

// 输出结果
echo "共找到 $result 条记录\n";

// 查看结果结构
print_r($matches);

输出结果:

共找到 4 条记录
Array
(
    [0] => Array
        (
            [0] => 张三 25岁
            [1] => 李四 30岁
            [2] => 王五 28岁
            [3] => 赵六 33岁
        )

    [1] => Array
        (
            [0] => 张三
            [1] => 李四
            [2] => 王五
            [3] => 赵六
        )

    [2] => Array
        (
            [0] => 25
            [1] => 30
            [2] => 28
            [3] => 33
        )
)

注释说明

  • ([a-zA-Z]+):第一组,匹配姓名。
  • \s+:匹配一个或多个空白字符(如空格)。
  • (\d+)岁:第二组,匹配数字和“岁”字。
  • $matches[0]:完整匹配结果。
  • $matches[1]:第一组(姓名)。
  • $matches[2]:第二组(年龄)。

这种结构非常适合后续处理,比如构建关联数组:

$people = [];
for ($i = 0; $i < $result; $i++) {
    $people[] = [
        'name' => $matches[1][$i],
        'age'  => (int)$matches[2][$i]
    ];
}

print_r($people);

使用 PREG_SET_ORDER 优化结果格式

默认情况下,preg_match_all() 的返回结果是“按组组织”的,也就是 $matches[1] 是所有第一组的结果。但有时我们更希望“按匹配项组织”,即每条结果是一个完整的数组。

这时可以使用 PREG_SET_ORDER 标志:

<?php
$data = "张三 25岁,李四 30岁,王五 28岁";

$pattern = '/([a-zA-Z]+)\s+(\d+)岁/';

$matches = [];

$result = preg_match_all($pattern, $data, $matches, PREG_SET_ORDER);

echo "共找到 $result 条记录\n";

// 每个元素是一个完整的匹配结果数组
foreach ($matches as $match) {
    echo "姓名: {$match[1]}, 年龄: {$match[2]}\n";
}

输出结果:

共找到 3 条记录
姓名: 张三, 年龄: 25
姓名: 李四, 年龄: 30
姓名: 王五, 年龄: 28

优势说明

  • PREG_SET_ORDER 让结果更直观,适合遍历处理。
  • 每个 $match 都是一个完整的匹配项,索引 0 是完整匹配,1 是第一组,2 是第二组,依此类推。

实战案例:从 HTML 中提取所有链接

假设你正在做网页爬虫,需要从一段 HTML 中提取所有 href 属性的链接。

<?php
$html = '
<a href="https://example.com">首页</a>
<a href="https://docs.example.com">文档</a>
<a href="/about.html">关于</a>
<div class="nav"><a href="https://blog.example.com">博客</a></div>
';

// 匹配 href 属性值,支持双引号或单引号
$pattern = '/href=["\']([^"\']+)["\']/i';

$matches = [];
$result = preg_match_all($pattern, $html, $matches, PREG_PATTERN_ORDER);

echo "共提取到 $result 个链接\n";

foreach ($matches[1] as $url) {
    echo "链接: $url\n";
}

输出结果:

共提取到 4 个链接
链接: https://example.com
链接: https://docs.example.com
链接: /about.html
链接: https://blog.example.com

注释说明

  • /href=["\']([^"\']+)["\']/:匹配 href= 后的引号,再捕获引号内的内容。
  • [^"\']:表示“非引号字符”,避免匹配到引号本身。
  • i 标志:忽略大小写(虽然 href 通常是小写,但保险起见)。
  • $matches[1]:第一组,即提取出的 URL。

常见错误与注意事项

  1. 正则表达式未正确转义
    如果你在模式中使用了特殊字符(如 .*?),需要使用反斜杠 \ 转义。例如匹配字面量 .,应写成 \.

  2. 使用了错误的分组顺序
    如果你有多个分组,一定要清楚 $matches[1]$matches[2] 对应的是哪一组。建议使用 PREG_SET_ORDER 以避免混淆。

  3. 忽略大小写问题
    对于文本匹配,特别是中文或英文混合内容,建议使用 i 标志,避免因大小写导致漏匹配。

  4. 性能问题
    复杂正则表达式可能影响性能,尤其在处理大文本时。建议提前测试,必要时使用更轻量的字符串函数。


总结与建议

PHP preg_match_all() 函数是处理复杂文本提取任务的利器,尤其在需要“找出所有匹配项”的场景中表现突出。通过合理使用分组和标志位,你可以灵活控制返回结果的结构,从而轻松应对各种实际需求。

  • 初学者建议从简单模式开始,如提取单词、邮箱、手机号。
  • 中级开发者可尝试解析 HTML、日志、JSON 等结构化文本。
  • 高级用户可结合 preg_replace() 实现内容替换与清洗。

掌握这个函数,等于在 PHP 工具箱里多了一把“精准的筛子”——不仅能筛出你想要的数据,还能帮你快速构建自动化处理流程。

无论你是写爬虫、做日志分析,还是处理用户输入,PHP preg_match_all() 函数都值得你深入学习与实践。多写、多试、多调试,你很快就能成为正则表达式高手。