PHP基础教程(77)PHP正则表达式介绍:【PHP正则通关秘籍】别再抓狂了!这可能是全网最“秃“的魔法指南,附送防脱发代码包!

一、欢迎来到“正则魔法学校”

那天深夜,我盯着屏幕上这行代码已经两个小时:

// 用户输入的电话号码,格式五花八门
$userInput = "我电话是138-0013-8000,微信同号哦~";

我需要提取电话号码。用strpos?用explode?试了十几种方法,写了50行代码,结果发现用户还可能输入“13800138000”、“138 0013 8000”、“+86-13800138000”……

就在我准备砸键盘时,前辈飘过来看了一眼:“用正则啊,一行搞定。”

正则表达式——这个听起来像数学课噩梦的名词,其实是程序员世界里的文字魔法。今天,就让我带你走进这个奇妙世界,保证不说“废话文学”,只讲“实战干货”!

二、魔法入门:正则表达式到底是什么鬼?

2.1 现实版“大家来找茬”

想象一下:你面前有1000份简历,要快速找出所有留了手机号的人。你会怎么找?

“找11位数字组合”——这就是最原始的正则思维!

在PHP中,正则表达式就是一套模式匹配规则,专门用来在字符串大海里捞针。更妙的是,这根“针”可以有弹性:可以是具体字符,也可以是某种“类型”的字符。

2.2 PHP的两种“魔法杖”

PHP给了我们两根魔法杖,各有特色:

// 第一根:PCRE魔法杖(推荐使用)
$pattern = '/\d{3}-\d{4}-\d{4}/';  // 看起来像颜文字,其实是正经模式
$result = preg_match($pattern, $string);

// 第二根:POSIX魔法杖(已过时,了解一下就行)
$result = ereg($pattern, $string);  // PHP 7.0+ 已经移除了

简单来说:PCRE功能更强、速度更快,是我们今天的主角

三、基础咒语:从零开始写模式

3.1 定界符——魔法的起手式

写正则的第一条规则:用分隔符包起来,通常用/,就像魔法阵的画框:

$pattern = '/abc/';  // 寻找字符串中的"abc"

但如果你要找的内容包含/怎么办?这时候可以换其他符号,我个人的选择习惯是:

$pattern = '#https?://#i';  // 匹配http://或https://,用#号做分隔符
$pattern = '~/\d+~';        // 匹配斜杠加数字,用~号

3.2 元字符——魔法世界的特殊符号

这些是正则的“关键字”,需要背下来(别怕,就几个):

// 最常用的几个元字符
$pattern = '/^abc$/';     // ^开头,$结尾,表示完全匹配"abc"
$pattern = '/a.c/';       // .匹配任意单个字符(除了换行)
$pattern = '/ab*c/';      // *前面的b出现0次或多次
$pattern = '/ab+c/';      // +前面的b出现1次或多次
$pattern = '/ab?c/';      // ?前面的b出现0次或1次
$pattern = '/a{2,4}/';    // a出现2到4次

3.3 字符类——你的“通配符卡”

// 匹配数字
$pattern = '/\d/';        // 匹配一个数字,等同于[0-9]
$pattern = '/\D/';        // 匹配非数字

// 匹配单词字符(字母、数字、下划线)
$pattern = '/\w/';        // 等同于[a-zA-Z0-9_]
$pattern = '/\W/';        // 非单词字符

// 匹配空白字符
$pattern = '/\s/';        // 空格、制表符、换行等
$pattern = '/\S/';        // 非空白字符

// 自定义字符类
$pattern = '/[aeiou]/';   // 匹配任何一个元音字母
$pattern = '/[^aeiou]/';  // ^在[]内表示“非”,匹配非元音字母
$pattern = '/[a-z]/i';    // i修饰符表示不区分大小写

四、实战演练:7个真实场景代码示例

示例1:验证邮箱格式(经典面试题)

function validateEmail($email) {
    $pattern = '/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/';
    
    if (preg_match($pattern, $email)) {
        return "邮箱格式正确!🎉";
    } else {
        return "兄弟,你这邮箱有点不对劲啊~";
    }
}

// 测试一下
echo validateEmail("hello@example.com");  // 正确
echo validateEmail("test@com");           // 错误(域名太短)
echo validateEmail("@example.com");       // 错误(没用户名)

注意:这个正则能覆盖90%的情况,但100%完美的邮箱正则不存在(RFC标准太复杂)。实际项目中可以考虑用filter_var($email, FILTER_VALIDATE_EMAIL)

示例2:提取文章中的所有图片链接

$article = "
    <p>看看我拍的照片:</p>
    <img src='https://example.com/img1.jpg' alt='美景'>
    <img src='/uploads/img2.png' width='100'>
    <div>这里是文字内容</div>
    <img src='img3.gif'>
";

$pattern = '/<img\s+[^>]*src=[\'"]([^\'"]+)[\'"][^>]*>/i';
preg_match_all($pattern, $article, $matches);

echo "找到" . count($matches[1]) . "张图片:\n";
print_r($matches[1]);
/*
输出:
Array
(
    [0] => https://example.com/img1.jpg
    [1] => /uploads/img2.png
    [2] => img3.gif
)
*/

示例3:密码强度验证

function checkPasswordStrength($password) {
    $errors = [];
    
    // 至少8位
    if (!preg_match('/^.{8,}$/', $password)) {
        $errors[] = "密码至少8位";
    }
    
    // 包含大小写字母
    if (!preg_match('/[a-z]/', $password) || !preg_match('/[A-Z]/', $password)) {
        $errors[] = "需要同时包含大小写字母";
    }
    
    // 包含数字
    if (!preg_match('/\d/', $password)) {
        $errors[] = "至少包含一个数字";
    }
    
    // 包含特殊字符
    if (!preg_match('/[!@#$%^&*(),.?":{}|<>]/', $password)) {
        $errors[] = "至少一个特殊字符";
    }
    
    return empty($errors) ? "密码强度💪" : implode(",", $errors);
}

echo checkPasswordStrength("Weak");      // 一堆错误
echo checkPasswordStrength("StrongP@ss1"); // 密码强度💪

示例4:智能提取手机号(应对各种格式)

function extractPhoneNumbers($text) {
    // 匹配11位手机号,允许空格、横杠分隔
    $pattern = '/(?:\+86)?[-\s]?1[3-9]\d[-\s]?\d{4}[-\s]?\d{4}/';
    
    preg_match_all($pattern, $text, $matches);
    
    // 清理格式:移除所有非数字字符(除了开头的+86)
    $phones = [];
    foreach ($matches[0] as $phone) {
        // 保留+86,其他只留数字
        if (strpos($phone, '+86') === 0) {
            $cleaned = '+86' . preg_replace('/\D/', '', substr($phone, 3));
        } else {
            $cleaned = preg_replace('/\D/', '', $phone);
        }
        $phones[] = $cleaned;
    }
    
    return array_unique($phones); // 去重
}

$testText = "
    联系方式:
    王经理:138-0013-8000
    李总:139 1234 5678  
    客服:+86-156-8888-9999
    诈骗电话:191-9988-7766(虚拟运营商)
";

print_r(extractPhoneNumbers($testText));

示例5:解析简单JSON(正则不是万能的,但有时很管用)

// 注意:对于完整的JSON,请用json_decode()!
// 这里只是展示正则的能力边界

$miniJson = '{"name": "张三", "age": 25, "city": "北京"}';

// 提取所有键值对
$pattern = '/"([^"]+)":\s*("[^"]*"|\d+|true|false|null)/';
preg_match_all($pattern, $miniJson, $matches, PREG_SET_ORDER);

foreach ($matches as $match) {
    echo "键: {$match[1]}, 值: {$match[2]}\n";
}

示例6:Markdown标题转HTML

function markdownToHtml($markdown) {
    // 转换#标题
    $html = preg_replace('/^#\s+(.+)$/m', '<h1>$1</h1>', $markdown);
    $html = preg_replace('/^##\s+(.+)$/m', '<h2>$1</h2>', $html);
    $html = preg_replace('/^###\s+(.+)$/m', '<h3>$1</h3>', $html);
    
    // 转换**粗体**
    $html = preg_replace('/\*\*(.+?)\*\*/', '<strong>$1</strong>', $html);
    
    // 转换*斜体*
    $html = preg_replace('/\*(.+?)\*/', '<em>$1</em>', $html);
    
    return $html;
}

$md = "# 主标题\n这是内容**加粗文字**和*斜体*。\n## 子标题";
echo markdownToHtml($md);

示例7:清理用户输入的XSS攻击

function safeInput($input) {
    // 移除<script>标签及其内容
    $clean = preg_replace('/<script\b[^>]*>(.*?)<\/script>/is', '', $input);
    
    // 移除on事件属性(如onclick、onload等)
    $clean = preg_replace('/\s+on\w+\s*=\s*["\'][^"\']*["\']/i', '', $clean);
    
    // 移除javascript:协议链接
    $clean = preg_replace('/href=["\']\s*javascript:/i', 'href="#"', $clean);
    
    return htmlspecialchars($clean, ENT_QUOTES, 'UTF-8');
}

$dangerInput = '<script>alert("哈哈")</script><a href="javascript:evil()">点我</a>';
echo safeInput($dangerInput); // 安全的内容

五、高级技巧:从“会用”到“精通”

5.1 分组捕获——魔法中的魔法

// 提取日期各部分
$date = "2023-12-25";
$pattern = '/(\d{4})-(\d{2})-(\d{2})/';

preg_match($pattern, $date, $matches);
echo "年: {$matches[1]}, 月: {$matches[2]}, 日: {$matches[3]}";
// 输出:年: 2023, 月: 12, 日: 25

// 命名分组(更清晰)
$pattern = '/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/';
preg_match($pattern, $date, $matches);
echo $matches['year']; // 2023

5.2 贪婪 vs 非贪婪匹配

这是正则最容易踩的坑!

$html = '<div>标题</div><div>内容</div>';

// 贪婪匹配(默认):尽可能多吃
preg_match('/<div>.*<\/div>/', $html, $match);
echo $match[0]; // 匹配整个字符串:<div>标题</div><div>内容</div>
// 非贪婪匹配:吃到不饿就行
preg_match('/<div>.*?<\/div>/', $html, $match);
echo $match[0]; // 只匹配第一个:<div>标题</div>

记忆口诀*? +? ?? {n,}?——加问号变“谦让”。

5.3 零宽断言——正则里的“透视眼”

// 找后面是.com的域名
$text = "example.com example.net example.org";
$pattern = '/\w+(?=\.com)/';  // 正向肯定预查
preg_match_all($pattern, $text, $matches);
print_r($matches[0]); // Array([0] => example)

// 找后面不是.com的域名
$pattern = '/\w+(?!\.com)/';  // 正向否定预查
preg_match_all($pattern, $text, $matches);
print_r($matches[0]); // 包含example(来自.net和.org)

// 找前面是"域名:"的单词
$text = "域名:baidu.com 域名:google.com";
$pattern = '/(?<=域名:)\w+/';  // 反向肯定预查
preg_match_all($pattern, $text, $matches);
print_r($matches[0]); // Array([0] => baidu [1] => google)

六、性能优化与调试技巧

6.1 正则性能陷阱

// 灾难性的回溯(Catastrophic Backtracking)
$badPattern = '/(a+)+b/';  // 多个a后面跟b
$testString = str_repeat('a', 100) . 'c'; // 100个a加c,没有b

// 这会卡很久!因为正则引擎会尝试所有可能的a的组合
// preg_match($badPattern, $testString); // 慎运行!

// 优化方案:避免嵌套的量词
$goodPattern = '/a+b/';  // 简单直接

6.2 调试技巧

// 1. 先用简单模式测试
$pattern = '/\d+/'; // 先测试是否匹配数字

// 2. 分步构建复杂模式
$pattern = '/^';          // 开头
$pattern .= '\d{3,4}';    // 区号
$pattern .= '-\d{7,8}';   // 号码
$pattern .= '$/';         // 结尾

// 3. 使用在线工具验证(如 regex101.com)

// 4. PHP的错误信息
$result = @preg_match('/invalid (pattern/', 'test');
if (preg_last_error() !== PREG_NO_ERROR) {
    echo "正则错误: " . preg_last_error_msg();
    // 输出:正则错误: Compilation failed: missing ) at offset 15
}

七、常见坑点与解决方案

坑点1:中文匹配问题

// 错误做法:用\w匹配中文
$pattern = '/\w+/u'; // 加u修饰符,\w能匹配中文吗?不能!

// 正确做法:用Unicode属性或范围
$pattern = '/[\x{4e00}-\x{9fa5}]+/u';  // 匹配中文字符
$pattern = '/\p{Han}+/u';              // 匹配汉字(需要PCRE>=8.10)

$text = "Hello 世界!";
preg_match_all('/[\x{4e00}-\x{9fa5}]/u', $text, $matches);
print_r($matches[0]); // Array([0] => 世 [1] => 界)

坑点2:多行匹配模式

$text = "第一行\n第二行\n第三行";

// m修饰符:^和$匹配每行的开头结尾
preg_match_all('/^第.行$/m', $text, $matches);
print_r($matches[0]); // 匹配所有三行

// s修饰符:让.匹配换行符
$multiline = "<div>\n内容\n</div>";
preg_match('/<div>(.*)<\/div>/s', $multiline, $match);
echo $match[1]; // 包含换行符的"内容"

坑点3:特殊字符转义

// 错误:直接使用用户输入作为模式
$userSearch = $_GET['q']; // 用户可能输入"test.+*"
$badPattern = "/$userSearch/"; // 如果用户输入包含正则元字符就崩了

// 正确:用preg_quote转义
$safePattern = '/' . preg_quote($userSearch, '/') . '/';

八、综合实战:一个完整的表单验证类

class FormValidator {
    public static function validateEmail($email) {
        $pattern = '/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/';
        return preg_match($pattern, $email);
    }
    
    public static function validateChineseName($name) {
        // 2-4个中文字符
        $pattern = '/^[\x{4e00}-\x{9fa5}]{2,4}$/u';
        return preg_match($pattern, $name);
    }
    
    public static function validateIDCard($id) {
        // 简单版身份证验证(15位或18位)
        $pattern = '/^(\d{15}$|^\d{18}$|\d{17}(\d|X|x))$/';
        return preg_match($pattern, $id);
    }
    
    public static function validateBankCard($card) {
        // 银行卡号(16-19位数字)
        $pattern = '/^\d{16,19}$/';
        return preg_match($pattern, $card);
    }
    
    public static function extractLinks($text) {
        // 提取URL链接
        $pattern = '/https?:\/\/[^\s"\'<>]+/';
        preg_match_all($pattern, $text, $matches);
        return $matches[0] ?? [];
    }
    
    public static function removeEmoji($text) {
        // 移除emoji表情
        $pattern = '/[\x{1F600}-\x{1F64F}\x{1F300}-\x{1F5FF}\x{1F680}-\x{1F6FF}]/u';
        return preg_replace($pattern, '', $text);
    }
}

// 使用示例
$data = [
    'email' => 'test@example.com',
    'name' => '张三',
    'content' => '欢迎访问 https://example.com 👍'
];

if (!FormValidator::validateEmail($data['email'])) {
    echo "邮箱格式错误";
}

$cleanContent = FormValidator::removeEmoji($data['content']);
$links = FormValidator::extractLinks($data['content']);

九、写在最后:正则学习的三个阶段

阶段一:看正则如天书(第一周)

  • 感受:“这写的什么鬼?”
  • 策略:抄! 先找现成模式,理解每个部分的意思

阶段二:能写简单模式(第一个月)

  • 感受:“好像有点懂了”
  • 策略:练! 从验证手机号、邮箱开始,每天写3个

阶段三:游刃有余(半年后)

  • 感受:“这个问题可以用正则解决”
  • 策略:优化! 考虑性能、可读性、边缘情况

最后的最后

正则表达式就像编程世界里的瑞士军刀——不是每天用,但用的时候真香。关键是:

  1. 别怕写错——我写了5年正则,依然要测试
  2. 别追求完美——能解决80%问题就行,剩下20%用其他方法
  3. 多用工具——regex101.com是你的好朋友
  4. 保持幽默——当正则让你抓狂时,记得这行代码:
// 传说中的“匹配一切”
$pattern = '//';  // 其实匹配空字符串
// 真正的“匹配任何字符”是:'/.?/s'

好了,现在你已经是“正则魔法学校”的合格毕业生了。下次看到奇怪的字符串,别再手动substrstrpos组合拳了,试试正则,你会发现——真香!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

值引力

持续创作,多谢支持!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值