PHP项目Laravel withTrashed等查询

wen PHP项目 196

Laravel 软删除进阶指南:深入解析 withTrashed() 与关联查询的实战艺术


📚 目录导读

  1. 为什么需要软删除? —— 从物理删除到逻辑删除的架构演进
  2. Laravel 软删除核心机制 —— SoftDeletes Trait 与数据库结构设计
  3. withTrashed() 的魔法 —— 突破默认查询范围,找回“已删除”数据
  4. onlyTrashed() 与 restore() —— 特定场景的精准操作
  5. 关联模型中的软删除陷阱 —— 如何正确使用 withTrashed() 处理嵌套关系
  6. 性能优化与索引策略 —— 避免全表扫描的实战技巧
  7. 高频问题专家问答 —— 解决你最常见的 5 个困惑

为什么需要软删除?

PHP项目Laravel withTrashed等查询

在电商、CMS 或后台管理系统中,直接使用 DELETE FROM 永久移除数据是危险的,用户误操作、审计需求、关联数据完整性(如订单详情)都要求我们保留数据痕迹,软删除(Soft Delete)通过为表添加 deleted_at 时间戳字段,将物理删除转化为“标记删除”,Laravel 的 SoftDeletes Trait 会自动在查询中追加 WHERE deleted_at IS NULL 条件,从而默认“隐藏”已删除记录。

Laravel 软删除核心机制

你需要在模型中引入 Illuminate\Database\Eloquent\SoftDeletes:

use Illuminate\Database\Eloquent\SoftDeletes;
class Post extends Model
{
    use SoftDeletes;
    protected $dates = ['deleted_at']; // Laravel 7+ 无需手动声明
}

数据库迁移需增加可空时间戳字段:

Schema::table('posts', function (Blueprint $table) {
    $table->softDeletes(); // 等价于 $table->timestamp('deleted_at')->nullable();
});

Post::all() 自动生成 select * from posts where posts.deleted_at is null。

withTrashed() 的魔法

当我们需要在回收站、报表或管理员后台展示包括已删除记录在内的全量数据时,必须打破默认过滤。withTrashed() 方法会暂时取消该模型的全局作用域:

// 获取所有帖子(含已删除)
$posts = Post::withTrashed()->get();
// 在分页中使用
$trashedAndActive = Post::withTrashed()->paginate(15);

注意:withTrashed() 对 find() 同样有效:Post::withTrashed()->find($id) 可以尝试找回特定已删除模型。

onlyTrashed() 与 restore()

  • onlyTrashed() 仅获取已软删除的数据:
    $trashedPosts = Post::onlyTrashed()->where('category_id', 5)->get();
  • restore() 恢复数据:
    Post::onlyTrashed()->where('user_id', 42)->restore();

关联模型中的软删除陷阱(重点)

假设你有 User 和 Post 模型,且 User 存在多个 Post,默认情况下,$user->posts 只会返回未删除的帖子。如果你希望加载该用户的所有帖子(包括软删除的),必须在关联定义或查询时显式声明:

// 方法一:在关联定义中临时移除约束
public function allPosts()
{
    return $this->hasMany(Post::class)->withTrashed();
}
// 方法二:在查询时动态处理
$user = User::find(1);
$posts = $user->posts()->withTrashed()->get();

更为复杂的场景:当关联链路上有多个模型均使用软删除(如 Post 有 Comment,而 Comment 也软删除),请使用 withTrashed() 组合:

$postsWithAllComments = Post::withTrashed()
    ->with(['comments' => function ($query) {
        $query->withTrashed();
    }])->get();

性能优化与索引策略

  • 必加索引:为 deleted_at 字段创建复合索引(与业务查询字段联合),例如频繁按 user_id 和 deleted_at 查询,则索引 (user_id, deleted_at) 可显著加速 onlyTrashed() 的过滤。
  • 避免 COUNT 滥用:在后台统计总记录数时,Post::withTrashed()->count() 会扫描全表,若数据量极大,可维护冗余计数器或使用分区表。
  • 警惕 restore() 的风暴:在批量恢复上下行数据时,请使用 chunkById() 分批处理,避免内存溢出。

高频问题专家问答

Q1:为什么我在关联模型里调用 withTrashed() 失效了? A:请确认你是否在关联闭包中正确使用,而非在外部链式调用。$user->posts()->withTrashed() 是有效的,但 $user->posts 却不行,因为访问属性会立即触发查询,请统一使用关联方法 posts()。

Q2:软删除的数据还能使用唯一索引吗? A:可以,但若业务要求“用户名唯一”并允许软删除,则需将 deleted_at 加入唯一索引(复合唯一索引),否则可能阻止新用户注册相同用户名。

Q3:withTrashed() 会破坏全局作用域(如 where('status', 1))吗? A:不会。withTrashed() 仅移除软删除作用域,其他全局作用域仍然有效。

Q4:如何永久移除一条软删除记录? A:调用 forceDelete() 即可物理删除,该操作不可逆,推荐仅在明确权限下使用。

Q5:在 API 资源响应中,如何区分已删除和未删除数据? A:检查 $model->trashed() 方法,返回布尔值,可在资源类的 toArray() 中转置为 'is_deleted' 字段。


掌握 withTrashed() 不只是会调用一个方法,更是理解 Laravel 查询作用域与模型生命周期的关键,在实际项目中,建议在你的 Repositories 或自定义 Query Scopes 中封装这些逻辑,让代码更可读,善用软删除,为你的数据穿上“防弹衣”。

抱歉,评论功能暂时关闭!

友情链接攻略窝 · 礼包窝 · 配置窝 · 换算窝 · 算法窝 · 查文窝 · 手册窝 · 问答窝 · 全文搜 · 手册库
相关站点礼包窝 · 查文窝