首页 > 解决方案 > 计算相关 Laravel 模型的特征,仅在必要时访问数据库

问题描述

我想知道 Laravel 是否已经存在类似以下的内容?这是我写的称为 CarefulCount 的特征。

它的作用:它返回相关模型的计数(使用任何定义的关系),但仅在绝对必要时才访问数据库。首先,如果信息已经可用,它会尝试两个选项来避免访问数据库:

  1. 检索模型时是否使用检索的计数withCount('relation')- 即确实$model->relation_count存在?如果是这样,只需返回。
  2. 关系是否已预先加载?如果是这样,在不打数据库的情况下计算 Eloquent 集合中的模型,使用$model->relation->count().
  3. 只有这样才诉诸调用$model->relation()->count()从数据库中检索计数。

要为任何模型类启用它,您只需将 trait 包含在use CarefulCount. 然后,您可以调用$model->carefulCount('relation')任何已定义的关系。

例如,在我的应用程序中,有一张桌子与桌子和桌子suburbs都具有多重关系(即,一个郊区可以有许多用户和许多教堂)。只需添加到模型中,我就可以同时调用和。userschurchesuse CarefulCountSuburb$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;

瞧!对此进行修补并检查查询日志,它可以工作。例如,使用用户数:

正如我所说,我很想知道这样的东西是否已经存在并且我还没有找到它(无论是在核心还是在包中)——或者我是否错过了一些明显的东西。

这是我的特征的代码:

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

标签: phplaraveleloquenteloquent-relationship

解决方案


推荐阅读