PHP PCRE

Regex functions, modifiers, common patterns, and catastrophic backtracking.

Intermediate
Key PointsRegex checks shape; it cannot parse HTML or nested structures.

Functions

FunctionReturns
preg_match匹配次数 0/1
preg_match_all全部匹配
preg_replace替换后的字符串
preg_replace_callback用回调生成替换内容
preg_split按模式切分
preg_grep筛选数组中匹配的元素
preg_quote转义正则元字符
preg_last_error最后一次错误码

Basics

PHP
<?php
var_dump(preg_match('/^\d{3}-\d{4}$/', "123-4567"));
var_dump(preg_match('/^\d{3}-\d{4}$/', "12-4567"));

preg_match('/(\d{4})-(\d{2})-(\d{2})/', "2023-11-15", $m);
print_r($m);

print_r(preg_split('/[,\s]+/', "a, b   c,d"));

echo preg_replace('/\s+/', " ", "a   b    c"), "\n";
echo preg_quote("a.b*c", "/"), "\n";
Output
int(1)
int(0)
Array
(
    [0] => 2023-11-15
    [1] => 2023
    [2] => 11
    [3] => 15
)
Array
(
    [0] => a
    [1] => b
    [2] => c
    [3] => d
)
a b c
a\.b\*c

Modifiers

ModifierEffect
i忽略大小写
m多行:^ $ 匹配每行首尾
s. 匹配换行
uUTF-8 模式(处理中文必加)
x忽略空白,可写注释
U量词默认变懒惰
D$ 只匹配结尾,不匹配末尾换行前
PHP
<?php
echo preg_match('/abc/i', "ABC") ? "忽略大小写命中" : "未命中", "\n";
echo preg_match('/^b/m', "a\nb") ? "多行命中" : "未命中", "\n";
echo preg_match('/a.b/s', "a\nb") ? "跨行命中" : "未命中", "\n";

// 中文:不加 /u 会按字节匹配
echo preg_match('/^\p{Han}+$/u', "中文") ? "是纯中文" : "不是", "\n";
echo preg_match('/^\p{Han}+$/', "中文") ? "是纯中文" : "不是", "\n";
Output
忽略大小写命中
多行命中
跨行命中
是纯中文
不是
\p{Han} and \w behave entirely differently with /u. Without it PCRE works on bytes and \w matches ASCII only.

Common patterns

PatternMatches
/^1[3-9]\d{9}$/中国大陆手机号
/^[\w.+-]+@[\w-]+\.[\w.]+$/邮箱(简化版)
/^\d{17}[\dXx]$/18 位身份证
/^[a-zA-Z0-9_]{6,18}$/用户名
/^\d{4}-\d{2}-\d{2}$/日期形状
/^#[0-9a-fA-F]{6}$/十六进制颜色

Named groups and callbacks

PHP
<?php
preg_match('/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/', "2023-11-15", $m);
echo $m["year"] . "/" . $m["month"] . "/" . $m["day"], "\n";

$out = preg_replace_callback(
    '/\[(\w+)\]/',
    fn($m) => strtoupper($m[1]),
    "hello [world] and [php]"
);
echo $out, "\n";

// 反向引用
echo preg_replace('/(\w+) \1/', "$1", "the the cat"), "\n";
Output
2023/11/15
hello WORLD and PHP
the cat

Catastrophic backtracking

Nested quantifiers make attempts grow exponentially. (a+)+b against a long run of a pins the CPU.

PHP
<?php
$bad = '/(a+)+b/';
$input = str_repeat("a", 26) . "c";

$start = microtime(true);
$r = preg_match($bad, $input);
$ms = round((microtime(true) - $start) * 1000, 1);

echo $r === 0 ? "未命中" : "命中", "\n";
echo "耗时超过 " . ($ms > 100 ? "100ms(危险)" : "100ms(可接受)"), "\n";
echo preg_last_error() === PREG_BACKTRACK_LIMIT_ERROR ? "触发回溯上限" : "未触发上限", "\n";
Output
未命中
耗时超过 100ms(危险)
触发回溯上限
User-controlled input feeding a regex is a DoS vector. Limit input length, lower pcre.backtrack_limit, or rewrite the pattern.

When not to use regex

  • Parsing HTML/XML —— use DOM; regex cannot handle nesting
  • Parsing JSON —— use json_decode
  • Validating email/URL —— use filter_var
  • Simple substring checks —— str_contains is far faster

Friends & Partners