安全截取长文本需先判空和字符串类型,再 strip_tags 去 HTML,最后 mb_substr 按字符截取并指定 'UTF-8' 编码;富文本建议保存时生成摘要存独立字段。

ThinkPHP 模型中用 getAttr 自定义字段获取逻辑时,怎么安全截取长文本?
直接在模型的 getAttr 方法里对字段做 mb_substr 或 substr 截取,看似简单,但容易踩两个坑:一是没判断字段是否为空或非字符串类型,二是没处理 HTML 标签导致摘要乱码或截断标签。
推荐做法是:先检查值是否存在且为字符串,再用 strip_tags 去除 HTML,最后用 mb_substr 按字符数截取(注意指定编码,如 'UTF-8')。
- 别在
getAttr里直接调用html_entity_decode或htmlspecialchars_decode—— 多数内容入库前已转义,重复解码可能引入 XSS 风险 - 如果字段本身含富文本(如 UEditor 输出),建议提前在保存时生成摘要并存入独立字段(如
summary),避免每次读取都解析 -
mb_substr($value, 0, 120, 'UTF-8')中的长度 120 是视觉友好值,但实际要结合前端展示区域宽度和中英文混合情况微调
用 append 动态添加摘要属性时,为什么有时返回 null?
ThinkPHP 的 append 是在查询后通过反射调用对应方法(如 getSummaryAttr),若该方法未定义、抛出异常或返回 null,字段就为空。最常见原因是方法体内用了未初始化的变量或错误调用了模型关联。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 确保
getSummaryAttr方法存在且 public,参数必须是$value, $data两个($data是当前记录完整数组,可用于跨字段计算) - 不要在该方法里调用
$this->relation()或其他可能触发新查询的操作——会拖慢性能且易造成 N+1 - 若需基于关联字段生成摘要(如文章 + 分类名),应改用
with预加载,再从$data中取值
数据库字段类型是 text 或 longtext,但想按字节数而非字符数截取,怎么办?
MySQL 的 text 字段按字节存储,而 PHP 的 mb_substr 默认按字符计数;中文 UTF-8 下一个字符占 3 字节,直接按字符截 200 个,实际可能超出前端限制的字节上限(比如接口要求 ≤512 字节)。
这时候得换思路:
- 用
strlen+substr控制字节数,但需先mb_convert_encoding($value, 'UTF-8', 'auto')统一编码,否则乱码 - 更稳妥的是用
mb_strcut($value, 0, 512, 'UTF-8')—— 它按字节截取但保证不切断多字节字符 - 注意:ThinkPHP 6.1+ 的
json类型字段自动序列化,若摘要字段也设为json类型,会强制转义,反而增加体积,应保持varchar或text
为什么用 setAttr 在写入时自动生成摘要,却总和原文不一致?
因为 setAttr 只在新增/更新时触发,而 ThinkPHP 的数据写入流程中,setAttr 执行时机早于验证器、自动完成(auto)和事件钩子,若你在验证器里修改了原始字段(如过滤 XSS),摘要就基于旧值生成了。
关键点:
- 摘要生成逻辑应放在
beforeWrite事件里,它在所有字段处理之后、SQL 执行之前运行,能拿到最终入库值 - 不要依赖
$this->data['content'],要用$this->getData('content')获取当前已处理过的值 - 如果摘要字段也参与验证(比如必填),记得在验证规则里把它设为
ignore,否则会因自动生成逻辑未执行而报错
复杂点在于:摘要不是纯函数式转换,它依赖上下文(是否含图片、是否有标题、前端渲染方式)。真正稳定的方案是把摘要生成下沉到业务层,模型只负责透传,而不是塞进各种钩子里硬凑。


















