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:
- Create a custom logger class that writes to different files based on log level (INFO, WARNING, ERROR)
- Build a profiler that measures the execution time of different sections of your code
- Write a debug function that formats arrays and objects in an HTML table
- Create a request debugger that logs all incoming request data to a file
- 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