Представляю вам вольный перевод статьи "Tweaking Eloquent relations – how to get hasMany relation count efficiently?" с сайта softonsofa.com.
Работая с отношениями моделей, вы вероятнее всего хотели бы подсчитать количество полученных элементов (например, комментарии или лайки). Очевидно существуют способы для этого, но не всегда эффективные, особенно когда вы загружаете коллекцию моделей и их отношения.
Что ж, позвольте мне рассказать вам что мы можем с этим сделать.
hasMany relation
Наиболее прямолинейным способом может быть использование одного из двух методов ниже (возможно, обернутый в метод типа getCommentsCount в Post модели):
Однако это все еще не лучшее решение.
Когда вам нужно получить коллекцию постов и посчитать комментарии к ним, то вы конечно можете использовать join, groupBy и все такое, но кого это волнует? Почему мы должны писать что вроде этого?
Если мы предпочитаем элегантный синтаксис в стиле Eloquent.
Именно это нам и нужно, давайте наконец сделаем это. Чтобы заставить Eloquent предзагрузить колличество наших комментариев, мы должны создать в модели метод, возвращяющий Relation обьект.
Это конечно работает, но я не был бы самим собой, если бы оставил все как есть.
Давайте немного улучшим на код.
Теперь намного проще и красивее работать с этим.
Работая с отношениями моделей, вы вероятнее всего хотели бы подсчитать количество полученных элементов (например, комментарии или лайки). Очевидно существуют способы для этого, но не всегда эффективные, особенно когда вы загружаете коллекцию моделей и их отношения.
Что ж, позвольте мне рассказать вам что мы можем с этим сделать.
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;