修改 Eloquent 查询以使其始终返回空集合的最有效方法是啥?

Posted

技术标签:

【中文标题】修改 Eloquent 查询以使其始终返回空集合的最有效方法是啥?【英文标题】:Most efficient way to modify an Eloquent query such that it always returns the empty collection?修改 Eloquent 查询以使其始终返回空集合的最有效方法是什么? 【发布时间】:2021-09-11 13:13:57 【问题描述】:

我经常遇到采用 Eloquent Query 对象并为其添加过滤条件的编程模式。 (如果您愿意,您可以将过滤器视为一个范围。)这主要发生在授权的上下文中,无论经过身份验证的用户可能会或可能不会看到结果的某些子集。

图案如下所示:

use Illuminate\Database\Eloquent\Builder;

public function applyFilter(Builder $query): Builder 
  $model = $query->getModel();
  if (!($model instanceof MyModel)) 
    throw new \InvalidArgumentException('the given query does not query for <MyModel>');
  
  $user = Auth::user();
  return $query->where(function (Builder $subquery) use ($user) 
      // Add some filter conditions to the query
      // Typically those depend on the currently authenticated user,
      // privileges of the user and and some properties of the model.
    );

有时碰巧已经很清楚查询不能返回任何结果,只需检查$user 对象或其他属性(如应用程序的状态),即使没有查询实际模型。在这些情况下,上面的过滤器函数会在查询中添加一个条件,该条件总是评估为false,如-&gt;where('1', '=', '2') 或类似的。虽然,这可以解决问题并且效果很好,但感觉不对。 Eloquent 仍然查询 DB 后端并收到(预期的)空结果。

主要问题:

有没有办法告诉 Eloquent 已知查询返回空结果并且不必进行实际查询,但 -&gt;get(和朋友)只是返回一个空集合?我正在考虑类似$query-&gt;toNoOp() 或类似的方法。

还有一个问题:

此外,还要考虑上面的构造被用作更复杂查询的一部分并且恰好在子查询中结束的情况。例如考虑以下情况:

$relatedModels = MyRelatedModel::query()
  ->where(...)
  ->whereHas('my_model', $q => applyFilter($q))
  ->get();

目前,这种构造会导致相当复杂的 SQL 查询

SELECT *
FROM my_related_models
WHERE
  ...
  AND
  EXISTS (
    SELECT *
    FROM my_models
    WHERE
      my_models.id = my_related_models.model_id
      AND
      1 = 2
  );

虽然这再次返回了预期的空结果,但如果内部查询是“无操作”这一事实可以传播到外部查询,那就更好了。换句话说,如果在EXIST 子句中使用子查询(由-&gt;whereHas 和朋友构造)并且这个子查询被称为“无操作”查询,那么外部查询也应该变成“无操作”查询。

【问题讨论】:

【参考方案1】:

如果您知道查询无效,您甚至不应该运行查询/执行它。只需让您的 API 返回空数组即可。

public function index() 
   if (! $this->shouldRunQuery(auth()->user())) 
       return response()->json([]);
   

   return response()->json(Model::query()->get());

在您的子问题中,您可以使用 when() 根据条件执行查询,这是由 Laravel 而不是 SQL 处理的。

Model::query()
    ->when($this->shouldRunQuery(auth()->user()), function ($query)          
        // your complex query
    )
->get();

一般来说,您似乎对解决SQL 中的问题非常着迷,而不是在执行SQL 之前处理条件和状态。我见过很多,但从未见过有人执行这种方法。

【讨论】:

我不确定我误解了这个问题,或者你看错了问题:) 如果这种方法可能是错误的,请清楚地告诉我。

以上是关于修改 Eloquent 查询以使其始终返回空集合的最有效方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

Laravel Eloquent 在 belongsToMany 关系上返回一个空集合

为啥 eloquent orm 上的查询语句结果返回空值

Eloquent 集合:计数和检测空

与where子句查询的Eloquent嵌套关系在false条件下返回集合

Laravel Eloquent - 如何将范围查询内的计算值作为模型列或集合属性返回

如何制作脚本/程序以使其始终运行?