Skip to main content

Filtering by Interaction Counts

This guide explains how to filter and sort your content based on interaction counts using the Laravel Like package. You'll learn how to find popular content, filter by thresholds, and create re-usable custom queries.

Prerequisites

  • Installation completed
  • HasLike / HasLove trait on your content model

Filtering by minimum likes

// Posts with at least 10 likes
$popular = Post::whereHas('likesTo', '>=', 10)->get();

// Posts with at least 50 likes AND at most 2 dislikes
$highQuality = Post::query()
->whereHas('likesTo', '>=', 50)
->whereHas('dislikesTo', '<=', 2)
->get();

Filtering by ratio

// Posts where likes are ≥ 80% of total interactions
$highQuality = Post::query()
->withCount(['likesTo as likes_count', 'dislikesTo as dislikes_count'])
->havingRaw('likes_count / (likes_count + dislikes_count) >= 0.8')
->get();

Sorting by popularity

// Posts sorted by like count (descending)
$posts = Post::query()
->withCount('likesTo as likes_count')
->orderByDesc('likes_count')
->paginate(15);

// Combined sorting — likes desc, then dislikes asc
$posts = Post::query()
->withCount([
'likesTo as likes_count',
'dislikesTo as dislikes_count',
'lovesTo as loves_count', // requires HasLove
])
->orderByDesc('likes_count')
->orderBy('dislikes_count')
->orderByDesc('loves_count')
->paginate(15);

Re-usable scopes

Wrap these queries into local scopes on your model:

class Post extends Model
{
use HasLike, HasLove;

public function scopePopular($query, int $minLikes = 10, int $maxDislikes = 2)
{
return $query->whereHas('likesTo', '>=', $minLikes)
->whereHas('dislikesTo', '<=', $maxDislikes)
->withCount('likesTo as likes_count')
->orderByDesc('likes_count');
}

public function scopeMostTrending($query, int $hours = 24, int $min = 5)
{
$cutoff = now()->subHours($hours);

return $query->whereHas('likes', function ($q) use ($cutoff) {
$q->where('created_at', '>=', $cutoff);
}, '>=', $min)
->withCount(['likes as recent_likes' => function ($q) use ($cutoff) {
$q->where('created_at', '>=', $cutoff);
}])
->orderByDesc('recent_likes');
}
}
$popularPosts = Post::popular()->get();
$weeklyTrends = Post::mostTrending(168, 20)->take(10)->get();

Real-world examples

$weeklyPopular = Post::whereHas('likesTo', function ($q) {
$q->where('created_at', '>=', now()->subWeek());
})
->withCount(['likesTo as weekly_likes' => function ($q) {
$q->where('created_at', '>=', now()->subWeek());
}])
->orderByDesc('weekly_likes')
->take(5)
->get();

Controversial content

$controversial = Post::query()
->withCount(['likesTo as likes_count', 'dislikesTo as dislikes_count'])
->having('likes_count', '>', 5)
->having('dislikes_count', '>', 5)
->orderByRaw('ABS(likes_count - dislikes_count)')
->get();

Performance optimization

Indexes

Add explicit indexes if your dataset grows large:

public function up(): void
{
Schema::table('likes', function (Blueprint $table) {
$table->index(['model_type', 'model_id', 'type']);
$table->index(['user_id', 'type']);
});
}

Cache hot queries

use Illuminate\Support\Facades\Cache;

function getPopularPosts(int $limit = 10)
{
return Cache::remember('popular_posts_' . $limit, 1800, function () use ($limit) {
return Post::query()
->withCount('likesTo as likes_count')
->orderByDesc('likes_count')
->limit($limit)
->get();
});
}

Common pitfalls

  1. N+1 queries — always use withCount() (never $post->likesCount() in a loop).
  2. Relation names — there are no dislikes / loves relationship methods. Use dislikesTo / lovesTo (or the generic likes) with withCount/whereHas.
  3. having with withCounthaving() only works on aggregated columns; when grouping, wrap raw expressions in havingRaw().
  4. Pagination — always paginate potentially large datasets.

Next Steps