如何详细实现ThinkPHP的软删除接口?
- 内容介绍
- 文章标签
- 相关推荐
本文共计857个文字,预计阅读时间需要4分钟。
软删除通过在数据表中增加一个删除标记字段来实现,而不是直接删除数据。这样做不是通过添加一个+trait+就能对提供的外部功能进行增强,它依赖于模型配置、数据库字段和调用方式三者的紧密配合。若处理不当,可能会漏掉任意一环,导致数据被意外删除。以下是几个关键点:
模型必须显式启用 SoftDelete 且字段名对得上
只写 use SoftDelete; 不够,还必须确保:
-
$deleteTime值和数据库字段名完全一致(比如数据库是deleted_at,模型里就得写protected $deleteTime = 'deleted_at';) - 字段类型必须可空:MySQL 中该字段要定义为
DATETIME NULL或TIMESTAMP NULL,不能带NOT NULL DEFAULT CURRENT_TIMESTAMP - 如果用了
int类型存时间戳,必须在模型中补上protected $type = ['deleted_at' => 'integer'];,否则写入时会被转成字符串再强转为 int,结果常为 0
接口里调用 delete() 必须走模型实例或 destroy(),不能碰 Db
以下写法都会绕过软删除逻辑,变成物理删除:
Db::name('user')->where('id', 1)->delete()-
$user = new User(); $user->where('id', 1)->delete()(where()后已降级为 Db 查询器)
正确姿势只有两种:
立即学习“PHP免费学习笔记(深入)”;
- 实例方式:
$user = User::find(1); $user?->delete(); - 静态方式:
User::destroy(1);(注意不是User::destroy([1]),传单 ID 时不要包数组)
查询软删数据的接口必须显式加 withTrashed() 或 onlyTrashed()
所有模型默认自动过滤掉 deleted_at IS NOT NULL 的记录——这不是 bug,是设计。所以:
- 回收站列表接口必须用
User::withTrashed()->select(),否则永远查不到已删数据 - 恢复前校验接口必须用
User::onlyTrashed()->find($id),不能先find()再判断字段,因为默认查不到 - 关联数据也要单独处理:比如查用户及他软删过的文章,得写
User::with(['articles' => function ($q) { $q->withTrashed(); }])->find($id),父模型的withTrashed()不会透传
restore() 是实例方法,且失败不报异常
restore() 不是静态方法,也不能链式调用。常见错误:
-
User::where('id', 1)->restore()→ 报错Call to undefined method -
User::onlyTrashed()->find(1)->restore()→ 看似对,但若查不到(比如 ID 不存在或没软删过),返回null,再调restore()会 PHP Warning
安全写法:
$user = User::onlyTrashed()->find($id); if ($user && false === $user->restore()) { // 恢复失败:可能是 deleted_at 被手动改过、字段类型不匹配、或事务中断 }
注意:restore() 不触发 before_write 或 after_write 钩子,也不会重置 updated_at,如需同步更新时间,得手动 $user->save(['updated_at' => date('Y-m-d H:i:s')])。
本文共计857个文字,预计阅读时间需要4分钟。
软删除通过在数据表中增加一个删除标记字段来实现,而不是直接删除数据。这样做不是通过添加一个+trait+就能对提供的外部功能进行增强,它依赖于模型配置、数据库字段和调用方式三者的紧密配合。若处理不当,可能会漏掉任意一环,导致数据被意外删除。以下是几个关键点:
模型必须显式启用 SoftDelete 且字段名对得上
只写 use SoftDelete; 不够,还必须确保:
-
$deleteTime值和数据库字段名完全一致(比如数据库是deleted_at,模型里就得写protected $deleteTime = 'deleted_at';) - 字段类型必须可空:MySQL 中该字段要定义为
DATETIME NULL或TIMESTAMP NULL,不能带NOT NULL DEFAULT CURRENT_TIMESTAMP - 如果用了
int类型存时间戳,必须在模型中补上protected $type = ['deleted_at' => 'integer'];,否则写入时会被转成字符串再强转为 int,结果常为 0
接口里调用 delete() 必须走模型实例或 destroy(),不能碰 Db
以下写法都会绕过软删除逻辑,变成物理删除:
Db::name('user')->where('id', 1)->delete()-
$user = new User(); $user->where('id', 1)->delete()(where()后已降级为 Db 查询器)
正确姿势只有两种:
立即学习“PHP免费学习笔记(深入)”;
- 实例方式:
$user = User::find(1); $user?->delete(); - 静态方式:
User::destroy(1);(注意不是User::destroy([1]),传单 ID 时不要包数组)
查询软删数据的接口必须显式加 withTrashed() 或 onlyTrashed()
所有模型默认自动过滤掉 deleted_at IS NOT NULL 的记录——这不是 bug,是设计。所以:
- 回收站列表接口必须用
User::withTrashed()->select(),否则永远查不到已删数据 - 恢复前校验接口必须用
User::onlyTrashed()->find($id),不能先find()再判断字段,因为默认查不到 - 关联数据也要单独处理:比如查用户及他软删过的文章,得写
User::with(['articles' => function ($q) { $q->withTrashed(); }])->find($id),父模型的withTrashed()不会透传
restore() 是实例方法,且失败不报异常
restore() 不是静态方法,也不能链式调用。常见错误:
-
User::where('id', 1)->restore()→ 报错Call to undefined method -
User::onlyTrashed()->find(1)->restore()→ 看似对,但若查不到(比如 ID 不存在或没软删过),返回null,再调restore()会 PHP Warning
安全写法:
$user = User::onlyTrashed()->find($id); if ($user && false === $user->restore()) { // 恢复失败:可能是 deleted_at 被手动改过、字段类型不匹配、或事务中断 }
注意:restore() 不触发 before_write 或 after_write 钩子,也不会重置 updated_at,如需同步更新时间,得手动 $user->save(['updated_at' => date('Y-m-d H:i:s')])。

