Как решить проблему с N + 1 запросом БД при выборке модели с getNameAttribute?

Есть система отзывов для постов, которая работает так: К посту можно оставить отзыв, а к отзыву - комментарий. Получается 3 модели Post, Review, Comment. В модели Post я создаю новый аттрибут, который возвращает количество комментариев в посте через отзывы этого поста.

public function getCommentsCountAttribute() : int
    {
        return $this->hasManyThrough(
                Review::class,
                Comment::class,
                'commentable_id',
                'reviewable_id'
            )
            ->where('commentable_type', Review::class)
            ->count();
    }


Но так как это атрибут поста (comments_count), то он высчитывается для каждого поста, получается что помимо выборки 30 постов, я делаю по одному запросу на каждый пост для подсчета количества комментариев, получается что я дополнительно делаю 30 запросов.

Есть ли вариант добавлять выборку атрибута как и отношение через Post::with() или необходимо подгружать заранее все отзывы в посты, с заранее подгруженными комментариям, а затем в методе работать с уже загруженными данными? Или есть способ лучше?
  • Вопрос задан
  • 71 просмотр
Решения вопроса 2
alexey-m-ukolov
@alexey-m-ukolov Куратор тега Laravel
Вот это должно работать:
public function reviews(): HasManyThrough
{
    return $this->hasManyThrough(
        Review::class,
        Comment::class,
        'commentable_id',
        'reviewable_id'
    );
}

public function getCommentsCountAttribute(): int
{
    return $this
        ->reviews
        ->where('commentable_type', Review::class)
        ->count();
}


Ну и да, при выборке вы должны указать, что хотите для коллекции постов жадно загрузить comments.
Ответ написан
@jazzus
Получается 3 модели Post, Review, Comment.

Нет смысла делать модель Review. Это одинаковая с Comment сущность. Добавляется поле parent_id и "Review" это будет Comment со скоупом whereNull parent_id.
public function scopeParents($query)
{
    return $query->whereNull('parent_id');
}

А у комментариев отзыва parent_id == id отзыва. И будет структура, как на Тостере.
Если отзывы и комменты добавляются к разным моделям (посты, товары и тд) то стандартная полиморфная связь.
В модели коммент
public function commentable():
{
    return $this->morphTo();
}


В трейте, который нужно добавить ко всем моделям у которых есть комменты

public function comments()
{
    return $this->morphMany('App\Models\Comment', 'commentable');
}


Количество комментариев считать как обычно методом withCount. Просто для "отзывов" добавлять скоуп ->parents() к отношениям.
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы