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) - 支持图片: ![示例图片](https://example.com/image.jpg) 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正则表达式是一种强大的文本处理工具,掌握它可以帮助你更高效地处理各种文本操作和数据验证任务。本文从基础概念开始,逐步介绍了正则表达式的各种元素和技巧,并通过大量实例展示了如何在实际应用中使用正则表达式。

关键要点:

  1. 基础元素:了解正则表达式的基本元素,如字符类、量词、断言等,是构建复杂模式的基础。

  2. 数据验证:正则表达式在表单验证、数据清洗等方面非常有用,可以验证电子邮件、电话号码、URL等格式。

  3. 文本处理:使用正则表达式可以高效地提取、替换和格式化文本内容。

  4. 高级技巧:掌握断言、条件模式、递归模式等高级技巧,可以解决更复杂的文本处理问题。

  5. 性能优化:避免回溯、使用具体字符类、预编译正则表达式等技巧可以提高正则表达式的执行效率。

  6. 最佳实践:在适当的情况下使用正则表达式,避免过度使用,并考虑使用PHP内置函数作为替代方案。

通过不断练习和实践,你将能够熟练掌握PHP正则表达式,并在实际项目中灵活应用它们来解决各种文本处理和数据验证问题。