It is possible to load relations to models in a Collection but it seems not to be possible to load count relation on a Collection.
For example, if I have these classes
class Post extends Model {
public function tags() {
return $this->hasMany(Tag::class);
}
}
class Tag extends Model {
public function posts() {
return $this->hasMany(Post::class);
}
}
and assuming that $tags
is a Collection of Tags, I can do $tags->load('posts')
to load relation posts
on every tag on $tags
but I cannot do $tags->load('posts_count')
to load posts_count
.
Is there a way to do that ?
Is this what you're looking for?
Counting Related Models
@devcircus It is related to that but not exactly what I am looking for.
If I have another class, let's say Category
class Category extends Model {
public function tags() {
return $this->hasMany(Tags::class);
}
}
From a Category
called $category
, I can get a collections of tags, related to that category :
$tags = $category->tags
and if I also need posts related to the tags, I can load then to the Collection of tags (in a single request in database) :
$tags->load('posts');
This is quite similar to Tag::with('posts')->all()
, if I want to get all tags.
But if I don't need to load the posts but I just want to get the number of posts related to the tags, I can call Tag::withCount('posts')->all()
.
My problem is then from my Category $category
, how to get its tags with the number of posts related to them ?
$tags = $category->tags
$tags->load('posts_count'); // Obviously not working, there is not such a relation
$tags->loadCount('posts'); // Could be great but does not exists
For the moment, I bypass the limitation doing
$tags = $category->tags()->withCount('posts')->get();
The problem is that relations are not loaded for $category
or the tags in $tags
:
$category->relationLoaded('tags'); \\ false
$tags->map->relationLoaded('category'); \\ collection of false
Another way would be to load the posts of all tags and counting the relation but it is obviously not very efficient as I just want the count of posts, not the posts themselves...
Is there another way to load posts_count on my Collection of tags with relations correctly loaded ?
Could it be a new feature (for example loadCount($relations) in Illuminate\Database\Eloquent\Collection )?
I tried to be as clear as possible. Do not hesitate if you have more questions
Can you try something like this?
Category::with('tags')->withCount('tags.posts')->find(1);
@srmklive, it seems not to be not a solution...
@bastien-phi Can't you do something like this.
Category::with(['tags' => function ($query) {
$query->withCount('posts');
}])->find(1);
Or if you already have your category loaded
$category->load(['tags' => function ($query) {
$query->withCount('posts');
}]);
That's what I finally did in that case.
But it does not solve the problem if I have a collection $tags
and I want to load some count relations...
I solved this like this:
LoadCountModel.php
use \Illuminate\Database\Eloquent\Model;
abstract class LoadCountModel extends Model {
public function loadCount($relations) {
if (is_string($relations)) {
$relations = func_get_args();
}
$query = $this->newQuery()->select($this->primaryKey)
->withCount($relations);
$query->eagerLoadCounts([$this]);
return $this;
}
public function newEloquentBuilder($query) {
return new EagerLoadCountsBuilder($query);
}
public function newCollection(array $models = Array()) {
return new LoadCountCollection($models);
}
}
LoadCountCollection.php
use \Illuminate\Database\Eloquent\Collection;
class LoadCountCollection extends Collection {
public function loadCount($relations) {
if (count($this->items) > 0) {
if (is_string($relations)) {
$relations = func_get_args();
}
$model = $this->first();
$query = $this->first()->newQuery()
->select($this->first()->getKeyName())
->withCount($relations);
$this->items = $query->eagerLoadCounts($this->items);
}
return $this;
}
}
EagerLoadCountsBuilder.php
use \Illuminate\Database\Eloquent\Builder;
class EagerLoadCountsBuilder extends Builder {
/**
* The counts that should be eager loaded.
*
* @var array
*/
protected $eagerLoadCount = [];
public function withCount($relations) {
$eagers = $this->parseWithRelations($relations);
$this->eagerLoadCount = array_merge($this->eagerLoadCount, $eagers);
return parent::withCount($relations);
}
public function eagerLoadCounts(array $models) {
if(count($models) > 0) {
$ids = [];
$key = $models[0]->getKeyName();
foreach($models as $model) {
$ids[] = $model->{ $key };
}
$results = $this->whereIn($key, $ids)->get();
$dictionary = [];
foreach($results as $result) {
$dictionary[$result->{ $key }] = $result;
}
foreach($models as $model) {
if(isset($dictionary[$model->{ $key }])) {
$model->forceFill($dictionary[$model->{ $key }]->toArray());
} else {
foreach($this->eagerLoadCount as $name => $constraints) {
$model->{ $name } = 0;
}
}
}
}
return $models;
}
}
Most helpful comment
@bastien-phi Can't you do something like this.
Or if you already have your category loaded