PHP项目Laravel withTrashed等查询

wen PHP项目 6

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();

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

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

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

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

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

性能优化与索引策略

  • 必加索引:为 deleted_at 字段创建复合索引(与业务查询字段联合),例如频繁按 user_iddeleted_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 中封装这些逻辑,让代码更可读,善用软删除,为你的数据穿上“防弹衣”。

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