Development debugging toolbar for Forge Kernel. Provides automatic HTML injection, multiple data collectors, and a tabbed interface for debugging information.
ForgeDebugbar is a comprehensive debugging toolbar that automatically injects into HTML responses during development. It provides real-time insights into memory usage, execution time, database queries, routes, sessions, views, exceptions, and more.
Generic Module: ForgeDebugbar is a generic module (type: 'generic', order: 3), providing debugging functionality that can be installed as needed. It has no dependencies and automatically integrates with the request lifecycle.
ForgeDebugbar uses a collector-based architecture that allows for extensible data collection. It integrates seamlessly with the Forge Kernel lifecycle hooks and automatically injects the debug bar into HTML responses.
All debugging data is collected through collectors that implement the CollectorInterface. Collectors are registered with the DebugBar using callables, allowing for lazy evaluation of data.
interface CollectorInterface
{
public static function collect(...$args): mixed;
}
The DebugBar class uses a singleton pattern to ensure only one instance exists per request, maintaining consistent state across the application lifecycle.
ForgeDebugbar integrates with Forge Kernel lifecycle hooks:
The debug bar only injects into HTML responses. It checks the Content-Type header and skips:
{"html":... pattern)</body> tagThe debug bar is only active when both conditions are met:
APP_DEBUG environment variable is trueforge_debug_bar.enabled config is trueForgeDebugbar can be installed via ForgePackageManager. It's a generic module with no dependencies.
php forge.php module:install ForgeDebugbar
After installation, the module automatically:
asset:link commandphp forge.php module:uninstall ForgeDebugbar
On uninstall, the module automatically unlinks assets via asset:unlink command.
Collectors are the core of ForgeDebugbar's data collection system. Each collector implements the CollectorInterface and provides a static collect() method.
interface CollectorInterface
{
public static function collect(...$args): mixed;
}
Collectors are registered with the DebugBar using callables, allowing for lazy evaluation:
$debugbar->addCollector('memory', function () {
return MemoryCollector::collect();
});
ForgeDebugbar includes the following built-in collectors:
Tracks memory usage throughout the request lifecycle.
class MemoryCollector implements CollectorInterface
{
public static function collect(...$args): array
{
return self::instance()->getMemoryUsage();
}
public function getMemoryUsage(): array
{
return [
'current' => 'X.XX MB', // Current memory usage
'used' => 'X.XX MB', // Memory used (delta)
'peak' => 'X.XX MB', // Peak memory usage
'percentage' => 'XX.X%' // Percentage of memory limit
];
}
}
The MemoryCollector tracks:
Tracks total request execution time.
class TimeCollector implements CollectorInterface
{
public static function collect(...$args): string
{
$startTime = $args[0] ?? microtime(true);
return round((microtime(true) - $startTime) * 1000, 2) . 'ms';
}
}
Returns the total request execution time in milliseconds, calculated from the request start time.
Tracks custom timeline events throughout the request lifecycle.
class TimelineCollector implements CollectorInterface
{
public function addEvent(string $name, string $label = 'event', array $data = []): void
{
$this->events[] = [
'name' => $name,
'label' => $label,
'time' => microtime(true),
'data' => $data,
'origin' => Debuger::backtraceOrigin()
];
}
}
Timeline events include:
Collects custom debug messages with labels.
class MessageCollector implements CollectorInterface
{
public function addMessage(mixed $message, string $label = 'info'): void
{
$this->messages[] = [
'message' => $message,
'label' => $label, // 'info', 'warning', 'error'
'time' => microtime(true),
];
}
}
Messages support labels: info, warning, error.
Tracks exceptions that occur during the request.
class ExceptionCollector implements CollectorInterface
{
public function addException(\Throwable $exception): void
{
$this->exceptions[] = [
'type' => get_class($exception),
'message' => $exception->getMessage(),
'code' => $exception->getCode(),
'file' => BASE_PATH . $exception->getFile() . ':' . $exception->getLine(),
'trace' => $exception->getTraceAsString(),
];
}
}
Captures exception type, message, code, file location, and full stack trace.
Collects HTTP request information.
class RequestCollector
{
public static function collect(Request $request): array
{
return [
'url' => $request->getUrl(),
'method' => $request->getMethod(),
'ip' => $request->getClientIp(),
'headers' => $request->getHeaders(),
'query' => $request->getQuery(),
'body' => $request->all(),
'cookies' => $_COOKIE,
'files' => $_FILES,
];
}
}
Collects current route information.
class RouteCollector implements CollectorInterface
{
public function collectRoutes(): array
{
return [
'uri' => '/example',
'method' => 'GET',
'handler' => 'App\\Controllers\\HomeController::index',
'middleware' => ['auth', 'csrf'],
];
}
}
Collects session data and metadata.
class SessionCollector
{
public function collectSessionData(): array
{
return [
'session_id' => 'abc123...',
'data' => [...], // Formatted session data
'count' => 5 // Number of session keys
];
}
}
Tracks rendered views and their data.
class ViewCollector implements CollectorInterface
{
public function addView(string $viewPath, array|object $data = []): void
{
$this->views[] = [
'path' => $viewPath,
'data' => $this->formatDebugData($data),
];
}
}
Tracks database queries with execution time and performance classification. Note: This collector exists but requires integration with the database layer to automatically collect queries.
class DatabaseCollector implements CollectorInterface
{
public function addQuery(
string $query,
array $bindings,
float $time,
string $connectionName,
string $origin
): void {
$this->queries[] = [
'query' => $query,
'bindings' => $bindings,
'time_ms' => number_format($time, 2),
'connection_name' => $connectionName,
'origin' => $origin,
'performance' => $this->classifyQueryPerformance($time) // 'fast', 'medium', 'slow'
];
}
private function classifyQueryPerformance(float $time): string
{
if ($time > 100) return 'slow';
if ($time > 50) return 'medium';
return 'fast';
}
}
Query performance is classified as:
ForgeDebugbar provides helper functions for easy integration with your application code.
Adds a custom timeline event to track specific points in your application's execution.
add_timeline_event(string $name, string $label, array $data = []): void
Parameters:
$name - Event name (e.g., 'user_authenticated', 'data_processed')$label - Event label (e.g., 'start', 'end', 'info', 'warning')$data - Optional event data arrayExample:
// Track when a user is authenticated
add_timeline_event('user_authenticated', 'info', [
'user_id' => $user->id,
'method' => 'email'
]);
// Track start of data processing
add_timeline_event('data_processing', 'start');
// Track end of data processing
add_timeline_event('data_processing', 'end');
Note: This function only works when APP_DEBUG is true.
Collects view rendering data for debugging. This is automatically called by the View class, but can be called manually if needed.
collect_view_data(string $view, mixed $data = []): void
Parameters:
$view - View path$data - View data (array or object)Example:
// Automatically called by View class
// But can be called manually if needed
collect_view_data('pages/home/index', [
'title' => 'Home Page',
'users' => $users
]);
ForgeDebugbar automatically injects the debug bar into HTML responses without requiring any manual integration. The injection is non-intrusive and only occurs when certain conditions are met.
The debug bar is injected during the AFTER_REQUEST lifecycle hook:
</body> tagThe debug bar is only injected when all of the following conditions are met:
APP_DEBUG is trueforge_debug_bar.enabled config is truetext/html (or contains 'text/html')</body> tag{"html": (JSON response pattern)public function injectDebugBarIntoHtml(
string $htmlContent,
string $debugBarHtml,
Container $container
): string {
$cssLinkTag = sprintf(
'<link rel="stylesheet" href="/assets/modules/forge-debug-bar/css/debugbar.css">'
);
$jsScriptTag = sprintf(
'<script src="/assets/modules/forge-debug-bar/js/debugbar.js"></script>'
);
$injectionPoint = strripos($htmlContent, '</body>');
if ($injectionPoint !== false) {
$injectedContent = substr($htmlContent, 0, $injectionPoint) .
$cssLinkTag . "\n" .
$debugBarHtml . "\n" .
$jsScriptTag . "\n" .
substr($htmlContent, $injectionPoint);
return $injectedContent;
}
return $htmlContent . "\n" . $cssLinkTag . "\n" . $debugBarHtml . "\n" . $jsScriptTag;
}
The following assets are automatically injected:
/assets/modules/forge-debug-bar/css/debugbar.css - Debug bar styles/assets/modules/forge-debug-bar/js/debugbar.js - Debug bar JavaScript
These assets are linked during module installation via the asset:link command.
ForgeDebugbar integrates with Forge Kernel lifecycle hooks to collect data and inject the debug bar at the appropriate times.
Initializes timeline tracking at the start of the request:
#[LifecycleHook(hook: LifecycleHookName::BEFORE_REQUEST)]
public function onBeforeRequest(Request $request): void
{
add_timeline_event('onBeforeRequest', 'start');
}
Collects all data and injects the debug bar into the response:
#[LifecycleHook(hook: LifecycleHookName::AFTER_REQUEST)]
public function onAfterRequest(Request $request, Response $response): void
{
add_timeline_event('onAfterRequest', 'end');
// Initialize memory collector
self::$memoryCollector = MemoryCollector::instance();
// Get debug bar instance
$debugbar = $this->getDebugbarInstance();
// Register all collectors
$requestData = RequestCollector::collect($request);
$debugbar->addCollector('request', function () use ($requestData) {
return $requestData;
});
$sessionData = SessionCollector::collect();
$debugbar->addCollector('session', function () use ($sessionData) {
return $sessionData;
});
$debugbar->addCollector('memory', function () {
return self::$memoryCollector ?
self::$memoryCollector->getMemoryUsage() :
['error' => 'Memory collector not initialized'];
});
$timeData = TimeCollector::collect();
$debugbar->addCollector('time', function () use ($timeData) {
return $timeData;
});
$timelineData = TimelineCollector::collect();
$debugbar->addCollector('timeline', function () use ($timelineData) {
return $timelineData;
});
$messagesData = MessageCollector::collect();
$debugbar->addCollector('messages', function () use ($messagesData) {
return $messagesData;
});
$routeData = RouteCollector::collect();
$debugbar->addCollector('route', function () use ($routeData) {
return $routeData;
});
$viewData = ViewCollector::collect();
$debugbar->addCollector('views', function () use ($viewData) {
return $viewData;
});
$exceptionData = ExceptionCollector::collect();
$debugbar->addCollector('exceptions', function () use ($exceptionData) {
return $exceptionData;
});
// Inject debug bar into response
DebugBar::getInstance()->injectDebugBarIfEnabled($response, Container::getInstance());
}
ForgeDebugbar requires both an environment variable and a configuration setting to be enabled.
Set APP_DEBUG in your .env file:
APP_DEBUG=true
Security Warning: Never set APP_DEBUG=true in production. The debug bar exposes sensitive information and should only be used in development environments.
The module provides a default configuration:
#[ConfigDefaults(defaults: [
'forge_debug_bar' => [
'enabled' => true
]
])]
You can override this in your application configuration:
// config/forge_debug_bar.php
return [
'enabled' => env('APP_DEBUG', false),
];
The debug bar is only shown when both conditions are true:
public function shouldEnableDebugBar(Container $container): bool
{
$forgeDebug = env('APP_DEBUG');
$config = $container->get(Config::class);
$configEnabled = $config->get('forge_debug_bar.enabled', true);
return $configEnabled && $forgeDebug;
}
The debug bar provides a tabbed interface with multiple panels for different types of debugging information.
The debug bar includes the following tabs:
Tabs display counts for collections:
Messages (5)Timeline (12)Exceptions (2)Queries (8)Panels can be collapsed/expanded by clicking on the Forge logo. Individual sections within panels can also be collapsed for better organization.
The debug bar JavaScript handles:
The debug bar header displays key metrics:
ForgeDebugbar integrates with various parts of the Forge Kernel to collect debugging information.
The View class automatically calls collect_view_data() when rendering views:
// engine/Core/View/View.php
private function ensureRequestCollectorExist(string $view, array $data): void
{
$ready = is_file($requestCollectorFile)
&& class_exists(\App\Modules\ForgeDebugbar\Collectors\RequestCollector::class);
if ($ready) {
if (function_exists('collect_view_data')) {
collect_view_data($view, $data);
}
}
}
The Debuger helper class provides methods for logging exceptions:
// engine/Core/Helpers/Debuger.php
public static function logException(\Throwable $exception): void
{
if (class_exists(ExceptionCollector::class)) {
if (env('APP_ENV', false)) {
$exceptionCollector = Container::getInstance()->get(ExceptionCollector::class);
$exceptionCollector::instance()->addException($exception);
}
}
}
The Debuger helper class provides a method for adding debug messages:
// engine/Core/Helpers/Debuger.php
public static function message(mixed $message, string $label = 'info'): void
{
if (class_exists(\App\Modules\ForgeDebugbar\DebugBar::class)) {
if (env('APP_ENV', false)) {
$messageCollector = Container::getInstance()->get(MessageCollector::class);
$messageCollector::instance()->addMessage($message, $label);
}
}
}
ForgeDebugbar hooks into the request lifecycle via lifecycle hooks:
SessionCollector automatically collects session data from the active session if available.
DatabaseCollector exists and can track queries, but requires integration with the database layer. The collector expects queries to be added via:
DatabaseCollector::instance()->addQuery(
$query,
$bindings,
$timeInMilliseconds,
$connectionName,
$origin
);
// Track when a user logs in
add_timeline_event('user_login', 'info', [
'user_id' => $user->id,
'method' => 'email'
]);
// Track start of expensive operation
add_timeline_event('data_processing', 'start');
// Process data...
$result = processLargeDataset($data);
// Track end of expensive operation
add_timeline_event('data_processing', 'end', [
'records_processed' => count($result)
]);
use Forge\Core\Helpers\Debuger;
// Add info message
Debuger::message('User authenticated successfully', 'info');
// Add warning message
Debuger::message('Cache miss detected', 'warning');
// Add error message
Debuger::message('Failed to connect to external API', 'error');
use Forge\Core\Helpers\Debuger;
try {
// Some code that might throw an exception
$result = riskyOperation();
} catch (\Exception $e) {
// Log exception to debug bar
Debuger::logException($e);
// Handle exception...
throw $e;
}
// .env
APP_DEBUG=true
// config/forge_debug_bar.php
return [
'enabled' => env('APP_DEBUG', false),
];
You can create custom collectors by implementing the CollectorInterface:
use App\Modules\ForgeDebugbar\Collectors\CollectorInterface;
class CustomCollector implements CollectorInterface
{
private array $data = [];
public static function collect(...$args): array
{
return self::instance()->data;
}
public static function instance(): self
{
static $instance = null;
if (null === $instance) {
$instance = new self();
}
return $instance;
}
public function addData(string $key, mixed $value): void
{
$this->data[$key] = $value;
}
}
// Register in DebugBarModule::onAfterRequest()
$customData = CustomCollector::collect();
$debugbar->addCollector('custom', function () use ($customData) {
return $customData;
});
APP_DEBUG in production