PHP Debugging — Complete Guide to Finding and Fixing Bugs
In this tutorial, you will learn about PHP Debugging. We cover key concepts, practical examples, and best practices to help you master this topic.
PHP debugging is the Process of identifying, analyzing, and fixing errors in PHP code using tools like Xdebug, error logs, and stack traces to trace execution flow. This tutorial covers configuring Xdebug with VS Code, interpreting PHP error messages, using var_dump and error_log strategically, handling fatal errors gracefully, and debugging both web and CLI applications.
What You'll Learn
- Configuring Xdebug for step-through debugging in VS Code and PhpStorm
- Reading and understanding PHP error messages, stack traces, and log output
- Using var_dump, print_r, and error_log for quick variable inspection
- Handling fatal errors and exceptions with custom error handlers
- Debugging AJAX requests, API responses, and CLI scripts
- Profiling with Xdebug to find performance bottlenecks
Why It Matters
Every developer writes bugs. The difference between a junior and senior developer is not avoiding bugs but finding them quickly. In production, a single bug can bring down a site, leak data, or corrupt a database. Mastering debugging tools reduces resolution time from hours to minutes, protects your reputation, and keeps your application running smoothly.
Real-World Use
When Doda Browser's sync feature failed silently for users, the engineering team used Xdebug tracing to discover that a Redis connection timeout was caught by a generic try-catch block that logged nothing. Adding proper error logging and debugging instrumentation turned a two-week investigation into a two-hour fix.
Learning Path
flowchart LR A[Testing with PHPUnit] --> B[Debugging\nYou are here] B --> C[Composer] style B fill:#f90,color:#fff
Understanding PHP Error Types
PHP errors fall into several categories, each with a severity level. Familiarize yourself with the most common ones:
- E_ERROR: Fatal runtime error. Execution stops immediately.
- E_WARNING: Non-fatal warning. Execution continues.
- E_PARSE: Compile-time parse error. Code never runs.
- E_NOTICE: Minor issue like accessing an undefined variable.
- E_DEPRECATED: Warning about code that will break in a future PHP version.
Configure error reporting in your development environment:
<?php
error_reporting(E_ALL);
ini_set('display_errors', '1');
ini_set('log_errors', '1');
ini_set('error_log', '/var/log/php_errors.log');
Output (no visible output unless an error occurs; error details go to the log file).
Using Xdebug for Step-Through Debugging
Xdebug allows you to set breakpoints, step through code line by line, and inspect variables in real time. Install it:
pecl install xdebug
Configure PHP to load Xdebug. Create /etc/php/8.3/cli/conf.d/99-xdebug.ini:
zend_extension=xdebug
xdebug.mode=debug,develop
xdebug.start_with_request=yes
xdebug.client_port=9003
xdebug.idekey=VSCODE
For VS Code, install the PHP Debug extension and create a .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "Listen for Xdebug",
"type": "php",
"request": "launch",
"port": 9003,
"pathMappings": {
"/var/www/html": "${workspaceFolder}"
}
}
]
}
Set a breakpoint by clicking the gutter in VS Code, start the listener (Run > Start Debugging), and run your script. Execution pauses at the breakpoint, letting you inspect variables and step through code.
Strategic var_dump and error_log
When Xdebug is unavailable, use var_dump with exit to inspect variables:
<?php
function processOrder(array $items, float $discount): float
{
$total = 0;
foreach ($items as $item) {
$total += $item['price'] * $item['quantity'];
}
var_dump($total, $discount);
exit;
return $total - ($total * $discount);
}
$order = [
['price' => 10.00, 'quantity' => 2],
['price' => 5.00, 'quantity' => 3],
];
echo processOrder($order, 0.1);
Output:
float(35)
float(0.1)
For production-safe debugging, write to a log file:
<?php
function calculateTotal(array $items): float
{
$total = array_sum(array_map(fn($item) => $item['price'] * $item['quantity'], $items));
error_log('calculateTotal: ' . json_encode(['items' => $items, 'total' => $total])); // Logs to /var/log/php_errors.log or stderr
return $total;
}
$order = [['price' => 25, 'quantity' => 3]];
echo calculateTotal($order);
Output:
75
The log entry appears in your error log:
[28-Jun-2026 12:00:00 UTC] calculateTotal: {"items":[{"price":25,"quantity":3}],"total":75}
Custom Error Handler
Replace PHP's default error handler with one that provides more context:
<?php
set_error_handler(function (int $severity, string $message, string $file, int $line): bool {
if (!(error_reporting() & $severity)) {
return false;
}
$log = sprintf("[%s] [%d] %s in %s:%d\n", date('Y-m-d H:i:s'), $severity, $message, $file, $line);
file_put_contents('/var/log/app_errors.log', $log, FILE_APPEND);
return true;
});
echo $undefinedVariable;
Output (no visible output; error logged to file):
[2026-06-28 12:00:00] [8] Undefined variable: undefinedVariable in /var/www/html/index.php:12
Debugging AJAX and API Requests
When debugging API endpoints, log request details and return structured error responses:
<?php
header('Content-Type: application/json');
try {
$input = json_decode(file_get_contents('php://input'), true, 512, JSON_THROW_ON_ERROR);
if (!isset($input['email'])) {
throw new InvalidArgumentException('Email field is required');
}
$result = processRegistration($input['email']);
echo json_encode(['success' => true, 'data' => $result]);
} catch (\Throwable $e) {
error_log('API Error: ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine());
http_response_code(400);
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}
Test with curl:
curl -X POST -H "Content-Type: application/json" -d '{"email":"test@example.com"}' http://localhost/api/register
Output:
{"success":true,"data":{"id":42,"email":"test@example.com"}}
Common Mistakes
- Relying on var_dump in production: var_dump leaks internal data to users and breaks JSON responses. Use error_log instead and tail the log file.
- Ignoring error_reporting settings: If display_errors is off and log_errors is off, you will see a blank white screen with no clue what went wrong.
- Not catching exceptions globally: An uncaught exception in PHP 8 produces a 500 error with no helpful output. Wrap your entire application in a try-catch block.
- Debugging by adding random echo statements: This clutters output and often misses the root cause. Use a debugger or error log instead.
- Not checking log file permissions: If the web server cannot write to the error log, your log entries disappear silently.
Practice Questions
What is the difference between E_WARNING and E_ERROR?
- E_WARNING is non-fatal — execution continues. E_ERROR stops execution immediately.
How do you enable Xdebug in VS Code?
- Install the PHP Debug extension, create a launch.json with port 9003, and start debugging with F5.
What is the purpose of
error_reporting(E_ALL)?- It tells PHP to report all errors, notices, and deprecations. Use it in development to catch every potential issue.
How do you log a variable without stopping execution?
- Use
error_log(json_encode($variable))to write to the PHP error log without affecting output.
- Use
Challenge: Write a custom error handler that sends critical errors (E_ERROR, E_PARSE) to an email address and logs warnings to a file. Test it by triggering both types.
Mini Project
Build a debugging dashboard for a PHP application:
- Create a Debug utility class with methods for logging, timing execution, and tracking memory usage.
- Add a global error handler that captures all errors and stores them in an array.
- Render a debug toolbar at the bottom of HTML pages that shows elapsed time, memory, database queries, and logged errors.
- Ensure the toolbar only appears in development mode.
FAQ
What's Next
Master debugging, then learn how to manage dependencies and packages with Composer.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro