应使用 preg_match_all('/\X/u', $s, $matches) 按 Unicode 字形安全分割含 emoji 的字符串,因 emoji 为多字节 UTF-8 字符,explode() 等字节级函数会破坏其完整性;同时需确保 mb_internal_encoding() 为 'UTF-8' 并使用 utf8mb4 数据库编码。

PHP 用 explode() 分割含 emoji 的字符串会出错
直接用 explode() 或 str_split() 处理带 emoji 的文本,常出现乱码、截断或字符数错乱——因为 emoji 多为 UTF-16 补充平面字符(如 ?、??),在 UTF-8 下占 4 字节,而 PHP 默认的字节级函数不识别 Unicode 边界。
典型表现:strlen('??') === 7(正确),但 substr('??', 0, 1) 返回空或乱码;explode(' ', $text) 在 emoji 后面的空格可能被跳过或错位。
- 别用
mb_split()(已废弃且不支持 PCRE Unicode 模式) - 避免
preg_split('/./u', $s)这类“逐字符”正则——它会把 ZWJ 连接符(如 ?? 中的\u200D)拆开,破坏组合 emoji - 优先用
preg_match_all('/\X/u', $s, $matches)提取完整 Unicode 字形(grapheme)
用 preg_match_all('/\X/u', ...) 安全提取 emoji 和文字
\X 是 PCRE 的 Unicode 字形(extended grapheme cluster)匹配模式,能正确识别 emoji 序列(包括带修饰符的 ??、ZWJ 组合 ??)、中文、拉丁字母等,是目前最可靠的基础切分方式。
示例:对含 emoji 的句子做「按字形分割」:
立即学习“PHP免费学习笔记(深入)”;
preg_match_all('/\X/u', 'Hello ? world ?!', $matches);
// $matches[0] = ['H', 'e', 'l', 'l', 'o', ' ', '?', ' ', 'w', 'o', 'r', 'l', 'd', ' ', '?', '!']
- 注意必须加
/u修饰符,否则\X无效 - 若需保留原始分隔符(比如按空格分割但保留 emoji 完整),先用
preg_match_all('/\S+|\s+/u', $s, $matches)匹配非空白/空白块 - 性能上比
mb_substr()循环略慢,但对几千字符以内的文本无感知
需要「按指定分隔符切割」时,用 mb_ereg_replace() 预处理再 explode()
如果业务逻辑依赖 explode(' | ', $text) 这类固定分隔符,又怕 emoji 干扰,不能硬改分隔逻辑,就该预处理:把分隔符「锚定」在非 emoji 区域。
做法是先用正则把分隔符替换为唯一标记(如 \x01),确保只匹配纯 ASCII/空白分隔符,再 explode():
$clean_sep = preg_quote(' | ', '/');
$text_safe = mb_ereg_replace("($clean_sep)(?=[^\x{1F600}-\x{1F6FF}\x{200D}\x{1F900}-\x{1F9FF}]+\$)", "\x01", $text, 'm');
$parts = explode("\x01", $text_safe);
- 关键点:用
(?=[^\x{...}]+\$)断言分隔符后面没紧挨 emoji,避免误伤 - 更稳妥可改用
preg_split("/$clean_sep(?![\x{1F600}-\x{1F6FF}\x{200D}\x{1F900}-\x{1F9FF}])/u", $text),直接否定后置 emoji - emoji Unicode 范围要覆盖常用区:基本表情、修饰符、ZWJ、扩展补充(如 ???),别只写
\x{1F600}-\x{1F64F}
存储和传输前统一转成 UTF-8 + 检查 mb_internal_encoding()
很多问题其实源于环境配置:MySQL 连接未设 utf8mb4、PHP mb_internal_encoding() 不是 UTF-8、Nginx 或 Apache 未声明 charset utf-8。
- 执行前务必确认:
mb_internal_encoding() === 'UTF-8',否则mb_*函数行为不可靠 - 数据库连接必须显式设置:
mysqli_set_charset($conn, 'utf8mb4')或 PDO DSN 加;charset=utf8mb4 - 用
json_encode($data, JSON_UNESCAPED_UNICODE)输出 API,避免 emoji 被编码成\ud83d\udc4b
真正麻烦的不是切分本身,而是整个链路中任意一环用了字节操作或错误编码——比如日志里看到 ,往往意味着数据进 PHP 前就损坏了,这时候再怎么修 preg_match_all() 都没用。











