restore() 必须先用 withtrashed() 或 onlytrashed() 查询软删除记录,否则 find() 返回 null 导致调用失败;恢复前需校验唯一索引冲突并预检未删除同邮箱记录;恢复后须调用 refresh() 同步 deleted_at 属性。

restore() 调用前必须查到带 deleted_at 的实例
直接 User::find(1)->restore() 几乎总是失败——因为 find() 默认跳过软删除记录,返回 null,调用 restore() 就是 null->restore(),PHP 报错或静默忽略。
正确做法是先用 withTrashed() 或 onlyTrashed() 查出真实存在且 deleted_at 非空的模型实例:
✅ $user = User::withTrashed()->find(123); $user->restore();
✅ $user = User::onlyTrashed()->where('email', 'x@y.z')->first(); $user?->restore();
注意:如果 $user 是 null(比如 ID 不存在,或已被 forceDelete() 物理删掉),restore() 不会报错,但数据库无变化;建议加空值判断或改用 restoreOrFail()。
唯一索引冲突不是 bug,是联合索引生效的必然结果
当你给 email 加了联合唯一索引 ['email', 'deleted_at'] 后,MySQL 允许多条 email=user@example.com 记录共存——只要它们的 deleted_at 值互不相同(比如一个是 2024-01-01 10:00:00,另一个是 2024-02-01 15:30:00)。但一旦你尝试 restore() 一条已被软删的记录,而表里已存在另一条未删除的同邮箱记录,就会触发 SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry。
这不是 Laravel 的问题,是数据库在执行 UPDATE deleted_at = NULL 时发现违反唯一约束。
必须手动预检:
• 查询当前是否存在未删除的同邮箱记录:User::where('email', $user->email)->whereNull('deleted_at')->where('id', '!=', $user->id)->exists()
• 若存在,不能直接 restore(),得先处理冲突(如软删新记录、提示用户、或拒绝恢复)
• 别依赖 restore() 返回值判断成败——它失败时只返回 false,不抛异常,容易被忽略
PHP中文网提供Laravel 13.2.0版本下载,Laravel框架 是基于 PHP 8.3+ 的高性能框架,官方推荐通过 Composer 安装。它内置 AI SDK、JSON:API Resources 及原生向量搜索,支持属性驱动开发与队列路由,大幅提升开发效率。相比旧版,13.2.0 优化了缓存 TTL 管理与实时通信,无需 Redis 即可横向扩展。作为现代 Web 开发首选,它兼顾安全与极速体验,助您快速构建企业级应用。
MySQL 8.0+ 可用函数索引替代联合索引,但迁移要手写
Laravel 的 Schema::table(...)->unique(['email', 'deleted_at']) 是兼容性最广的方案,但它要求所有已软删记录的 deleted_at 必须为 NULL(而非时间字符串或 '0000-00-00 00:00:00'),否则索引无效。如果你用的是 MySQL 8.0+,更干净的做法是建函数索引:DB::statement("CREATE UNIQUE INDEX users_email_active_unique ON users (email) WHERE deleted_at IS NULL");
这样索引只覆盖未删除行,deleted_at 字段可保持原生时间戳(Laravel 默认行为),不用改数据或迁移到 nullable()。
但注意:
• Schema 构建器不支持这种语法,必须用 DB::statement()
• 索引名不能和 Laravel 自动生成的重复(比如避免叫 users_email_unique)
• SQLite 和低版本 MySQL 不支持,上线前务必确认数据库版本与引擎(InnoDB)
恢复后立刻 refresh(),否则 deleted_at 属性还是旧值
$user->restore() 只更新数据库,PHP 对象里的 $user->deleted_at 仍是原来的时间戳。后续代码若直接读这个属性(比如 if ($user->trashed())),会误判为“仍被删除”。
解决方法只有两个:
• $user->refresh():重新加载该模型的所有字段,轻量且常用
• $user = $user->fresh():重新查询整条记录,适合涉及关联或计算属性的场景
别用 $user->isForceDeleting() 或 $user->wasRecentlyCreated 这类内部状态来推断恢复结果——它们和 deleted_at 无关,且文档未承诺稳定性。
最容易被忽略的是事件监听器里没做 refresh():比如你在 restored 事件里发通知,但通知内容还基于旧的 deleted_at 值,那就白做了。










