PHP Fundamentals

Debugging Tools & Techniques

13 min Lesson 28 of 45

Debugging Tools & Techniques

Debugging is an essential skill for every PHP developer. Understanding the available tools and techniques will help you identify and fix issues quickly and efficiently.

var_dump() and print_r()

The most basic debugging functions in PHP:

<?php $user = [ 'name' => 'John Doe', 'email' => 'john@example.com', 'age' => 30, 'active' => true ]; // var_dump() - shows type and value information var_dump($user); // Output: // array(4) { // ["name"]=> string(8) "John Doe" // ["email"]=> string(16) "john@example.com" // ["age"]=> int(30) // ["active"]=> bool(true) // } // print_r() - more readable, no type information print_r($user); // Output: // Array // ( // [name] => John Doe // [email] => john@example.com // [age] => 30 // [active] => 1 // ) // Return as string instead of printing $output = print_r($user, true); ?>
Tip: Use var_dump() when you need to see variable types and print_r() when you need more readable output.

Enhanced Debug Output

Create a custom debug function for better formatting:

<?php function debug($data, $label = 'Debug') { echo "<div style='background:#f4f4f4;border:1px solid #ccc;padding:10px;margin:10px 0;'>"; echo "<strong>$label:</strong><br>"; echo "<pre>"; print_r($data); echo "</pre>"; echo "</div>"; } // Usage $database = ['host' => 'localhost', 'user' => 'root']; debug($database, 'Database Config'); // Debug with die (stop execution) function dd($data, $label = 'Debug & Die') { debug($data, $label); die(); } dd($database); // Shows output and stops ?>

error_log() for Production

Use error_log() to debug in production without displaying information to users:

<?php // Simple logging error_log("User login attempt"); // Log variables $userId = 123; error_log("User ID: " . $userId); // Log arrays and objects $user = ['id' => 123, 'name' => 'John']; error_log("User data: " . print_r($user, true)); // Log to specific file error_log("Custom message\n", 3, "/path/to/custom.log"); // Log with timestamp function logDebug($message, $data = null) { $timestamp = date('Y-m-d H:i:s'); $logMessage = "[$timestamp] $message"; if ($data !== null) { $logMessage .= "\n" . print_r($data, true); } error_log($logMessage); } logDebug("Processing order", ['order_id' => 456, 'amount' => 99.99]); ?>
Note: Always use error_log() for debugging in production. Never use var_dump() or print_r() in production code.

debug_backtrace()

Get information about the call stack to trace where functions are called from:

<?php function levelOne() { levelTwo(); } function levelTwo() { levelThree(); } function levelThree() { $trace = debug_backtrace(); echo "<pre>"; print_r($trace); echo "</pre>"; // Simplified trace foreach ($trace as $i => $call) { echo "Step $i: "; echo $call['function'] ?? 'main'; if (isset($call['file'])) { echo " in " . basename($call['file']) . ":" . $call['line']; } echo "\n"; } } levelOne(); // Output: // Step 0: levelThree in script.php:10 // Step 1: levelTwo in script.php:6 // Step 2: levelOne in script.php:2 ?>

get_defined_vars()

See all variables available in the current scope:

<?php $username = 'john'; $email = 'john@example.com'; $age = 30; // Get all defined variables $allVars = get_defined_vars(); echo "<pre>"; print_r($allVars); echo "</pre>"; // Check if specific variable exists if (array_key_exists('username', $allVars)) { echo "Username is defined"; } ?>

Xdebug Extension

Xdebug is a powerful PHP extension for debugging and profiling:

<?php // Install Xdebug (via command line) // pecl install xdebug // Enable in php.ini: // zend_extension=xdebug.so // xdebug.mode=debug // xdebug.start_with_request=yes // Better var_dump() with Xdebug $data = [ 'users' => [ ['id' => 1, 'name' => 'John'], ['id' => 2, 'name' => 'Jane'] ] ]; var_dump($data); // Nicely formatted with colors // Stack traces with Xdebug function errorFunction() { trigger_error("Test error", E_USER_ERROR); } errorFunction(); // Shows detailed stack trace ?>
Tip: Xdebug provides colored, formatted output and can integrate with IDEs for step-by-step debugging with breakpoints.

Breakpoint Debugging with Xdebug and IDE

Set breakpoints in your IDE (VS Code, PhpStorm) to pause execution:

<?php // Configure Xdebug in php.ini // xdebug.mode=debug // xdebug.client_host=127.0.0.1 // xdebug.client_port=9003 function calculateTotal($items) { $total = 0; foreach ($items as $item) { // Set breakpoint here in your IDE $total += $item['price'] * $item['quantity']; } return $total; } $items = [ ['name' => 'Book', 'price' => 15.99, 'quantity' => 2], ['name' => 'Pen', 'price' => 2.99, 'quantity' => 5] ]; $total = calculateTotal($items); // When breakpoint is hit, you can: // - Inspect variables ($total, $item) // - Step through code line by line // - Evaluate expressions // - View call stack ?>

Query Debugging for MySQL

Debug database queries effectively:

<?php // Log all queries function debugQuery($sql, $params = []) { $timestamp = date('Y-m-d H:i:s'); $logMessage = "[$timestamp] SQL: $sql"; if (!empty($params)) { $logMessage .= "\nParams: " . json_encode($params); } error_log($logMessage); } // Example usage $sql = "SELECT * FROM users WHERE email = ? AND status = ?"; $params = ['john@example.com', 'active']; debugQuery($sql, $params); // Execute query $stmt = $pdo->prepare($sql); $stmt->execute($params); // Check for errors if ($stmt->errorCode() !== '00000') { $error = $stmt->errorInfo(); error_log("SQL Error: " . $error[2]); } // Get executed query (with mysqli) $mysqli = new mysqli('localhost', 'user', 'pass', 'database'); mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); $query = "SELECT * FROM users WHERE id = 1"; $result = $mysqli->query($query); // Check last query error_log("Last query: " . $mysqli->info); ?>

Performance Profiling

Measure execution time and memory usage:

<?php // Measure execution time $startTime = microtime(true); // Your code here for ($i = 0; $i < 1000000; $i++) { // Some operation } $endTime = microtime(true); $executionTime = $endTime - $startTime; echo "Execution time: " . number_format($executionTime, 4) . " seconds"; // Measure memory usage $startMemory = memory_get_usage(); $data = range(1, 100000); $endMemory = memory_get_usage(); $memoryUsed = $endMemory - $startMemory; echo "Memory used: " . number_format($memoryUsed / 1024 / 1024, 2) . " MB"; // Peak memory usage echo "Peak memory: " . number_format(memory_get_peak_usage() / 1024 / 1024, 2) . " MB"; ?>

Profiler Class

Create a reusable profiler for tracking performance:

<?php class Profiler { private static $timers = []; private static $memory = []; public static function start($label) { self::$timers[$label] = microtime(true); self::$memory[$label] = memory_get_usage(); } public static function end($label) { if (!isset(self::$timers[$label])) { return "Timer '$label' not started"; } $time = microtime(true) - self::$timers[$label]; $memory = memory_get_usage() - self::$memory[$label]; return [ 'label' => $label, 'time' => number_format($time, 4) . 's', 'memory' => number_format($memory / 1024, 2) . 'KB' ]; } public static function report() { echo "<h3>Performance Report</h3>"; echo "<table border='1'>"; echo "<tr><th>Operation</th><th>Time</th><th>Memory</th></tr>"; foreach (self::$timers as $label => $startTime) { $result = self::end($label); echo "<tr>"; echo "<td>{$result['label']}</td>"; echo "<td>{$result['time']}</td>"; echo "<td>{$result['memory']}</td>"; echo "</tr>"; } echo "</table>"; } } // Usage Profiler::start('database_query'); // Execute database query sleep(1); // Simulate work $result = Profiler::end('database_query'); print_r($result); Profiler::start('file_processing'); // Process files sleep(2); // Simulate work Profiler::end('file_processing'); Profiler::report(); ?>

Request/Response Debugging

Debug HTTP requests and responses:

<?php // Log all request data function logRequest() { $log = [ 'method' => $_SERVER['REQUEST_METHOD'], 'url' => $_SERVER['REQUEST_URI'], 'headers' => getallheaders(), 'get' => $_GET, 'post' => $_POST, 'files' => $_FILES, 'cookies' => $_COOKIE, 'session' => $_SESSION ?? [], 'ip' => $_SERVER['REMOTE_ADDR'] ]; error_log("Request: " . print_r($log, true)); } // Log response function logResponse($data, $statusCode = 200) { $log = [ 'status' => $statusCode, 'data' => $data, 'headers' => headers_list() ]; error_log("Response: " . print_r($log, true)); } // Usage logRequest(); $response = ['success' => true, 'message' => 'Order created']; logResponse($response, 201); ?>

Debugging Tips and Best Practices

<?php // 1. Use descriptive log messages error_log("Processing payment for order #" . $orderId); // Good error_log("Step 1"); // Bad // 2. Add context to logs function logWithContext($message, $context = []) { $contextStr = empty($context) ? '' : ' | Context: ' . json_encode($context); error_log($message . $contextStr); } logWithContext("User login failed", ['email' => 'user@example.com', 'ip' => '192.168.1.1']); // 3. Use conditional debugging define('DEBUG', true); function debugLog($message, $data = null) { if (!DEBUG) { return; } echo "<pre>[DEBUG] $message\n"; if ($data !== null) { print_r($data); } echo "</pre>"; } debugLog("User data loaded", $userData); // 4. Create debug mode checks if (isset($_GET['debug']) && $_GET['debug'] === 'true') { ini_set('display_errors', 1); error_reporting(E_ALL); } // 5. Log method entry/exit function processOrder($orderId) { error_log("ENTER: processOrder($orderId)"); try { // Process order logic $result = true; error_log("EXIT: processOrder($orderId) - Success"); return $result; } catch (Exception $e) { error_log("EXIT: processOrder($orderId) - Error: " . $e->getMessage()); throw $e; } } ?>
Warning: Remove or disable debug code before deploying to production. Debug output can expose sensitive information and slow down your application.

Chrome DevTools for AJAX Debugging

Debug AJAX requests in the browser:

<?php // Server-side (PHP API) header('Content-Type: application/json'); $data = [ 'success' => true, 'message' => 'Data retrieved', 'data' => [/* your data */] ]; // Add debug info in development if (DEBUG) { $data['debug'] = [ 'query_time' => 0.045, 'queries' => ['SELECT * FROM users', 'SELECT * FROM orders'], 'memory' => memory_get_usage() ]; } echo json_encode($data, JSON_PRETTY_PRINT); // Client-side debugging: // 1. Open Chrome DevTools (F12) // 2. Go to Network tab // 3. Click on the AJAX request // 4. View Headers, Preview, Response tabs ?>

Common Debugging Scenarios

<?php // Scenario 1: Empty or unexpected variable $username = $_POST['username'] ?? null; var_dump($username); // Check actual value var_dump(isset($username)); // Is it set? var_dump(empty($username)); // Is it empty? // Scenario 2: Array structure issues $users = getUsersFromDatabase(); echo "Count: " . count($users) . "\n"; echo "First element: "; print_r($users[0] ?? 'No elements'); // Scenario 3: Function not returning expected value function getTotal($items) { $total = 0; foreach ($items as $item) { error_log("Item: " . print_r($item, true)); $total += $item['price']; } error_log("Final total: $total"); return $total; } // Scenario 4: Conditional not working $age = "25"; // String, not integer if ($age === 25) { // False - strict comparison echo "Adult"; } var_dump($age, gettype($age)); // Debug type issues // Scenario 5: Include/require path issues $includePath = __DIR__ . '/includes/config.php'; echo "Trying to include: $includePath\n"; echo "File exists: " . (file_exists($includePath) ? 'Yes' : 'No') . "\n"; include $includePath; ?>
Exercise:
  1. Create a custom logger class that writes to different files based on log level (INFO, WARNING, ERROR)
  2. Build a profiler that measures the execution time of different sections of your code
  3. Write a debug function that formats arrays and objects in an HTML table
  4. Create a request debugger that logs all incoming request data to a file
  5. Implement a query logger that saves all database queries with execution time

Debugging Checklist

When debugging, always:
  • Check PHP error logs first
  • Use var_dump() or print_r() to inspect variables
  • Verify variable types with gettype()
  • Check if variables are set with isset() and empty()
  • Use error_log() for production debugging
  • Add descriptive messages to your debug output
  • Test edge cases (null, empty, zero, negative values)
  • Use a debugger (Xdebug) for complex issues
  • Remove all debug code before deployment