
本文介绍如何通过 Eloquent 全局作用域,确保 Question 模型查询结果仅包含关联未软删除用户的记录,彻底避免 find() 或 get() 返回与已删除用户关联的数据。
本文介绍如何通过 eloquent 全局作用域,确保 `question` 模型查询结果仅包含关联未软删除用户的记录,彻底避免 `find()` 或 `get()` 返回与已删除用户关联的数据。
在 Laravel 中,软删除(Soft Deletes)通过 deleted_at 字段标记记录状态,但默认情况下,Eloquent 的 belongsTo 关系不会自动过滤软删除的父级模型——正如你在 Question::find($id) 中观察到的:即使关联的 User 已被软删除(deleted_at IS NOT NULL),该 Question 仍能正常加载,且 question->user 可能返回一个“已删除但未被排除”的 User 实例。
根本原因在于:关系定义中的 whereNull('deleted_at') 仅影响 懒加载(eager loading)或显式调用 $question->user 时的关联查询,而对主模型(Question)本身的检索(如 find()、all()、where(...)->get())完全无约束力。因此,必须在 Question 查询层面主动排除“关联用户已被软删除”的记录。
✅ 推荐方案:使用 全局作用域(Global Scope),确保所有 Question 查询(无论是否显式调用 with())均自动应用过滤逻辑。
步骤一:在 Question 模型中注册全局作用域
在 app/Models/Question.php 中添加以下代码(注意引入 Builder 类):
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
class Question extends Model
{
protected $fillable = ['title', 'question'];
public function user()
{
return $this->belongsTo(User::class)->whereNull('deleted_at');
}
// ✅ 全局作用域:强制所有 Question 查询只返回关联有效用户的数据
protected static function booted()
{
static::addGlobalScope('activeUsers', function (Builder $builder) {
$builder->whereHas('user', function ($query) {
$query->whereNull('deleted_at');
});
});
}
}
?
whereHas('user', ...)确保 Question 表与 User 表存在有效 JOIN 条件,并过滤掉user.deleted_at IS NOT NULL的行;它会在生成 SQL 时自动添加INNER JOIN users ON questions.user_id = users.id AND users.deleted_at IS NULL类似逻辑。
步骤二:验证效果
启用全局作用域后,以下操作将自动排除关联软删除用户的 Question:
// ✅ 返回 null(若该 ID 对应 Question 的 user 已软删除)
$question = Question::find(123);
// ✅ 返回空集合(不包含任何关联已删除用户的 Question)
$questions = Question::where('title', 'like', '%Laravel%')->get();
// ✅ 同样生效(with 不影响全局作用域,但主查询已过滤)
$questions = Question::with('user')->get();
⚠️ 注意事项
-
性能考虑:
whereHas会引入子查询或 JOIN,若数据量极大且高频查询,建议为users.deleted_at字段添加数据库索引:CREATE INDEX idx_users_deleted_at ON users(deleted_at);
-
临时禁用作用域:如需在特定场景下绕过该过滤(例如后台审核),可使用
withoutGlobalScopes():$question = Question::withoutGlobalScopes()->find(123);
-
作用域命名唯一性:
'activeUsers'是作用域标识符,确保在同一模型中不重复;若需复用,建议封装为独立作用域类(参考 Laravel 官方文档)。
✅ 总结
全局作用域是 Laravel 中统一约束模型查询行为的最佳实践。相比手动在每个查询链中追加 whereHas,它保障了数据一致性与维护性——从此 Question::find() 不再返回“幽灵关联”,系统逻辑更健壮、语义更清晰。











