PHP正则表达式实战示例代码大全从基础入门到高级应用助你快速掌握文本处理与数据验证的核心技巧
1. 正则表达式基础概念
正则表达式(Regular Expression)是一种强大的文本处理工具,它使用特定的模式来描述、匹配一系列符合某个句法规则的字符串。在PHP中,正则表达式被广泛应用于表单验证、数据提取、文本替换等场景。
1.1 正则表达式的基本组成
正则表达式由普通字符(如字母a-z)和特殊字符(称为”元字符”)组成。以下是一些基本的元字符:
.
:匹配除换行符外的任意单个字符*
:匹配前面的子表达式零次或多次+
:匹配前面的子表达式一次或多次?
:匹配前面的子表达式零次或一次^
:匹配字符串的开始位置$
:匹配字符串的结束位置[]
:字符类,匹配方括号中的任意一个字符[^]
:否定字符类,匹配除了方括号中字符的任意字符|
:选择,匹配|左右任意一个表达式()
:分组,将括号内的表达式作为一个整体{n}
:精确匹配n次{n,}
:至少匹配n次{n,m}
:匹配n到m次
1.2 PHP中的正则表达式函数
PHP提供了两组正则表达式函数:PCRE(Perl Compatible Regular Expressions)和POSIX扩展。PCRE函数更强大且常用,主要包括:
preg_match()
:执行一个正则表达式匹配preg_match_all()
:执行一个全局正则表达式匹配preg_replace()
:执行一个正则表达式的搜索和替换preg_filter()
:执行一个正则表达式搜索和替换preg_split()
:通过一个正则表达式分隔字符串preg_grep()
:返回匹配模式的数组条目preg_quote()
:转义正则表达式字符
2. 基础模式匹配
2.1 简单字符串匹配
<?php // 检查字符串中是否包含"hello" $pattern = '/hello/'; $string = 'hello world'; if (preg_match($pattern, $string)) { echo "匹配成功!"; } else { echo "匹配失败!"; } ?>
2.2 不区分大小写的匹配
<?php // 使用i修饰符进行不区分大小写的匹配 $pattern = '/hello/i'; $string = 'Hello World'; if (preg_match($pattern, $string)) { echo "匹配成功!"; } else { echo "匹配失败!"; } ?>
2.3 匹配字符串开始或结束位置
<?php // 匹配以"hello"开头的字符串 $pattern = '/^hello/'; $string = 'hello world'; if (preg_match($pattern, $string)) { echo "匹配成功!"; } else { echo "匹配失败!"; } // 匹配以"world"结尾的字符串 $pattern = '/world$/'; $string = 'hello world'; if (preg_match($pattern, $string)) { echo "匹配成功!"; } else { echo "匹配失败!"; } ?>
2.4 使用字符类
<?php // 匹配任意一个元音字母 $pattern = '/[aeiou]/'; $string = 'hello world'; if (preg_match($pattern, $string)) { echo "匹配成功!"; } else { echo "匹配失败!"; } // 匹配任意一个数字 $pattern = '/[0-9]/'; $string = 'hello 123 world'; if (preg_match($pattern, $string)) { echo "匹配成功!"; } else { echo "匹配失败!"; } // 匹配任意一个非元音字母 $pattern = '/[^aeiou]/'; $string = 'hello world'; if (preg_match($pattern, $string)) { echo "匹配成功!"; } else { echo "匹配失败!"; } ?>
2.5 使用预定义字符类
<?php // d 匹配任意一个数字,相当于[0-9] $pattern = '/d/'; $string = 'hello 123 world'; if (preg_match($pattern, $string)) { echo "匹配成功!"; } else { echo "匹配失败!"; } // D 匹配任意一个非数字字符,相当于[^0-9] $pattern = '/D/'; $string = '123'; if (preg_match($pattern, $string)) { echo "匹配成功!"; } else { echo "匹配失败!"; } // w 匹配任意一个单词字符(字母、数字、下划线),相当于[a-zA-Z0-9_] $pattern = '/w/'; $string = '!@#$%'; if (preg_match($pattern, $string)) { echo "匹配成功!"; } else { echo "匹配失败!"; } // W 匹配任意一个非单词字符,相当于[^a-zA-Z0-9_] $pattern = '/W/'; $string = 'hello'; if (preg_match($pattern, $string)) { echo "匹配成功!"; } else { echo "匹配失败!"; } // s 匹配任意一个空白字符(空格、制表符、换行符等) $pattern = '/s/'; $string = 'hello world'; if (preg_match($pattern, $string)) { echo "匹配成功!"; } else { echo "匹配失败!"; } // S 匹配任意一个非空白字符 $pattern = '/S/'; $string = ' '; if (preg_match($pattern, $string)) { echo "匹配成功!"; } else { echo "匹配失败!"; } ?>
3. 量词与重复模式
3.1 基本量词
<?php // * 匹配前面的元素零次或多次 $pattern = '/ab*c/'; // 匹配ac、abc、abbc、abbbc等 $string = 'abbbc'; if (preg_match($pattern, $string)) { echo "匹配成功!"; } else { echo "匹配失败!"; } // + 匹配前面的元素一次或多次 $pattern = '/ab+c/'; // 匹配abc、abbc、abbbc等,但不匹配ac $string = 'abc'; if (preg_match($pattern, $string)) { echo "匹配成功!"; } else { echo "匹配失败!"; } // ? 匹配前面的元素零次或一次 $pattern = '/ab?c/'; // 匹配ac或abc $string = 'ac'; if (preg_match($pattern, $string)) { echo "匹配成功!"; } else { echo "匹配失败!"; } ?>
3.2 指定次数的量词
<?php // {n} 精确匹配n次 $pattern = '/ab{3}c/'; // 匹配abbbc $string = 'abbbc'; if (preg_match($pattern, $string)) { echo "匹配成功!"; } else { echo "匹配失败!"; } // {n,} 至少匹配n次 $pattern = '/ab{2,}c/'; // 匹配abbc、abbbc、abbbbc等 $string = 'abbbc'; if (preg_match($pattern, $string)) { echo "匹配成功!"; } else { echo "匹配失败!"; } // {n,m} 匹配n到m次 $pattern = '/ab{2,4}c/'; // 匹配abbc、abbbc、abbbbc $string = 'abbbc'; if (preg_match($pattern, $string)) { echo "匹配成功!"; } else { echo "匹配失败!"; } ?>
3.3 贪婪、懒惰与占有量词
<?php // 贪婪量词(默认):尽可能多地匹配 $pattern = '/<.*>/'; $string = '<div>hello</div>'; preg_match($pattern, $string, $matches); echo "贪婪匹配结果: " . $matches[0] . "n"; // 输出: <div>hello</div> // 懒惰量词(非贪婪):尽可能少地匹配 $pattern = '/<.*?>/'; $string = '<div>hello</div>'; preg_match($pattern, $string, $matches); echo "懒惰匹配结果: " . $matches[0] . "n"; // 输出: <div> // 占有量词:尽可能多地匹配,但不回溯 $pattern = '/<.*+>/'; $string = '<div>hello</div>'; if (preg_match($pattern, $string, $matches)) { echo "占有匹配结果: " . $matches[0] . "n"; } else { echo "占有匹配失败!n"; // 输出: 占有匹配失败! } ?>
4. 分组与捕获
4.1 基本分组
<?php // 使用()创建分组 $pattern = '/(ab)+/'; $string = 'ababab'; if (preg_match($pattern, $string, $matches)) { echo "完整匹配: " . $matches[0] . "n"; // 输出: ababab echo "分组1: " . $matches[1] . "n"; // 输出: ab } ?>
4.2 非捕获分组
<?php // 使用(?:)创建非捕获分组 $pattern = '/(?:ab)+/'; $string = 'ababab'; if (preg_match($pattern, $string, $matches)) { echo "完整匹配: " . $matches[0] . "n"; // 输出: ababab // 非捕获分组不会出现在$matches数组中 echo "分组数量: " . count($matches) . "n"; // 输出: 1 } ?>
4.3 命名捕获分组
<?php // 使用(?P<name>)创建命名捕获分组 $pattern = '/(?P<year>d{4})-(?P<month>d{2})-(?P<day>d{2})/'; $string = '2023-07-15'; if (preg_match($pattern, $string, $matches)) { echo "完整匹配: " . $matches[0] . "n"; // 输出: 2023-07-15 echo "年份: " . $matches['year'] . "n"; // 输出: 2023 echo "月份: " . $matches['month'] . "n"; // 输出: 07 echo "日期: " . $matches['day'] . "n"; // 输出: 15 } ?>
4.4 反向引用
<?php // 使用1、2等引用前面的捕获分组 $pattern = '/(w+)s+1/'; $string = 'hello hello'; if (preg_match($pattern, $string, $matches)) { echo "匹配成功: " . $matches[0] . "n"; // 输出: hello hello echo "分组1: " . $matches[1] . "n"; // 输出: hello } // 使用命名反向引用 $pattern = '/(?P<word>w+)s+(?P=word)/'; $string = 'world world'; if (preg_match($pattern, $string, $matches)) { echo "匹配成功: " . $matches[0] . "n"; // 输出: world world echo "单词: " . $matches['word'] . "n"; // 输出: world } ?>
5. 常见的数据验证示例
5.1 验证电子邮件地址
<?php function validateEmail($email) { $pattern = '/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$/'; return preg_match($pattern, $email) === 1; } // 测试 $emails = [ 'user@example.com', 'user.name@example.com', 'user-name@example.co.uk', 'user123@example123.com', 'user@.com', 'user@example', 'user.example.com', '@example.com' ]; foreach ($emails as $email) { echo $email . " 是" . (validateEmail($email) ? "有效的" : "无效的") . "电子邮件地址n"; } ?>
5.2 验证电话号码
<?php function validatePhoneNumber($phone) { // 支持多种格式:123-456-7890, (123) 456-7890, 123.456.7890, 1234567890 $pattern = '/^(+d{1,2}s?)?((d{3})|d{3})[s.-]?d{3}[s.-]?d{4}$/'; return preg_match($pattern, $phone) === 1; } // 测试 $phones = [ '123-456-7890', '(123) 456-7890', '123.456.7890', '1234567890', '+1 123-456-7890', '123-456-789', '12-345-67890', 'abc-def-ghij' ]; foreach ($phones as $phone) { echo $phone . " 是" . (validatePhoneNumber($phone) ? "有效的" : "无效的") . "电话号码n"; } ?>
5.3 验证URL
<?php function validateUrl($url) { $pattern = '/^(https?://)?([da-z.-]+).([a-z.]{2,6})([/w .-]*)*/?$/'; return preg_match($pattern, $url) === 1; } // 测试 $urls = [ 'http://example.com', 'https://example.com', 'www.example.com', 'example.com', 'http://example.com/path', 'http://example.com/path?query=value', 'http://example.com/path#section', 'ftp://example.com', 'http://.com', 'http://example.' ]; foreach ($urls as $url) { echo $url . " 是" . (validateUrl($url) ? "有效的" : "无效的") . "URLn"; } ?>
5.4 验证IP地址
<?php function validateIPv4($ip) { $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_match($pattern, $ip) === 1; } function validateIPv6($ip) { $pattern = '/^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/'; return preg_match($pattern, $ip) === 1; } // 测试IPv4 $ipv4s = [ '192.168.1.1', '255.255.255.255', '0.0.0.0', '256.1.2.3', '1.2.3', '1.2.3.4.5' ]; echo "IPv4验证:n"; foreach ($ipv4s as $ip) { echo $ip . " 是" . (validateIPv4($ip) ? "有效的" : "无效的") . "IPv4地址n"; } // 测试IPv6 $ipv6s = [ '2001:0db8:85a3:0000:0000:8a2e:0370:7334', '2001:db8:85a3:0:0:8a2e:370:7334', '2001:db8:85a3::8a2e:370:7334', '::1', '::', '2001::25de::cade', '2001:0db8:85a3:0000:0000:8a2e:0370:7334:extra' ]; echo "nIPv6验证:n"; foreach ($ipv6s as $ip) { echo $ip . " 是" . (validateIPv6($ip) ? "有效的" : "无效的") . "IPv6地址n"; } ?>
5.5 验证日期格式
<?php function validateDate($date, $format = 'YYYY-MM-DD') { switch ($format) { case 'YYYY-MM-DD': $pattern = '/^d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/'; break; case 'MM/DD/YYYY': $pattern = '/^(0[1-9]|1[0-2])/(0[1-9]|[12][0-9]|3[01])/d{4}$/'; break; case 'DD-MM-YYYY': $pattern = '/^(0[1-9]|[12][0-9]|3[01])-(0[1-9]|1[0-2])-d{4}$/'; break; default: return false; } if (preg_match($pattern, $date, $matches)) { // 进一步验证日期的有效性(例如,避免2月30日这样的无效日期) if ($format === 'YYYY-MM-DD') { list($year, $month, $day) = explode('-', $date); } elseif ($format === 'MM/DD/YYYY') { list($month, $day, $year) = explode('/', $date); } elseif ($format === 'DD-MM-YYYY') { list($day, $month, $year) = explode('-', $date); } return checkdate($month, $day, $year); } return false; } // 测试 $dates = [ ['2023-07-15', 'YYYY-MM-DD'], ['2023-02-28', 'YYYY-MM-DD'], ['2023-02-29', 'YYYY-MM-DD'], // 2023年不是闰年,2月没有29日 ['2024-02-29', 'YYYY-MM-DD'], // 2024年是闰年,2月有29日 ['07/15/2023', 'MM/DD/YYYY'], ['02/30/2023', 'MM/DD/YYYY'], // 无效日期 ['15-07-2023', 'DD-MM-YYYY'], ['30-02-2023', 'DD-MM-YYYY'] // 无效日期 ]; foreach ($dates as $date) { echo $date[0] . " (格式: " . $date[1] . ") 是" . (validateDate($date[0], $date[1]) ? "有效的" : "无效的") . "日期n"; } ?>
5.6 验证密码强度
<?php function validatePasswordStrength($password, $minLength = 8) { // 至少$minLength个字符 $lengthCheck = strlen($password) >= $minLength; // 包含至少一个大写字母 $uppercaseCheck = preg_match('/[A-Z]/', $password); // 包含至少一个小写字母 $lowercaseCheck = preg_match('/[a-z]/', $password); // 包含至少一个数字 $numberCheck = preg_match('/[0-9]/', $password); // 包含至少一个特殊字符 $specialCharCheck = preg_match('/[!@#$%^&*()-_=+{};:,<.>]/', $password); return $lengthCheck && $uppercaseCheck && $lowercaseCheck && $numberCheck && $specialCharCheck; } // 测试 $passwords = [ 'Password123!', 'weak', 'onlylowercase', 'ONLYUPPERCASE', 'NoSpecialChars123', '12345678', '!@#$%^&*', 'Short1!', 'GoodPassword123!' ]; foreach ($passwords as $password) { echo $password . " 是" . (validatePasswordStrength($password) ? "强的" : "弱的") . "密码n"; } ?>
6. 文本处理技巧
6.1 提取所有电子邮件地址
<?php function extractEmails($text) { $pattern = '/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}/'; preg_match_all($pattern, $text, $matches); return $matches[0]; } $text = "请联系我们:support@example.com 或 sales@example.org。如果您有任何问题,也可以发送邮件到info@example.com或contact@example.co.uk。"; $emails = extractEmails($text); echo "从文本中提取的电子邮件地址:n"; print_r($emails); ?>
6.2 提取所有URL
<?php function extractUrls($text) { $pattern = '/https?://[^s]+/'; preg_match_all($pattern, $text, $matches); return $matches[0]; } $text = "访问我们的网站:https://www.example.com 了解更多信息。你也可以查看我们的博客:https://blog.example.com/latest-posts 和我们的GitHub页面:https://github.com/example/project。"; $urls = extractUrls($text); echo "从文本中提取的URL:n"; print_r($urls); ?>
6.3 移除HTML标签
<?php function stripHtmlTags($html) { $pattern = '/<[^>]*>/'; return preg_replace($pattern, '', $html); } $html = "<div class='content'><h1>标题</h1><p>这是一个<b>示例</b>文本。</p></div>"; $plainText = stripHtmlTags($html); echo "原始HTML:n" . $html . "nn"; echo "移除HTML标签后的文本:n" . $plainText . "n"; ?>
6.4 高亮搜索关键词
<?php function highlightKeywords($text, $keywords) { foreach ($keywords as $keyword) { $pattern = '/(' . preg_quote($keyword, '/') . ')/i'; $replacement = '<span class="highlight">$1</span>'; $text = preg_replace($pattern, $replacement, $text); } return $text; } $text = "PHP是一种广泛使用的服务器端脚本语言,特别适合Web开发。PHP可以嵌入HTML中,也可以用于命令行脚本和桌面应用程序。"; $keywords = ["PHP", "Web", "HTML"]; $highlightedText = highlightKeywords($text, $keywords); echo "高亮关键词后的文本:n" . $highlightedText . "n"; ?>
6.5 格式化电话号码
<?php function formatPhoneNumber($phone) { // 移除所有非数字字符 $digits = preg_replace('/D/', '', $phone); // 根据数字长度应用不同的格式 if (strlen($digits) === 10) { // 格式: (123) 456-7890 return preg_replace('/(d{3})(d{3})(d{4})/', '($1) $2-$3', $digits); } elseif (strlen($digits) === 11 && $digits[0] === '1') { // 格式: 1 (123) 456-7890 return preg_replace('/1(d{3})(d{3})(d{4})/', '1 ($1) $2-$3', $digits); } else { // 无法识别的格式,返回原始字符串 return $phone; } } $phones = [ '1234567890', '11234567890', '123-456-7890', '(123) 456-7890', '1 (123) 456-7890', '123.456.7890', '+1 123-456-7890' ]; foreach ($phones as $phone) { echo "原始: " . $phone . " -> 格式化: " . formatPhoneNumber($phone) . "n"; } ?>
6.6 验证和格式化信用卡号
<?php function validateAndFormatCreditCard($cardNumber) { // 移除所有非数字字符 $digits = preg_replace('/D/', '', $cardNumber); // 检查长度是否在13到19位之间 if (strlen($digits) < 13 || strlen($digits) > 19) { return false; } // 使用Luhn算法验证信用卡号 $sum = 0; $length = strlen($digits); for ($i = 0; $i < $length; $i++) { $digit = (int)$digits[$length - $i - 1]; if ($i % 2 === 1) { $digit *= 2; if ($digit > 9) { $digit -= 9; } } $sum += $digit; } if ($sum % 10 !== 0) { return false; } // 格式化信用卡号(每4位一组,用空格分隔) return preg_replace('/(d{4})/', '$1 ', $digits); } $cardNumbers = [ '4111111111111111', // Visa测试卡号 '5500000000000004', // MasterCard测试卡号 '340000000000009', // American Express测试卡号 '6011000000000004', // Discover测试卡号 '4111-1111-1111-1111', // 带分隔符的Visa卡号 '4111111111111112', // 无效的卡号 '1234567890123456' // 无效的卡号 ]; foreach ($cardNumbers as $cardNumber) { $formatted = validateAndFormatCreditCard($cardNumber); if ($formatted !== false) { echo $cardNumber . " 是有效的信用卡号,格式化后: " . $formatted . "n"; } else { echo $cardNumber . " 是无效的信用卡号n"; } } ?>
7. 高级正则表达式技巧
7.1 使用断言
<?php // 正向先行断言 (?=) // 匹配后面跟着"world"的"hello" $pattern = '/hello(?=world)/'; $string = 'helloworld helloworlds hellothere'; preg_match_all($pattern, $string, $matches); echo "正向先行断言匹配结果: "; print_r($matches[0]); // 输出: Array ( [0] => hello [1] => hello ) // 负向先行断言 (?!) // 匹配后面不跟着"world"的"hello" $pattern = '/hello(?!world)/'; $string = 'helloworld helloworlds hellothere'; preg_match_all($pattern, $string, $matches); echo "n负向先行断言匹配结果: "; print_r($matches[0]); // 输出: Array ( [0] => hello ) // 正向后行断言 (?<=) // 匹配前面是"hello"的"world" $pattern = '/(?<=hello)world/'; $string = 'helloworld hellothere world'; preg_match_all($pattern, $string, $matches); echo "n正向后行断言匹配结果: "; print_r($matches[0]); // 输出: Array ( [0] => world ) // 负向后行断言 (?<!) // 匹配前面不是"hello"的"world" $pattern = '/(?<!hello)world/'; $string = 'helloworld hellothere world'; preg_match_all($pattern, $string, $matches); echo "n负向后行断言匹配结果: "; print_r($matches[0]); // 输出: Array ( [0] => world ) ?>
7.2 使用条件模式
<?php // 条件模式 (?(condition)then|else) // 如果字符串以"Q"开头,则匹配"Q followed by digits",否则匹配"Not Q followed by letters" $pattern = '/^(Q)?(?(1)d+[a-z]*|[a-z]+)$/'; $strings = [ 'Q123abc', 'Q123', 'abc', '123abc', 'Qabc' ]; foreach ($strings as $string) { if (preg_match($pattern, $string)) { echo "$string 匹配成功n"; } else { echo "$string 匹配失败n"; } } ?>
7.3 使用递归模式
<?php // 递归模式 (?R) // 匹配嵌套的括号 $pattern = '/(([^()]|(?R))*)/'; $strings = [ '(no nesting)', '(outer (inner) outer)', '(outer (inner (innermost) inner) outer)', '(unbalanced' ]; foreach ($strings as $string) { if (preg_match($pattern, $string, $matches)) { echo "$string 匹配成功: " . $matches[0] . "n"; } else { echo "$string 匹配失败n"; } } ?>
7.4 使用注释和模式修饰符
<?php // 使用x修饰符添加注释,忽略空白字符 $pattern = '/ ^ # 字符串开始 (d{4}) # 年份(4位数字) - # 分隔符 (d{2}) # 月份(2位数字) - # 分隔符 (d{2}) # 日期(2位数字) $ # 字符串结束 /x'; $string = '2023-07-15'; if (preg_match($pattern, $string, $matches)) { echo "匹配成功:n"; echo "完整匹配: " . $matches[0] . "n"; echo "年份: " . $matches[1] . "n"; echo "月份: " . $matches[2] . "n"; echo "日期: " . $matches[3] . "n"; } else { echo "匹配失败n"; } ?>
7.5 使用回调函数进行替换
<?php // 使用preg_replace_callback进行复杂替换 // 将文本中的所有日期格式从MM/DD/YYYY转换为YYYY-MM-DD $text = "事件1发生在12/25/2023,事件2发生在01/01/2024,事件3发生在02/14/2024。"; $pattern = '/(d{2})/(d{2})/(d{4})/'; $result = preg_replace_callback($pattern, function($matches) { return $matches[3] . '-' . $matches[1] . '-' . $matches[2]; }, $text); echo "原始文本: " . $text . "n"; echo "转换后文本: " . $result . "n"; ?>
7.6 多行匹配与处理
<?php // 使用m修饰符进行多行匹配 $text = "Start line 1 End line 1 Start line 2 End line 2"; // 匹配每行的开始 $pattern = '/^Start/m'; preg_match_all($pattern, $text, $matches); echo "每行开始处的'Start':n"; print_r($matches[0]); // 匹配每行的结束 $pattern = '/2$/m'; preg_match_all($pattern, $text, $matches); echo "n每行结束处的'2':n"; print_r($matches[0]); // 使用s修饰符使.匹配包括换行符在内的所有字符 $html = "<div>第一行n第二行</div>"; $pattern = '/<div>.*</div>/s'; if (preg_match($pattern, $html, $matches)) { echo "n匹配多行HTML:n" . $matches[0] . "n"; } ?>
8. 性能优化与最佳实践
8.1 避免回溯
<?php // 低效的正则表达式(可能导致大量回溯) $inefficientPattern = '/<[^>]+>/'; // 高效的正则表达式(使用原子组避免回溯) $efficientPattern = '/<(?>[^>]+)>/'; $html = '<div>' . str_repeat('a', 1000) . '</div>'; // 测试低效模式 $start = microtime(true); for ($i = 0; $i < 1000; $i++) { preg_match($inefficientPattern, $html); } $inefficientTime = microtime(true) - $start; // 测试高效模式 $start = microtime(true); for ($i = 0; $i < 1000; $i++) { preg_match($efficientPattern, $html); } $efficientTime = microtime(true) - $start; echo "低效模式执行时间: " . number_format($inefficientTime * 1000, 2) . " 毫秒n"; echo "高效模式执行时间: " . number_format($efficientTime * 1000, 2) . " 毫秒n"; ?>
8.2 使用具体字符类代替通配符
<?php // 低效的正则表达式(使用通配符) $inefficientPattern = '/^[a-z]+@[a-z]+.[a-z]+$/'; // 高效的正则表达式(使用具体字符类) $efficientPattern = '/^[a-z0-9._%+-]+@[a-z0-9.-]+.[a-z]{2,}$/'; $emails = [ 'user@example.com', 'user.name@example.co.uk', 'user123@example123.com', 'invalid-email', 'another.invalid.email@' ]; // 测试低效模式 $start = microtime(true); for ($i = 0; $i < 1000; $i++) { foreach ($emails as $email) { preg_match($inefficientPattern, $email); } } $inefficientTime = microtime(true) - $start; // 测试高效模式 $start = microtime(true); for ($i = 0; $i < 1000; $i++) { foreach ($emails as $email) { preg_match($efficientPattern, $email); } } $efficientTime = microtime(true) - $start; echo "低效模式执行时间: " . number_format($inefficientTime * 1000, 2) . " 毫秒n"; echo "高效模式执行时间: " . number_format($efficientTime * 1000, 2) . " 毫秒n"; ?>
8.3 预编译正则表达式
<?php // 不预编译正则表达式 function withoutCompilation($strings, $pattern) { $result = []; foreach ($strings as $string) { if (preg_match($pattern, $string)) { $result[] = $string; } } return $result; } // 预编译正则表达式 function withCompilation($strings, $pattern) { $result = []; foreach ($strings as $string) { if (preg_match($pattern, $string)) { $result[] = $string; } } return $result; } $strings = [ 'apple123', 'banana456', 'cherry789', 'date012', 'elderberry345', 'fig678', 'grape901' ]; $pattern = '/^[a-z]+d+$/'; // 测试不预编译 $start = microtime(true); for ($i = 0; $i < 1000; $i++) { withoutCompilation($strings, $pattern); } $withoutCompilationTime = microtime(true) - $start; // 测试预编译 $start = microtime(true); for ($i = 0; $i < 1000; $i++) { withCompilation($strings, $pattern); } $withCompilationTime = microtime(true) - $start; echo "不预编译执行时间: " . number_format($withoutCompilationTime * 1000, 2) . " 毫秒n"; echo "预编译执行时间: " . number_format($withCompilationTime * 1000, 2) . " 毫秒n"; ?>
8.4 使用合适的函数
<?php // 对于简单的字符串替换,str_replace比preg_replace更高效 $text = 'The quick brown fox jumps over the lazy dog.'; $search = 'fox'; $replace = 'cat'; // 使用str_replace $start = microtime(true); for ($i = 0; $i < 10000; $i++) { $result = str_replace($search, $replace, $text); } $strReplaceTime = microtime(true) - $start; // 使用preg_replace $start = microtime(true); for ($i = 0; $i < 10000; $i++) { $result = preg_replace('/' . preg_quote($search, '/') . '/', $replace, $text); } $pregReplaceTime = microtime(true) - $start; echo "str_replace执行时间: " . number_format($strReplaceTime * 1000, 2) . " 毫秒n"; echo "preg_replace执行时间: " . number_format($pregReplaceTime * 1000, 2) . " 毫秒n"; ?>
8.5 避免过度使用正则表达式
<?php // 检查字符串是否全是数字 // 使用正则表达式 function isNumericRegex($string) { return preg_match('/^d+$/', $string) === 1; } // 使用内置函数 function isNumericNative($string) { return ctype_digit($string); } $strings = [ '12345', '123abc', 'abc123', '12.34', '' ]; // 测试正则表达式方法 $start = microtime(true); for ($i = 0; $i < 10000; $i++) { foreach ($strings as $string) { isNumericRegex($string); } } $regexTime = microtime(true) - $start; // 测试内置函数方法 $start = microtime(true); for ($i = 0; $i < 10000; $i++) { foreach ($strings as $string) { isNumericNative($string); } } $nativeTime = microtime(true) - $start; echo "正则表达式方法执行时间: " . number_format($regexTime * 1000, 2) . " 毫秒n"; echo "内置函数方法执行时间: " . number_format($nativeTime * 1000, 2) . " 毫秒n"; ?>
9. 实战案例
9.1 创建一个简单的模板引擎
<?php class SimpleTemplateEngine { private $template; public function __construct($template) { $this->template = $template; } public function render($data) { // 替换变量 {{variable}} $result = preg_replace_callback('/{{(w+)}}/', function($matches) use ($data) { $varName = $matches[1]; return isset($data[$varName]) ? $data[$varName] : ''; }, $this->template); // 处理条件语句 {% if condition %}...{% endif %} $result = preg_replace_callback('/{%s*ifs+(w+)s*%}(.*?){%s*endifs*%}/s', function($matches) use ($data) { $condition = $matches[1]; $content = $matches[2]; return isset($data[$condition]) && $data[$condition] ? $content : ''; }, $result); // 处理循环 {% for item in array %}...{% endfor %} $result = preg_replace_callback('/{%s*fors+(w+)s+ins+(w+)s*%}(.*?){%s*endfors*%}/s', function($matches) use ($data) { $itemVar = $matches[1]; $arrayVar = $matches[2]; $content = $matches[3]; if (!isset($data[$arrayVar]) || !is_array($data[$arrayVar])) { return ''; } $result = ''; foreach ($data[$arrayVar] as $item) { // 替换循环变量 $itemContent = preg_replace('/{{' . $itemVar . '.(w+)}}/e', 'isset($item['$1']) ? $item['$1'] : ''', $content); $result .= $itemContent; } return $result; }, $result); return $result; } } // 使用示例 $template = ' <html> <head> <title>{{title}}</title> </head> <body> <h1>{{heading}}</h1> {% if showIntro %} <p>{{introText}}</p> {% endif %} <h2>产品列表</h2> <ul> {% for product in products %} <li>{{product.name}} - ${{product.price}}</li> {% endfor %} </ul> <footer> <p>{{footerText}}</p> </footer> </body> </html>'; $data = [ 'title' => '产品目录', 'heading' => '我们的产品', 'showIntro' => true, 'introText' => '欢迎查看我们的产品目录!', 'products' => [ ['name' => '产品A', 'price' => '19.99'], ['name' => '产品B', 'price' => '29.99'], ['name' => '产品C', 'price' => '39.99'] ], 'footerText' => '© 2023 我们的公司' ]; $engine = new SimpleTemplateEngine($template); echo $engine->render($data); ?>
9.2 创建一个路由解析器
<?php class SimpleRouter { private $routes = []; public function addRoute($method, $pattern, $handler) { $this->routes[] = [ 'method' => strtoupper($method), 'pattern' => $pattern, 'handler' => $handler ]; } public function route($method, $uri) { $method = strtoupper($method); foreach ($this->routes as $route) { if ($route['method'] !== $method) { continue; } // 将路由模式转换为正则表达式 $pattern = preg_replace('/{(w+)}/', '(?P<$1>[^/]+)', $route['pattern']); $pattern = '#^' . $pattern . '$#'; if (preg_match($pattern, $uri, $matches)) { // 提取命名参数 $params = []; foreach ($matches as $key => $value) { if (is_string($key)) { $params[$key] = $value; } } return [ 'handler' => $route['handler'], 'params' => $params ]; } } return null; // 没有匹配的路由 } } // 使用示例 $router = new SimpleRouter(); // 添加路由 $router->addRoute('GET', '/', 'HomeController@index'); $router->addRoute('GET', '/users', 'UserController@list'); $router->addRoute('GET', '/users/{id}', 'UserController@show'); $router->addRoute('POST', '/users', 'UserController@create'); $router->addRoute('GET', '/posts/{postId}/comments', 'CommentController@listByPost'); $router->addRoute('POST', '/posts/{postId}/comments', 'CommentController@create'); // 测试路由 $testCases = [ ['method' => 'GET', 'uri' => '/'], ['method' => 'GET', 'uri' => '/users'], ['method' => 'GET', 'uri' => '/users/123'], ['method' => 'POST', 'uri' => '/users'], ['method' => 'GET', 'uri' => '/posts/456/comments'], ['method' => 'POST', 'uri' => '/posts/789/comments'], ['method' => 'GET', 'uri' => '/nonexistent'] ]; foreach ($testCases as $testCase) { $result = $router->route($testCase['method'], $testCase['uri']); if ($result) { echo "路由匹配: " . $testCase['method'] . " " . $testCase['uri'] . "n"; echo "处理器: " . $result['handler'] . "n"; if (!empty($result['params'])) { echo "参数:n"; foreach ($result['params'] as $key => $value) { echo " $key: $valuen"; } } } else { echo "没有找到匹配的路由: " . $testCase['method'] . " " . $testCase['uri'] . "n"; } echo "n"; } ?>
9.3 创建一个简单的Markdown解析器
<?php class SimpleMarkdownParser { public function parse($markdown) { $html = $markdown; // 解析标题 $html = preg_replace('/^# (.*$)/m', '<h1>$1</h1>', $html); $html = preg_replace('/^## (.*$)/m', '<h2>$1</h2>', $html); $html = preg_replace('/^### (.*$)/m', '<h3>$1</h3>', $html); // 解析粗体和斜体 $html = preg_replace('/**(.*?)**/', '<strong>$1</strong>', $html); $html = preg_replace('/*(.*?)*/', '<em>$1</em>', $html); // 解析链接 $html = preg_replace('/[(.*?)]((.*?))/', '<a href="$2">$1</a>', $html); // 解析图片 $html = preg_replace('/)/', '<img src="$2" alt="$1">', $html); // 解析无序列表 $html = preg_replace('/^* (.*$)/m', '<ul><li>$1</li></ul>', $html); $html = preg_replace('/(</ul>s*<ul>)/', '', $html); // 解析有序列表 $html = preg_replace('/^d+. (.*$)/m', '<ol><li>$1</li></ol>', $html); $html = preg_replace('/(</ol>s*<ol>)/', '', $html); // 解析代码块 $html = preg_replace('/```(.*?)```/s', '<pre><code>$1</code></pre>', $html); // 解析行内代码 $html = preg_replace('/`(.*?)`/', '<code>$1</code>', $html); // 解析段落 $html = preg_replace('/nn/', '</p><p>', $html); $html = '<p>' . $html . '</p>'; // 清理空段落 $html = preg_replace('/<p></p>/', '', $html); return $html; } } // 使用示例 $markdown = '# 欢迎使用Markdown 这是一个**简单**的Markdown解析器示例。 ## 功能特性 - 支持标题 - 支持*斜体*和**粗体** - 支持[链接](https://example.com) - 支持图片:  1. 有序列表项1 2. 有序列表项2 3. 有序列表项3 ## 代码示例 这是一个行内代码:`$variable = "value";` 这是一个代码块:
function helloWorld() {
echo "Hello, World!";
}
> 这是一个引用块。 希望你喜欢这个简单的Markdown解析器!'; $parser = new SimpleMarkdownParser(); $html = $parser->parse($markdown); echo $html; ?>
10. 总结
PHP正则表达式是一种强大的文本处理工具,掌握它可以帮助你更高效地处理各种文本操作和数据验证任务。本文从基础概念开始,逐步介绍了正则表达式的各种元素和技巧,并通过大量实例展示了如何在实际应用中使用正则表达式。
关键要点:
基础元素:了解正则表达式的基本元素,如字符类、量词、断言等,是构建复杂模式的基础。
数据验证:正则表达式在表单验证、数据清洗等方面非常有用,可以验证电子邮件、电话号码、URL等格式。
文本处理:使用正则表达式可以高效地提取、替换和格式化文本内容。
高级技巧:掌握断言、条件模式、递归模式等高级技巧,可以解决更复杂的文本处理问题。
性能优化:避免回溯、使用具体字符类、预编译正则表达式等技巧可以提高正则表达式的执行效率。
最佳实践:在适当的情况下使用正则表达式,避免过度使用,并考虑使用PHP内置函数作为替代方案。
通过不断练习和实践,你将能够熟练掌握PHP正则表达式,并在实际项目中灵活应用它们来解决各种文本处理和数据验证问题。