Hyperf软删除需手动引入SoftDeletes trait、确保deleted_at字段存在且类型匹配、查询时显式调用withTrashed()等方法,三者缺一即静默失效。

必须手动引入 SoftDeletes,且数据库字段、模型配置、查询方式三者不匹配时,软删除会静默失效——不是报错,而是“看起来删了,其实没生效”,或“查不到已删数据”。
模型里怎么正确 use SoftDeletes
Hyperf 不像 Laravel 那样对 SoftDeletes 做了更多自动适配,必须显式引入 trait 并确保命名空间准确:
- 用
use Hyperf\Database\Model\SoftDeletes;(官方版),不是Illuminate\Database\Eloquent\SoftDeletes - 类定义中写
use SoftDeletes;,不能漏掉;或写成use SoftDeletesTrait - 如果用了第三方插件如
richard8768/hyperf-softdelete,要改用use Richard\HyperfSoftdelete\SoftDeletes;,且字段类型需匹配插件要求(比如它默认期望deleted_at是INT NOT NULL DEFAULT '0')
deleted_at 字段必须存在且类型兼容
字段缺失或类型错误是软删除“看似启用却无效”的最常见原因:
- 迁移中必须调用
$table->softDeletes();(生成DATETIME NULL字段),不能手写->timestamp('deleted_at')->nullable()—— 虽然效果相似,但 Hyperf 内部依赖softDeletes()的元信息做逻辑判断 - 若自定义字段名(如
is_deleted),除了重写$deletedAtColumn,还必须在$dates中声明该字段,否则时间解析失败,restore()可能静默失败 - 字段设为
NOT NULL DEFAULT CURRENT_TIMESTAMP是致命错误:软删时 UPDATE 会因无法写入NULL而报错或被忽略
查不到软删数据?先确认 withTrashed() 用对位置
默认所有查询(find()、all()、where()->get())都会自动加 WHERE deleted_at IS NULL。这不是 bug,是设计行为:
-
User::withTrashed()->find(123)才能拿到已软删的模型实例;User::find(123)返回null时,你根本分不清是“不存在”还是“已被软删” -
trashed()是实例方法,不能链在构造器后:User::where(...)->trashed()直接报错;必须先获取实例再调:User::withTrashed()->find(123)?->trashed() -
onlyTrashed()->find(1)返回null?说明这条记录压根没执行过delete(),或者delete()因事务未提交/异常中断而未落地
restore() 失败却不报错,往往卡在这三个点
$user->restore() 看似简单,但失败时经常无提示,查库发现 deleted_at 还在:
- 数据库字段被误设为
NOT NULL,导致 UPDATE SET deleted_at = NULL 失败(MySQL 默认会静默转成 0000-00-00) - 模型监听了
restoring事件,但回调里返回了false或抛出未捕获异常,中断了 restore 流程 - 在事务中调用
restore()后没commit,或者外层事务 rollback 了,查库自然看不到变化
最易被忽略的是:软删除不是“开关式功能”,它依赖字段、trait、查询构造器三者严格协同;少一个环节,就变成“删了等于没删”或“想恢复却找不到入口”。











