ThinkPHP模板中用substr截中文会乱码,必须改用mb_substr等多字节方案;因其按字节截取,UTF-8中文占3字节,易截断导致乱码。

直接说结论:ThinkPHP 模板里用 substr 截中文,99% 会乱码;必须换用支持多字节的方案,优先启用 mb_substr,而不是硬改框架源码或手写正则匹配。
为什么模板里的 substr 一截中文就乱码
ThinkPHP 模板引擎默认的 substr 是 PHP 原生函数,按字节切——UTF-8 下一个中文占 3 字节,substr($str, 0, 10) 可能刚切到某个汉字中间,后半截字节缺失,浏览器就渲染成 或其他乱码符号。这不是 ThinkPHP 的 bug,是原生 substr 的设计限制。
常见错误现象:
- 前端显示“这是一”“测试内”“欢”
- 截取长度越小,乱码概率越高(比如只取前 5 个字符)
- 混排字符串(中/英/数字/emoji)时,错位更明显
在模板中安全使用 mb_substr 的前提条件
不是写了 mb_substr 就自动生效——它依赖 PHP 的 mbstring 扩展,且必须显式传编码参数。漏掉任一环节都会退化回乱码。
立即学习“PHP免费学习笔记(深入)”;
实操要点:
- 确认服务器已启用
mbstring:运行php -m | grep mbstring,无输出需在php.ini中取消;extension=mbstring前的分号并重启 PHP - 模板中不能直接写
mb_substr,ThinkPHP 不识别该函数名,需注册为模板函数或使用think助手 - 必须显式指定编码,如
'utf-8';不能依赖mb_internal_encoding()全局设置,模板执行环境可能未初始化 - ThinkPHP 6+ 推荐用
Str::limit()(需use think\helper\Str;),但仅限于 PHP 代码层;模板中仍需自定义函数
推荐做法:在 app/common.php 定义 msubstr 模板函数
这是最轻量、不侵入核心、兼容性最好的方式。避免修改 ThinkPHP/Common/common.php(升级时会被覆盖)。
示例代码(直接复制进 app/common.php):
function msubstr($str, $start = 0, $length, $charset = 'utf-8', $suffix = true) {
if (!is_string($str)) {
return '';
}
if (function_exists('mb_substr')) {
$slice = mb_substr($str, $start, $length, $charset);
} else {
$slice = substr($str, $start, $length);
}
return $suffix && mb_strlen($str, $charset) > $length ? $slice . '...' : $slice;
}
模板中调用:{$title|msubstr=0,12,'utf-8',true}
关键细节:
-
$suffix默认为true,但 ThinkPHP 模板语法里布尔值不能直接写true,得用1或留空(取决于版本),稳妥起见建议传1 - 如果项目同时存在 GBK 页面(极少见),把第三个参数换成
'gbk',但务必确保字符串真实编码与之匹配 - 不要省略
$charset参数——即使全站 UTF-8,也不代表每个变量都经过正确编码声明
容易被忽略的边界情况
很多人测了“你好世界”能截,就以为万事大吉,其实还有几个隐性坑:
- emoji 表情(如 ?)在 UTF-8 下占 4 字节,
mb_substr能正确识别为 1 字符,但旧版 PHP(@ 抑制警告 - 用户输入含 BOM 头的 UTF-8 字符串,
mb_strlen可能多算 1 个字符,导致$suffix判断失准;可用ltrim($str, "\xEF\xBB\xBF")预处理 - 数据库字段类型为
TEXT但连接字符集设为latin1,查出来的中文实际是乱码二进制流,再怎么截也白搭——得先 fix 连接层和字段 collation
真正要稳,得从数据入库、传输、渲染三层确认编码一致;单靠模板函数只是最后一道补救。



















