Framework: Ability to load count relations on Illuminate\Database\Eloquent\Collection

Created on 9 Feb 2017  路  7Comments  路  Source: laravel/framework

  • Laravel Version: 5.4.10
  • PHP Version: 7.0.13

Description:

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 ?

Most helpful comment

@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');
}]);

All 7 comments

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...
capture d ecran de 2017-02-10 09-37-40

@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;
    }
}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

YannPl picture YannPl  路  3Comments

SachinAgarwal1337 picture SachinAgarwal1337  路  3Comments

felixsanz picture felixsanz  路  3Comments

kerbylav picture kerbylav  路  3Comments

fideloper picture fideloper  路  3Comments