strlen() 对 emoji 组合字符失效是因为它返回字节数而非视觉字符数;grapheme_strlen() 基于 Unicode 图形簇规则,能正确识别 ZWJ 连接、修饰符组合等为单个字符,是唯一可靠方案。

PHP 中 strlen() 为什么对 emoji 组合字符失效
strlen() 返回的是字节数,不是“人眼可见的字符数”。一个普通 ASCII 字符占 1 字节,而大多数 emoji(尤其是肤色修饰、家族、性别变体等组合型 emoji)由多个 UTF-8 编码单元组成:比如 ?? 是 "U+1F468 U+200D U+1F4BB" 三个 Unicode 码点,UTF-8 编码后占 13 字节。用 strlen() 测得是 13,但用户只认为这是「1 个字符」。
用 mb_strlen() 也不够——组合 emoji 仍被算作多个字符
mb_strlen($str, 'UTF-8') 按 Unicode 码点计数,对基础 emoji(如 ❤️)能正确返回 1,但对 ZWJ 连接的组合 emoji(如 ??、?❤️?)仍会拆成多个码点计数(例如 ?? 实际是 3 个码点),导致结果偏大。
- ✅ 正确识别单个基础 emoji(?、?)
- ❌ 错误拆分 ZWJ 序列(??)、修饰符序列(???)
- ❌ 忽略变体选择符(VS16)和区域指示符(如 ??)的成对逻辑
真正可靠的方案:用 grapheme_strlen() + 启用 ICU
grapheme_strlen() 基于 Unicode 图形簇(Grapheme Cluster)规则计算,能将 ZWJ 连接、修饰符组合、区域标志对等识别为「1 个视觉字符」,这才是用户感知的“长度”。
- 确保 PHP 编译时启用了
intl扩展(extension=intl在 php.ini 中启用) - 必须使用
grapheme_strlen($str),不传编码参数(它只支持 UTF-8 且自动处理) - 测试字符串需是合法 UTF-8(可用
mb_check_encoding($str, 'UTF-8')预检) - 示例:
grapheme_strlen("???????")→ 返回3,而非11或7
生产环境要防 fallback:检测函数是否存在并兜底
部分共享主机或精简 Docker 镜像可能未启用 intl,直接调用 grapheme_strlen() 会报 Fatal error: Uncaught Error: Call to undefined function grapheme_strlen()。
立即学习“PHP免费学习笔记(深入)”;
- 务必先检查:
function_exists('grapheme_strlen') - 无 ICU 时,只能退回到
mb_strlen()(明确告知业务方“emoji 组合会被高估”) - 绝不要 fallback 到
strlen()—— 字节长度在表单校验、截断、计数场景下完全不可用 - 可封装一层:
function visual_strlen($str) { if (function_exists('grapheme_strlen')) { return grapheme_strlen($str); } return mb_strlen($str, 'UTF-8'); }
grapheme_strlen() 校验长度限制。否则前端显示 12 个字符,后端却因计数错误拒绝提交,或者数据库截断出错——这类问题往往在灰度期才暴露,排查成本远高于一开始就选对函数。











