Skip to content

Instantly share code, notes, and snippets.

@vluzrmos
Last active September 23, 2021 09:00
Show Gist options
  • Save vluzrmos/70d32ddd2569cdc561efd4df43d3abbe to your computer and use it in GitHub Desktop.
Save vluzrmos/70d32ddd2569cdc561efd4df43d3abbe to your computer and use it in GitHub Desktop.
Laravel 5.1 withCount(relation) method.
<?php
namespace App;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOneOrMany;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Database\Query\Builder as QueryBuilder;
use Illuminate\Database\Query\Expression;
trait HasWithCountScope
{
/**
* @param QueryBuilder|EloquentBuilder $query
* @param string|array $name
*/
public function scopeWithCount($query, $name)
{
$relations = is_array($name) ? $name : array_slice(func_get_args(), 1);
if (empty($query->columns)) {
$query->select(['*']);
}
foreach ($relations as $key => $constraint) {
list($name, $alias, $constraint) = $this->parseWithCountConstraint($key, $constraint);
$query->selectSub($this->getRelatedCountSubQuery($name, $constraint), $alias);
}
}
/**
* @param string $name
* @param \Closure|null $constraint
* @return \Illuminate\Database\Query\Builder
*/
public function getRelatedCountSubQuery($name, $constraint = null)
{
/** @var Relation|HasOneOrMany|HasMany $relation */
$relation = call_user_func([$this, $name]);
$related = $relation->getRelated();
$relatedKey = $relation->getForeignKey();
$key = $relation->getQualifiedParentKeyName();
/** @var QueryBuilder|EloquentBuilder $subQuery */
$subQuery = $related->newQuery();
$subQuery->select(new Expression('COUNT(*)'));
$subQuery->whereRaw("{$relatedKey} = {$key}");
if ($constraint) {
$subQuery->where($constraint);
}
if ($subQuery instanceof EloquentBuilder) {
return $subQuery->getQuery();
}
return $subQuery;
}
/**
* @param string $key
* @param string|\Closure $constraint
* @return array
*/
protected function parseWithCountConstraint($key, $constraint = null)
{
list($name, $constraint) = is_string($key) ? [$key, $constraint] : [$constraint, null];
list($name, $alias) = $this->parseWithCountAlias($name);
return [$name, $alias, $constraint];
}
/**
* @param $name
* @return array
*/
protected function parseWithCountAlias($name)
{
$names = preg_split('/\s+(as)\s+/i', $name);
if (count($names) > 1) {
$name = $names[0];
$alias = $names[1];
} else {
$alias = $name.'_count';
}
return [$name, $alias];
}
}
<?php
namespace App;
use \Illuminate\Database\Eloquent\Model;
class Post extends Model{
use HasWithCountScope;
public function comments() {
return $this->hasMany(Comment::class);
}
public function votes() {
return $this->hasMany(Vote::class);
}
}
// USAGE 1
$posts = Post::withCount('comments')->get();
$posts[0]->comments_count; //total comments of the post
// USAGE 2
$posts = Post::withCount(['votes', 'comments' => function ($query) {
$query->where('created_at', '>', date('yesterday'))
}])->get();
$posts[0]->comments_count; //total comments created after yesterday
$posts[0]->votes_count; //total votes of the posts
// USAGE 3
$posts = Post::withCount(['votes as total_votes', 'comments as total_comments' => function ($query) {
$query->where('created_at', '>', date('yesterday'))
}])->get();
$posts[0]->total_comments; //total comments created after yesterday
$posts[0]->total_votes; //total votes of the posts
@kiran-jose
Copy link

Seems like it won't work with polymorphic relationships. So made some changes to make it work for polymorhpic relationship. Thank you for this gist @vluzrmos.

<?php

namespace App\Traits;

use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder as QueryBuilder;
use Illuminate\Database\Query\Expression;

// Trait created by refering https://gist.github.com/vluzrmos/70d32ddd2569cdc561efd4df43d3abbe.
trait WithCountScopeTrait
{

    /**
     * @param QueryBuilder|EloquentBuilder $query
     * @param string|array $name
     */
    public function scopeWithCount($query, $name)
    {
        $relations = is_array($name) ? $name : array_slice(func_get_args(), 1);

        if (empty($query->columns)) {
            if ($query instanceof EloquentBuilder) {
                $tableName = $query->getModel()->getTable();
            } elseif ($query instanceof QueryBuilder) {
                $tableName = $query->from;
            }
            $selectQuery = !empty($tableName) ? $tableName . '.*' : '*';
            $query->select([$selectQuery]);
        }

        foreach ($relations as $key => $constraint) {
            list($name, $alias, $constraint) = $this->parseWithCountConstraint($key, $constraint);
            $query->selectSub($this->getRelatedCountSubQuery($name, $query, $constraint), $alias);
        }
    }


    /**
     * @param string $name
     * @param Illuminate\Database\Eloquent\Builder $parentQuery
     * @param \Closure|null $constraint
     * @return \Illuminate\Database\Query\Builder
     */
    public function getRelatedCountSubQuery($name, $parentQuery, $constraint = null)
    {   
        /** @var Eloquent Relations $relation */
        $relation = call_user_func([$this, $name]);
        $related = $relation->getRelated();

        if (method_exists($relation, 'getRelationCountQuery')) {
            $subQuery = $relation->getRelationCountQuery($related->newQuery(), $parentQuery);
        } else {
            $relatedKey = $relation->getForeignKey();
            $key = $relation->getQualifiedParentKeyName();
            $subQuery = $related->newQuery();
            $subQuery->select(new Expression('COUNT(*)'));
            $subQuery->whereRaw("{$relatedKey} = {$key}");
        }

        if ($constraint) {
            $subQuery->where($constraint);
        }

        if ($subQuery instanceof EloquentBuilder) {
            return $subQuery->getQuery();
        }

        return $subQuery;
    }


    /**
     * @param string $key
     * @param string|\Closure $constraint
     * @return array
     */
    protected function parseWithCountConstraint($key, $constraint = null)
    {
        list($name, $constraint) = is_string($key) ? [$key, $constraint] : [$constraint, null];

        list($name, $alias) = $this->parseWithCountAlias($name);

        return [$name, $alias, $constraint];
    }


    /**
     * @param $name
     * @return array
     */
    protected function parseWithCountAlias($name)
    {
        $names = preg_split('/\s+(as)\s+/i', $name);

        if (count($names) > 1) {
            $name = $names[0];
            $alias = $names[1];
        } else {
            $alias = $name . '_count';
        }

        return [$name, $alias];
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment