[Из песочницы] Настройка Laravel relationships — подсчет комментариев (вольный перевод)

Представляю вам вольный перевод статьи «Tweaking Eloquent relations — how to get hasMany relation count efficiently?» с сайта softonsofa.com.

Работая с отношениями моделей, вы вероятнее всего хотели бы подсчитать количество полученных элементов (например, комментарии или лайки). Очевидно существуют способы для этого, но не всегда эффективные, особенно когда вы загружаете коллекцию моделей и их отношения.

Что ж, позвольте мне рассказать вам что мы можем с этим сделать.
hasMany relation

public function comments()
{
  return $this->hasMany('Comment');
}


Наиболее прямолинейным способом может быть использование одного из двух методов ниже (возможно, обернутый в метод типа getCommentsCount в Post модели):

[1] > $post = Post::first();
// object(Post)(
//   'incrementing' => true,
//   'timestamps' => true,
//   'exists' => true
// )
[2] > $post->comments->count();// 4
[3] > $post->comments()->count();// 4


Однако это все еще не лучшее решение.

  • $post→comments→count (); загружает коллекцию и возвращает количество ее элементов, не подходит если вам нужно получить только количество;
  • $post→comments ()→count (); лучше, не загружает коллекцию, но отправляет запрос каждый раз, когда мы запускаем метод.


Когда вам нужно получить коллекцию постов и посчитать комментарии к ним, то вы конечно можете использовать join, groupBy и все такое, но кого это волнует? Почему мы должны писать что вроде этого?

$posts = Post::leftJoin('comments', 'comments.post_id', '=', 'posts.id')
  ->select('posts.*', 'count(*) as commentsCount')
  ->groupBy('posts.id')
  ->get();


Если мы предпочитаем элегантный синтаксис в стиле Eloquent.

$posts = Post::with('commentsCount')->get();


Именно это нам и нужно, давайте наконец сделаем это. Чтобы заставить Eloquent предзагрузить колличество наших комментариев, мы должны создать в модели метод, возвращяющий Relation обьект.

public function comments()
{
  return $this->hasMany('Comment');
}
 
public function commentsCount()
{
  return $this->comments()
    ->selectRaw('post_id, count(*) as aggregate')
    ->groupBy('post_id');
}


Это конечно работает, но я не был бы самим собой, если бы оставил все как есть.

$post = Post::with('commentsCount')->first();
 
$post->commentsCount; 
$post->commentsCount->first(); 
$post->commentsCount->first()->aggregate; 


Давайте немного улучшим на код.

  • Будем использовать hasOne вместо hasMany, для того что бы избежать возврата коллекции с одним элементом
  • Будем использовать метод доступа, для того что бы просто посчитать комментарии

public function commentsCount()
{
  return $this->hasOne('Comment')
    ->selectRaw('post_id, count(*) as aggregate')
    ->groupBy('post_id');
}
 
public function getCommentsCountAttribute()
{
 
  if ( ! array_key_exists('commentsCount', $this->relations)) 
    $this->load('commentsCount');
 
  $related = $this->getRelation('commentsCount');
 
  
  return ($related) ? (int) $related->aggregate : 0;
}


Теперь намного проще и красивее работать с этим.


$post = Post::first();
$post->commentsCount; // 4

$posts = Post::with('commentsCount')->get();
$posts->first()->commentsCount;

© Habrahabr.ru