如何详细实现ThinkPHP的软删除接口?

2026-04-30 11:272阅读0评论SEO资讯
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计857个文字,预计阅读时间需要4分钟。

如何详细实现ThinkPHP的软删除接口?

软删除通过在数据表中增加一个删除标记字段来实现,而不是直接删除数据。这样做不是通过添加一个+trait+就能对提供的外部功能进行增强,它依赖于模型配置、数据库字段和调用方式三者的紧密配合。若处理不当,可能会漏掉任意一环,导致数据被意外删除。以下是几个关键点:

模型必须显式启用 SoftDelete 且字段名对得上

只写 use SoftDelete; 不够,还必须确保:

  • $deleteTime 值和数据库字段名完全一致(比如数据库是 deleted_at,模型里就得写 protected $deleteTime = 'deleted_at';
  • 字段类型必须可空:MySQL 中该字段要定义为 DATETIME NULLTIMESTAMP 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_writeafter_write 钩子,也不会重置 updated_at,如需同步更新时间,得手动 $user->save(['updated_at' => date('Y-m-d H:i:s')])

标签:PHPThinkPHP

本文共计857个文字,预计阅读时间需要4分钟。

如何详细实现ThinkPHP的软删除接口?

软删除通过在数据表中增加一个删除标记字段来实现,而不是直接删除数据。这样做不是通过添加一个+trait+就能对提供的外部功能进行增强,它依赖于模型配置、数据库字段和调用方式三者的紧密配合。若处理不当,可能会漏掉任意一环,导致数据被意外删除。以下是几个关键点:

模型必须显式启用 SoftDelete 且字段名对得上

只写 use SoftDelete; 不够,还必须确保:

  • $deleteTime 值和数据库字段名完全一致(比如数据库是 deleted_at,模型里就得写 protected $deleteTime = 'deleted_at';
  • 字段类型必须可空:MySQL 中该字段要定义为 DATETIME NULLTIMESTAMP 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_writeafter_write 钩子,也不会重置 updated_at,如需同步更新时间,得手动 $user->save(['updated_at' => date('Y-m-d H:i:s')])

标签:PHPThinkPHP