setSlugAttr是TP6中生成URL slug最稳妥的方式,需在模型中严格定义该方法处理标题转小写连字符格式,且仅在save/create等模型写入时生效,where()->update()等原生SQL操作完全绕过它。

setSlugAttr 是目前最稳妥的实现方式
ThinkPHP 6 不再支持 $_auto 或 auto(),老教程里写的 array('slug', 'strtolower', 1, 'function') 会静默失效——字段不变、不报错、也不触发。你要生成 SEO 友好的 URL slug(比如把“用户注册指南”转成“yong-hu-zhu-ce-zhi-nan”),必须用修改器(mutator)或手动赋值。
常见错误现象:
– 模板里输出 {$article.slug} 始终为空或原始标题
– 调试发现 create() 或 save() 后数据库 slug 字段没变
– 在控制器里直接 $model->slug = $this->makeSlug($title),但忘记在所有写入入口重复写,漏掉一处就出问题
- 必须在模型类中定义
setSlugAttr方法,命名严格为set + 字段名首字母大写 + Attr - 方法接收原始值(如
$title),返回处理后的字符串;传入null时需自行判断是否 fallback - 修改器只在
save()、create()、saveAll()等模型写入操作中生效;where()->update()和原生 SQL 完全绕过它 - 若依赖第三方库(如
Illuminate\Support\Str::slug),别在模板里用|管道符调用——它不认识命名空间,得提前封装成裸函数或注册 filter
onBeforeInsert 事件绑定后仍不触发?
立即学习“PHP免费学习笔记(深入)”;
调研本地常见问题,将其归类为若干主题簇,并为家事法律业务撰写可直接上线的 FAQ 内容。适用于开展针对……的本地 SEO 工作。
TP6 的模型事件不是自动加载的,写了 static function onBeforeInsert() 也没用,除非你显式绑定。很多人卡在这一步,以为事件机制坏了。
- 事件方法必须是
static,且命名只能是onBeforeInsert、onAfterUpdate等固定名称 - 必须在模型首次使用前调用
self::event('before_insert', [static::class, 'onBeforeInsert']) - 推荐统一放在模型的
init()静态方法里,这是 TP6 文档明确推荐的做法 - 如果你在控制器里
new UserModel()后立刻save(),而init()还没执行(比如模型刚加载),事件照样不触发——TP6 不保证模型类一加载就自动运行init(),得确保它被调用过
where()->update() 为什么 slug 不更新?
这个操作根本不会经过模型层,它直连数据库执行 SQL,跳过所有修改器、事件、验证、类型转换。哪怕你写了完美的 setSlugAttr,在这里也等于不存在。
- 所有需要自动生成 slug 的场景,必须走模型实例的
save()或create() - 如果业务逻辑要求批量更新(比如改一批文章分类),不能用
ArticleModel::where(...)->update(['category_id' => 5]),而要先查出模型集合,再逐个save()或用saveAll() - 若性能敏感且确实要绕过模型,就只能在 SQL 层自己拼 slug(例如用 MySQL 的
REPLACE()+ 正则模拟),但可维护性差,且无法复用 PHP 里的 slug 逻辑(如多语言处理、特殊字符映射)
真正容易被忽略的一点:slug 字段的唯一性保障不在自动完成里,而在数据库约束或业务层校验。修改器只管“生成”,不管“重名”。如果你允许用户编辑标题后 slug 自动变更,就得加逻辑检测是否已存在同名 slug,否则可能产生重复 URL。这个检查必须在修改器之后、写入之前做,不能依赖数据库唯一索引自动报错——因为报错时数据已部分写入,事务难回滚。


















