DebugBar & Model Eager Loading

Info: Chapter 27: Part of the Larafony Framework - A modern PHP 8.5 framework built from scratch. Full implementation details with tests available at masterphp.eu

Overview

Chapter 27 introduces two critical development features: a professional DebugBar for real-time application insights and N+1 query prevention through model eager loading. The DebugBar provides comprehensive debugging information during development, while eager loading ensures production-grade performance by eliminating the notorious N+1 query problem.

The DebugBar is a non-intrusive toolbar injected into HTML responses, collecting data through event listeners without modifying application logic. It tracks database queries with execution time and backtrace, cache operations (hits/misses/writes/deletes) with hit ratio calculation, view rendering with template names and data, route matching with parameters and controller info, request/response details (method, URI, headers, status), application performance (execution time, memory usage, peak memory), and timeline visualization showing the complete request lifecycle.

Model eager loading solves the N+1 query problem by loading related models in bulk rather than one-by-one. Instead of executing 1 + N queries (one to fetch parent models, then one query per parent for its relations), eager loading executes just 2 queries (one for parents, one for all related models), dramatically reducing database load and improving response times.

Warning: Key Performance Impact: Without Eager Loading: 101 queries for 100 users with roles (1 + 100) With Eager Loading: 2 queries for 100 users with roles (1 + 1) Reduction: 98% fewer queries

Key Components

DebugBar System

Data Collectors

Each collector implements DataCollectorContract and listens to framework events:

Model Eager Loading

Usage Examples

DebugBar Integration

The DebugBar is automatically enabled in development environments and displays at the bottom of HTML pages:

// config/app.php - DebugBar is registered via DebugBarServiceProvider
use Larafony\Framework\DebugBar\ServiceProviders\DebugBarServiceProvider;

return [
'providers' => [
// ... other providers
DebugBarServiceProvider::class,
],
];

// config/debugbar.php - Configure DebugBar behavior
use Larafony\Framework\Config\Environment\EnvReader;
use Larafony\Framework\DebugBar\Collectors\CacheCollector;
use Larafony\Framework\DebugBar\Collectors\PerformanceCollector;
use Larafony\Framework\DebugBar\Collectors\QueryCollector;
use Larafony\Framework\DebugBar\Collectors\RequestCollector;
use Larafony\Framework\DebugBar\Collectors\RouteCollector;
use Larafony\Framework\DebugBar\Collectors\TimelineCollector;
use Larafony\Framework\DebugBar\Collectors\ViewCollector;

return [
'enabled' => EnvReader::read('APP_DEBUG', false),

'collectors' => [
'queries' => QueryCollector::class,
'cache' => CacheCollector::class,
'views' => ViewCollector::class,
'route' => RouteCollector::class,
'request' => RequestCollector::class,
'performance' => PerformanceCollector::class,
'timeline' => TimelineCollector::class,
]
];

// The middleware is automatically registered in HTTP kernel
// No manual configuration needed!

What You See:

When you load any HTML page in development, the DebugBar appears at the bottom showing:

Basic Eager Loading

use App\Models\User;

// ❌ N+1 Problem (101 queries for 100 users)
$users = User::query()->get(); // 1 query

foreach ($users as $user) {
echo $user->role->name; // 100 queries (one per user)
}
// Total: 101 queries

// ✅ With Eager Loading (2 queries for 100 users)
$users = User::query()->with(['role'])->get(); // 2 queries (users + roles)

foreach ($users as $user) {
echo $user->role->name; // No query - already loaded
}
// Total: 2 queries

DebugBar Shows:

Nested Eager Loading

use App\Models\Post;

// Load posts with author and author's profile
$posts = Post::query()
 ->with(['author.profile'])
 ->get();

// 3 queries total:
// 1. SELECT * FROM posts
// 2. SELECT * FROM users WHERE id IN (...)
// 3. SELECT * FROM profiles WHERE user_id IN (...)

foreach ($posts as $post) {
echo $post->author->profile->bio; // No queries - all loaded
}

Nested Relation Syntax:

Multiple Relations

use App\Models\User;

// Load multiple relations at once
$users = User::query()
 ->with(['role', 'permissions', 'posts'])
 ->get();

// 4 queries total:
// 1. SELECT * FROM users
// 2. SELECT * FROM roles WHERE id IN (...)
// 3. SELECT * FROM permissions WHERE user_id IN (...)
// 4. SELECT * FROM posts WHERE author_id IN (...)

foreach ($users as $user) {
echo $user->role->name;
echo count($user->permissions);
echo count($user->posts);
// All data already loaded - no additional queries
}

Complex Nested Loading

use App\Models\Category;

// Deep nesting with multiple branches
$categories = Category::query()
 ->with([
'posts.author.profile', // Posts -> Authors -> Profiles
'posts.comments.user', // Posts -> Comments -> Users
'posts.tags', // Posts -> Tags
])
 ->get();

// 7 queries total:
// 1. SELECT * FROM categories
// 2. SELECT * FROM posts WHERE category_id IN (...)
// 3. SELECT * FROM users WHERE id IN (...) -- authors
// 4. SELECT * FROM profiles WHERE user_id IN (...)
// 5. SELECT * FROM comments WHERE post_id IN (...)
// 6. SELECT * FROM users WHERE id IN (...) -- comment authors
// 7. SELECT * FROM tags JOIN post_tag WHERE post_id IN (...)

foreach ($categories as $category) {
foreach ($category->posts as $post) {
echo $post->author->profile->bio;
foreach ($post->comments as $comment) {
echo $comment->user->name;
}
foreach ($post->tags as $tag) {
echo $tag->name;
}
}
}
// All data accessed without additional queries!

DebugBar Timeline Shows:

Implementation Details

DebugBar

Location: src/Larafony/DebugBar/DebugBar.php

Purpose: Central orchestrator managing all data collectors and coordinating data collection.

Key Methods:

InjectDebugBar Middleware

Location: src/Larafony/DebugBar/Middleware/InjectDebugBar.php

Purpose: PSR-15 middleware that injects DebugBar toolbar HTML into responses.

Injection Logic:

Safety Checks:

DebugBarServiceProvider

Location: src/Larafony/DebugBar/ServiceProviders/DebugBarServiceProvider.php

Purpose: Service provider responsible for bootstrapping DebugBar with configuration-driven collector registration.

Bootstrap Algorithm:

Success: Performance Optimization: The provider uses an early return pattern when DebugBar is disabled, ensuring zero overhead in production environments - no collectors are instantiated, no event listeners registered, and no memory allocated for debugging infrastructure.

EagerRelationsLoader

Location: src/Larafony/Database/ORM/EagerLoading/EagerRelationsLoader.php

Purpose: Orchestrate eager loading of model relations to prevent N+1 queries.

Algorithm:

HasManyLoader

Location: src/Larafony/Database/ORM/EagerLoading/HasManyLoader.php

Purpose: Load hasMany relations efficiently with single query.

Algorithm:

// Given: 100 users, each with multiple posts
// Without eager loading: 1 + 100 queries
// With eager loading: 1 + 1 queries

$users = User::query()->with(['posts'])->get();

// 2 queries:
// SELECT * FROM users
// SELECT * FROM posts WHERE user_id IN (1,2,3,...,100)

Testing

The DebugBar and eager loading features are tested through integration tests:

DebugBar Integration Tests

Coverage: Tests verify:

Eager Loading Tests

Coverage: Tests verify:

// Without eager loading
$users = User::query()->get();
$this->assertQueryCount(101); // 1 + 100

// With eager loading
$users = User::query()->with(['role'])->get();
$this->assertQueryCount(2); // 1 + 1

Success: Learn More: This implementation is explained in detail with step-by-step tutorials, tests, and best practices at masterphp.eu