php - 计算相关 Laravel 模型的特征,仅在必要时访问数据库
问题描述
我想知道 Laravel 是否已经存在类似以下的内容?这是我写的称为 CarefulCount 的特征。
它的作用:它返回相关模型的计数(使用任何定义的关系),但仅在绝对必要时才访问数据库。首先,如果信息已经可用,它会尝试两个选项来避免访问数据库:
- 检索模型时是否使用检索的计数
withCount('relation')
- 即确实$model->relation_count
存在?如果是这样,只需返回。 - 关系是否已预先加载?如果是这样,在不打数据库的情况下计算 Eloquent 集合中的模型,使用
$model->relation->count()
. - 只有这样才诉诸调用
$model->relation()->count()
从数据库中检索计数。
要为任何模型类启用它,您只需将 trait 包含在use CarefulCount
. 然后,您可以调用$model->carefulCount('relation')
任何已定义的关系。
例如,在我的应用程序中,有一张桌子与桌子和桌子suburbs
都具有多重关系(即,一个郊区可以有许多用户和许多教堂)。只需添加到模型中,我就可以同时调用和。users
churches
use CarefulCount
Suburb
$suburb->carefulCount('users')
$suburb->carefulCount('churches')
我的用例:我已经多次遇到这种情况 - 我需要一些相关模型,但它位于我的应用程序的较低级别部分,可以从多个地方调用。所以我不知道模型是如何被检索到的,以及计数信息是否已经存在。
在这些情况下,默认情况下会调用$model->relation()->count()
. 但这会导致N+1 查询问题。
事实上,具体的触发因素来自于将 Marcel Pociot 的优秀Laravel N+1 Query Detector包添加到我的项目中。它出现了许多我没有发现的 N+1 查询问题,其中大多数是我已经预先加载相关模型的情况。但是在我的 Blade 模板中,我使用策略来启用或禁用记录的删除;delete($user, $suburb)
我SuburbPolicy
班级的方法包括:
return $suburb->users()->count() == 0 && $suburb->churches()->count() == 0;
这引入了 N+1 问题——显然我不能在我的 Policy 类(或我的 Model 类本身)中假设用户和教堂是热切加载的。但是随着CarefulCount
特征的添加,它变成了:
return $suburb->carefulCount('users') == 0 && $suburb->carefulCount('churches') == 0;
瞧!对此进行修补并检查查询日志,它可以工作。例如,使用用户数:
- 如果
$suburb
使用 检索Suburb::withCount('users')
,则不执行额外查询。 - 同样,如果它是使用 检索的
Suburb::with('users')
,则不会执行额外的查询。 - 如果以上均未完成,则
select count(*)
执行查询以检索计数。
正如我所说,我很想知道这样的东西是否已经存在并且我还没有找到它(无论是在核心还是在包中)——或者我是否错过了一些明显的东西。
这是我的特征的代码:
use Illuminate\Support\Str;
trait CarefulCount
{
/**
* Implements a careful and efficient count algorithm for the given
* relation, only hitting the DB if necessary.
*
* @param string $relation
*
* @return integer
*/
public function carefulCount(string $relation): int
{
/*
* If the count has already been loaded using withCount('relation'),
* use the 'relation_count' property.
*/
$prop = Str::snake($relation) . "_count";
if (isset($this->$prop)) {
return $this->$prop;
}
/*
* If the related models have already been eager-loaded using
* with('relation'), count the loaded collection.
*/
if ($this->relationLoaded($relation)) {
return $this->$relation->count();
}
/*
* Neither loaded, so hit the database.
*/
return $this->$relation()->count();
}
}