在 PHP 中使用正则表达式踩到的坑
写在前面:此文为了记录问题排查过程,相关地址 Incorrect match result when in PHP。
开发环境
- PHP:Version 7.4.0 on macOS installed via Homebrew。
- 正则表达式在线测试、调试:<https://regex101.com/> 。
- 正则表达式:
([^a-zA-Z0-9])([a-zA-Z0-9]{11,})(?:\1)
。
- 测试字符串:
¥IyB1YBGsOT5¥
(注:¥
是日文反斜线)。
- PHP 函数:
preg_match_all()
。
排查过程
发现 PHP 中的期望结果与使用 regex101 在线验证结果不同,在 JavaScript 环境中测试与在线验证结果是相同的,便向作者提交了一票 issue。
@Doqnach 这位老哥说,regex101 的结果是正确的,不确定 PHP 出现非预期结果是什么原因,建议正则表达式带上 u
试试。我试了可以,难道 regex101 不应该改一下吗?@Doqnach 老哥回复,regex101 没错,错的是 PHP 默认不把字符串当 UTF-8 处理的行为。
我心想,世界上最好的语言 🙃 怎么可能会错?去查文档,从函数找起。在 pattern
和 subject
以及 flags
始终没有找到有效信息。浏览 User Contributed Notes 时意外发现,@bruha ¶ 提到 pcre。想起前些日子查看 brew list
时,注意到一个叫 pcre2
的,当时简单查了一下,了解大概是做什么用的。这下有点眉目了,该不会是 PHP 使用的这个标准(正则库)搞的鬼?继续顺着 pcre man page 查 unicode,终于找到这么一段有效信息。
The current implementation of PCRE corresponds approximately with Perl
5.12, including support for UTF-8/16/32 encoded strings and Unicode
general category properties. However, UTF-8/16/32 and Unicode support
has to be explicitly enabled; it is not the default. The Unicode tables
correspond to Unicode release 6.3.0.
顺便把 pcre2 man page 看一下 .
Unicode support is optional at build time (but is the default). However, processing strings as UTF code units must be enabled explicitly at run time.
心中又起疑问,PHP 7.4 用的是 pcre
还是 pcre2
?
brew uses --install | grep php
表明 PHP 使用了 pcre,php --re pcre
表明 preg_match_all
函数由 pcre version 7.4.0
支持。
至此,此话题便可完结。
反思总结
自己究竟多么不了解 PHP,当初上手是在 Windows 上,PHPStudy 一键安装好就开始用了,启用扩展模块右键即可。后来觉得点鼠标麻烦,自己解压 PHP 原版的分发包,改好配置文件,写批处理启动 Apache + PHP-FPM + MySQL。再后来,个人玩服务器系统和笔记本主力迁移到 Debian,PHP 也是使用 APT 包管理器几乎一键安装好,另外改改配置文件就好。
始终没有自己尝试编译过 PHP,Windows 上没有办法,搞起一套编译环境来得费好大功夫,甚至在那个系统上都没有过
编译的概念,软件安装包、压缩包下载好,点点鼠标就安装好,解压完就能用。以上种种导致不了解 PHP 使用了哪些第三方的模块,不了解编程语言和某些通用标准之间的关系,只停留在编程语言的基本使用层面。
现在,只有在 Docker 中使用 PHP 缺少某些扩展时才会重新编译一下。
最后,感谢伟大的开源社区,感谢热心的老哥们,由衷地佩服他们对于计算机编程的理解。
后记
Pattern Modifiers ¶ User Contributed Notes 中有很多关于 u(PCRE_UTF8)
修饰符的讨论,当初如果好好查查手册也不至于给人提 bug 去了 🤦♂️。
PHP-7.4/UPGRADING 中有启用外部 PCRE 库的指导:
- PCRE:
. --with-pcre-regex has been removed. Instead --with-external-pcre is provided
to opt into using an external PCRE library, rather than the bundled one.
References
- https://github.com/firasdib/Regex101/issues/1255.
- https://www.php.net/manual/en/function.preg-match-all.php.
- https://github.com/Doqnach.
- https://www.php.net/manual/en/function.preg-match-all.php#81559.
- https://www.php.net/manual/en/reference.pcre.pattern.modifiers.php.
- https://www.pcre.org/pcre.txt.
- https://www.pcre.org/pcre2.txt.
- https://github.com/php/php-src/blob/PHP-7.4/UPGRADING.