PHP 8.6 弃用 mb_ereg* 与 mb_split:Oniguruma 停更后怎么迁到 preg_*
Oniguruma 是 mbstring 扩展背后的正则引擎,2025 年 4 月结束维护。上游不再提供安全修复,PHP 无法继续把这条依赖链留在核心里,于是把整个 mbregex 层标记为弃用,PHP 9.0 移除。官方推荐的替代路径是带 /u 修饰符的 PCRE 函数。
受影响的函数
弃用范围覆盖 mbstring 正则接口的整组函数,不只是最常用的那两个:
- 匹配:
mb_ereg()、mb_eregi()、mb_ereg_match() - 替换:
mb_ereg_replace()、mb_eregi_replace()、mb_ereg_replace_callback() - 搜索状态机:
mb_ereg_search()、mb_ereg_search_init()、mb_ereg_search_pos()、mb_ereg_search_regs()、mb_ereg_search_getregs()、mb_ereg_search_getpos()、mb_ereg_search_setpos() - 拆分:
mb_split() - 编码与选项:
mb_regex_encoding()、mb_regex_set_options()
只要代码里还在调用这一组里的任意一个,8.6 下就会收到弃用提示。
迁到 preg_*:/u 与 \w 的差异
mb_ereg* 默认按 mbstring 的内部编码工作,编码由 mb_regex_encoding() 设置,通常是 UTF-8,因此按字符匹配。preg_* 默认按字节处理,要按 UTF-8 字符匹配必须显式加 /u。
// 旧写法
mb_regex_encoding('UTF-8');
$ok = mb_ereg('^\w+$', $name);
// 新写法
$ok = preg_match('/^\w+$/u', $name) === 1;
\w 在 PCRE 的 /u 下表示 Unicode 字母、数字和下划线,与 mb_ereg 的默认行为接近,但并非完全等价;涉及组合字符、连接符等边界情况时,需要用实际语料回归。
另一个必须处理的差异是返回值。无效 UTF-8 会让 preg_* 返回 false,而不是匹配失败:
preg_match('/^\w+$/u', "\xff"); // false
preg_last_error(); // PREG_BAD_UTF8_ERROR
判断时用 === 1 / === false 区分「不匹配」和「出错」,不要只写 if (preg_match(...))。
替换侧还有一个历史包袱:mb_ereg_replace() 的 e 修饰符早已移除,而 preg_replace 的 /e 从 PHP 7 起也已移除。迁移中遇到的这类遗留写法要改写为 preg_replace_callback():
$out = preg_replace_callback(
'/\{(\w+)\}/u',
fn (array $m) => $vars[$m[1]] ?? '',
$template
);
mb_onig PECL 包的位置
为依赖 mbregex 的存量代码准备了一个 PECL 包 mb_onig,把 Oniguruma 支持以扩展形式继续提供。它的作用是给迁移期一个缓冲,让老项目不必在一次升级中改完所有调用点,而不是长期方案。上游引擎本身已停止维护,安全修复跟不上,所以官方推荐仍然是迁到 preg_* + /u。
用静态检查把调用点找出来
先用一次全量扫描拿到清单,再决定是批量替换还是逐个看上下文:
rg -n '\b(mb_ereg\w*|mb_split|mb_regex_\w+)\s*\(' --glob '*.php'
把同样的规则固化成 CI 检查,PHPStan 自定义 Rule、PHP_CodeSniffer 的自定义 sniff 都能做,核心是匹配上述函数名并在新代码合入时拦截。扫描结果里要特别注意两类:一是 mb_ereg_replace 带选项参数的调用,二是 mb_ereg_search_* 这种分步调用状态机的写法,它们不能机械替换,需要重写成 preg_match + $matches。
同一版本里的另外两个弃用
php://filter过滤器链上限为 16。未配置filter.max_filter_count时超限发E_DEPRECATED;显式配置后超限直接打开失败,属于行为变化而非仅提示。__construct()/__destruct()的返回值弃用。检测到return就发弃用提示,PHP 9.0 变为错误。
按已公布的排期,8.6 的 GA 在 2026-11-19,正式发版前还有若干 RC。弃用项在 8.x 内只是提示,9.0 才真正移除,但 mb_ereg* 的替换越早做越好,因为语义差异需要靠测试兜住,而不是靠改函数名。