Table Of Contents
- The Performance Problem
- Laravel withCount() vs with()->count() Performance
- Advanced Count Scenarios and Optimization
The Performance Problem
When displaying relationship counts, there's a crucial difference between getting just the count versus loading all related records then counting them:
// INEFFICIENT - Loads ALL posts, then counts in PHP
$users = User::with('posts')->get();
foreach ($users as $user) {
echo $user->posts->count(); // N+1 queries + memory waste
}
// EFFICIENT - Gets count from database without loading data
$users = User::withCount('posts')->get();
foreach ($users as $user) {
echo $user->posts_count; // Single query, minimal memory
}
// WRONG - This doesn't work (syntax error)
$users = User::with()->count('posts')->get(); // Invalid syntax
// The comparison is: withCount() vs with()->count()
// with()->count() loads all records then counts
// withCount() gets the count from database
Laravel withCount() vs with()->count() Performance
Understanding when to use each approach for optimal performance:
// Scenario 1: Only need counts - Use withCount()
$categories = Category::withCount(['posts', 'products'])->get();
foreach ($categories as $category) {
echo "{$category->name}: {$category->posts_count} posts, {$category->products_count} products";
}
// Generated SQL: SELECT categories.*, (SELECT COUNT(*) FROM posts...) as posts_count
// Scenario 2: Need both data and count - Use with() then count()
$users = User::with('posts')->get();
foreach ($users as $user) {
echo "Posts by {$user->name}:";
foreach ($user->posts as $post) {
echo "- {$post->title}"; // Using the actual data
}
echo "Total: {$user->posts->count()}"; // Count from loaded collection
}
// Scenario 3: Need filtered counts - withCount() with conditions
$users = User::withCount(['posts' => function ($query) {
$query->where('published', true);
}])->get();
foreach ($users as $user) {
echo "{$user->name} has {$user->posts_count} published posts";
}
// Scenario 4: Multiple count variations
$users = User::withCount([
'posts', // All posts
'posts as published_posts_count' => function ($query) {
$query->where('published', true);
},
'posts as draft_posts_count' => function ($query) {
$query->where('published', false);
}
])->get();
Advanced Count Scenarios and Optimization
Handle complex counting requirements efficiently:
// E-commerce: Product statistics
$categories = Category::withCount([
'products',
'products as active_products_count' => function ($query) {
$query->where('active', true);
},
'products as out_of_stock_count' => function ($query) {
$query->where('stock', 0);
},
'products as featured_count' => function ($query) {
$query->where('featured', true);
}
])->get();
// Blog: User engagement metrics
$users = User::withCount([
'posts',
'comments',
'posts as recent_posts_count' => function ($query) {
$query->where('created_at', '>=', now()->subDays(30));
},
'comments as recent_comments_count' => function ($query) {
$query->where('created_at', '>=', now()->subDays(30));
}
])->get();
foreach ($users as $user) {
$totalActivity = $user->posts_count + $user->comments_count;
$recentActivity = $user->recent_posts_count + $user->recent_comments_count;
echo "{$user->name}: {$totalActivity} total, {$recentActivity} recent";
}
// Complex nested relationship counting
$posts = Post::withCount([
'comments',
'comments as approved_comments_count' => function ($query) {
$query->where('approved', true);
},
'comments as recent_comments_count' => function ($query) {
$query->where('created_at', '>=', now()->subWeek());
},
'likes',
'bookmarks'
])->get();
// Performance comparison demo
class PerformanceTest
{
public function inefficientApproach()
{
// DON'T DO THIS - Loads all data into memory
$startTime = microtime(true);
$startMemory = memory_get_usage();
$users = User::with(['posts', 'comments'])->get();
$stats = [];
foreach ($users as $user) {
$stats[] = [
'user_id' => $user->id,
'posts_count' => $user->posts->count(), // PHP count
'comments_count' => $user->comments->count() // PHP count
];
}
$endTime = microtime(true);
$endMemory = memory_get_usage();
return [
'time' => $endTime - $startTime,
'memory' => $endMemory - $startMemory,
'stats' => $stats
];
}
public function efficientApproach()
{
// DO THIS - Gets counts from database
$startTime = microtime(true);
$startMemory = memory_get_usage();
$users = User::withCount(['posts', 'comments'])->get();
$stats = [];
foreach ($users as $user) {
$stats[] = [
'user_id' => $user->id,
'posts_count' => $user->posts_count, // Database count
'comments_count' => $user->comments_count // Database count
];
}
$endTime = microtime(true);
$endMemory = memory_get_usage();
return [
'time' => $endTime - $startTime,
'memory' => $endMemory - $startMemory,
'stats' => $stats
];
}
}
// Real-world dashboard example
class DashboardController extends Controller
{
public function index()
{
// Efficient approach for dashboard stats
$stats = [
'users' => User::withCount([
'posts',
'comments',
'orders' => function ($query) {
$query->where('status', 'completed');
}
])->paginate(20),
'categories' => Category::withCount([
'posts' => function ($query) {
$query->where('published', true);
}
])->get(),
'products' => Product::withCount([
'reviews',
'orders' => function ($query) {
$query->where('created_at', '>=', now()->subMonth());
}
])->where('featured', true)->get()
];
return view('dashboard', compact('stats'));
}
}
// API endpoint for mobile app
class UserController extends Controller
{
public function profile($id)
{
$user = User::withCount([
'posts',
'followers',
'following',
'posts as published_posts_count' => function ($query) {
$query->where('published', true);
}
])->findOrFail($id);
return response()->json([
'id' => $user->id,
'name' => $user->name,
'stats' => [
'total_posts' => $user->posts_count,
'published_posts' => $user->published_posts_count,
'followers' => $user->followers_count,
'following' => $user->following_count
]
]);
}
}
// Blade template usage
<!-- Efficient count display -->
@foreach($users as $user)
<div class="user-card">
<h3>{{ $user->name }}</h3>
<div class="stats">
<span>{{ $user->posts_count }} posts</span>
<span>{{ $user->comments_count }} comments</span>
<span>{{ $user->published_posts_count }} published</span>
</div>
</div>
@endforeach
<!-- When you need both count and data -->
@foreach($categories as $category)
<div class="category">
<h3>{{ $category->name }} ({{ $category->posts_count }} posts)</h3>
@if($category->relationLoaded('posts'))
<!-- Show actual posts if loaded with with() -->
@foreach($category->posts->take(3) as $post)
<p>{{ $post->title }}</p>
@endforeach
@endif
</div>
@endforeach
Use withCount()
when you only need relationship counts - it's much more efficient. Use with()
followed by ->count()
only when you need both the actual relationship data AND the count. Never use with()
just to get counts, as it loads all relationship data into memory unnecessarily.
Related: Laravel Collections: Beyond Basic Array Operations | Laravel Events and Listeners: Building Decoupled Applications | Building Multi-tenant Applications with Laravel: A Comprehensive Guide
Share this article
Add Comment
No comments yet. Be the first to comment!