ThinkPHP8模型事件深入实战:自动字段填充、操作日志与缓存联动

2026-08-09 0 893

项目里有一张用户表,每次新增用户的时候,都要手动写状态、来源渠道、最后登录时间这些字段。一开始还能忍,后来越加越多,搞得新增用户的代码里全是setxxx。后来看到同事在模型里写了个onAfterInsert,才意识到ThinkPHP8的模型事件能把这些重复逻辑统统收编。

这篇文章不聊虚的,直接拿三个实际场景来拆解:自动填充字段、记录操作日志、更新缓存。看完你也能把模型事件用起来。

先搞清楚模型事件在ThinkPHP8里怎么玩

ThinkPHP8的模型事件其实就是一组在模型操作时自动调用的方法。你不需要手动触发,框架会在新增、更新、删除等操作的前后自动调用。常见的事件有这些:

onBeforeInsert 新增前
onAfterInsert  新增后
onBeforeUpdate 更新前
onAfterUpdate  更新后
onBeforeDelete 删除前
onAfterDelete  删除后
onBeforeWrite  写入前(含新增和更新)
onAfterWrite   写入后

定义方式很简单,在模型类里写一个名为onAfterInsert之类的静态方法就行。比如:

class User extends Model
{
    public static function onAfterInsert(User $model)
    {
        // 新增后要做的逻辑
    }
}

方法接收当前模型实例,通过$model->getData()可以拿到所有字段数据。注意这个User是模型类名,方法参数用User $model是为了类型提示,写不写都行。

案例一:自动填充字段,新增用户不再重复劳动

假设在新增用户时,必定要生成一个用户编号,同时设置默认头像和用户状态。原来写控制器的时候可能这样:

$user = new User();
$user->username = $data['username'];
$user->status = 1;
$user->avatar = '/default.png';
$user->userno = date('YmdHis') . rand(1000, 9999);
$user->save();

每个新增的地方都要写这三行,万一以后默认头像换了,还得全局搜。现在全部收进模型事件里:

class User extends Model
{
    public static function onBeforeInsert(User $model)
    {
        // 自动填充字段
        $model->status = 1;
        $model->avatar = '/default.png';
        $model->userno = date('YmdHis') . rand(1000, 9999);
    }
}

控制器里只保留业务字段:

$user = User::create($data);

这样不管哪里新增用户,这些默认字段都会被自动加上。如果你也想在更新前自动修改某个字段,类似地写onBeforeUpdate即可。

案例二:记录操作日志,不用在业务代码里埋雷

很多时候需要记录谁在什么时候改了什么数据。最常见的做法是在更新用户信息的代码里手动写日志逻辑,但很容易漏。模型事件可以统一拦截。

例如我要记录用户新增、更新、删除的操作日志,先创建一个日志表模型UserLog

class UserLog extends Model
{
    protected $name = 'user_log';
    protected $autoWriteTimestamp = true;
}

然后在User模型中写三个事件:

class User extends Model
{
    public static function onAfterInsert(User $model)
    {
        self::writeLog($model, '新增用户');
    }

    public static function onAfterUpdate(User $model)
    {
        self::writeLog($model, '更新用户');
    }

    public static function onAfterDelete(User $model)
    {
        self::writeLog($model, '删除用户');
    }

    protected static function writeLog(User $model, string $action)
    {
        // 排除一些不需要记录的字段
        $data = $model->getData();
        unset($data['password']);

        UserLog::create([
            'user_id' => $model->id ?? 0,
            'action'  => $action,
            'data'    => json_encode($data, JSON_UNESCAPED_UNICODE),
            'ip'      => request()->ip(),
            'time'    => time(),
        ]);
    }
}

这里要注意的是,删除操作之后$model->id仍然存在,因为数据已经查出来了。如果你的模型是软删除,事件同样会触发,这点后面再说。

这样业务代码里只需要调用$user->save()$user->delete(),日志自动留下。无论哪里操作了用户表,你都能从日志中追溯。

案例三:缓存联动,更新完数据立刻清掉旧缓存

缓存是提升性能的利器,但缓存一致性很头疼。以前我习惯在更新数据后手动删除缓存:

$user = User::find(1);
$user->nickname = '新名字';
$user->save();
cache('user_info_' . $user->id, null);

这个cache(... null)删缓存操作很容易漏。有了模型事件,就可以把它固定在onAfterWrite事件里,因为写操作包括新增和更新:

public static function onAfterWrite(User $model)
{
    // 如果存在这个缓存键,就删掉
    if (cache('user_info_' . $model->id)) {
        cache('user_info_' . $model->id, null);
    }
}

其他地方读取缓存的方法不变:

$userInfo = cache('user_info_' . $id);
if (!$userInfo) {
    $userInfo = User::find($id)->toArray();
    cache('user_info_' . $id, $userInfo, 3600);
}

因为模型事件在写成功后自动清缓存,所以任何入口只要修改了用户信息,下次读取就会自动回源,不需要在每个修改的地方手动清。

还有一个进阶玩法。你可能希望只有在某些字段变化时才清缓存,比如余额和积分,不需要每次昵称变化都清。可以在事件里判断模型的修改字段:

public static function onAfterUpdate(User $model)
{
    $changed = $model->getChangedData();
    if (isset($changed['money'])) {
        cache('user_money_' . $model->id, null);
    }
}

getChangedData() 是ThinkPHP8提供的方法,能拿到本次更新中实际被修改的字段值。这样缓存清理更精细,也更高效。

再聊聊模型事件和模型观察者的区别

ThinkPHP8还提供了观察者(Observer)机制。简单说,观察者是一个单独的类,专门用来监听模型事件。适合一个模型有很多事件逻辑,或者多个模型共用一套事件逻辑。

定义一个观察者类:

class UserObserver
{
    public function afterInsert(User $user)
    {
        // 新增后逻辑
    }

    public function afterUpdate(User $user)
    {
        // 更新后逻辑
    }
}

然后在模型里声明:

class User extends Model
{
    protected $observer = UserObserver::class;
}

或者全局注册。我自己习惯把日志和缓存这种横切逻辑放在观察者里,而把字段填充这种跟模型强相关的事件放在模型里。看你项目结构,没有绝对对错。

几个容易踩的坑

模型事件虽然方便,但用不好也会出麻烦。下面这几个坑我都踩过,写出来让你避一下。

第一个坑:事件里调用save()会无限递归。比如你在onAfterInsert里又调用了$model->save(),那会再次触发插入事件,循环下去。所以事件里尽量不要去操作当前模型,除非你明确知道自己在干嘛。

第二个坑:用create()方法时同样会触发事件。很多新手不知道User::create($data)$user->save()其实都会触发模型事件,这是好事,但有时候你只想简单插入一行,不想触发事件,那需要把事件临时关闭。ThinkPHP8提供了Model::unlinkEvent()静态方法:

Model::unlinkEvent('before_insert');
User::create($data);
Model::linkEvent('before_insert');

记得操作完恢复,不然后续模型的事件都不执行了。

第三个坑:软删除也会触发删除事件。如果你使用SoftDelete特性,执行delete()时实际上执行的是更新操作,但模型事件还是按delete来触发。也就是说onAfterDelete会被调用,但onAfterUpdate不会被调用。别搞混,不然日志里记录的内容可能不对。

总结一下模型事件给我带来的好处

模型事件最大的价值,就是让那些跟数据本身相关的横切逻辑不再散落在各个控制器里。字段填充、日志记录、缓存清理,这些都是对数据操作的“副作用”,放在模型这一层再合适不过。

前阵子我把项目里所有新增/更新用户的地方都清了一遍,删掉了大量重复代码,只保留业务独有的逻辑。现在新增一个用户接口,只需要五行代码,剩下的交给模型事件去处理。如果以后需要改变用户编号规则或者缓存策略,只需要改一个地方,别提多舒服了。

建议你先挑一个简单的模型试试,从字段填充开始,感受一下模型事件的魔力。然后再慢慢加上日志和缓存,相信你会回来感谢我的。

ThinkPHP8模型事件深入实战:自动字段填充、操作日志与缓存联动
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

版权声明:
本站资源有的来自互联网收集整理,本站纯免费分享提供学习使用,如果侵犯了您的合法权益,请联系本站我们会及时删除。
本站资源仅供研究、学习交流之用,免费开源项目不代表完全可商用,若商业用途请先咨询开发企业能否商用,否则产生的一切后果将由下载用户自行承担。
原创板块未经允许不得转载,否则将追究法律责任。

淘吗网 thinkphp ThinkPHP8模型事件深入实战:自动字段填充、操作日志与缓存联动 https://www.taomawang.com/server/thinkphp/2513.html

下一篇:

已经没有下一篇了!

常见问题

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务