Debugging PHP
What Is Debugging? In simple terms: Debugging is the process of finding out why a program does not do what you expect — and fixing it. A total is wrong, a page is blank, a form saves nothing. Good deb
What Is Debugging?
In simple terms: Debugging is the process of finding out why a program does not do what you expect — and fixing it. A total is wrong, a page is blank, a form saves nothing. Good debugging is not guessing: you observe what the program actually does, narrow down where it differs from what you expected, and confirm the cause before changing code.
Technically: PHP offers several layers of debugging tools: dump functions (var_dump(), print_r(), var_export()), error messages and logs, backtraces (debug_backtrace()), assertions (assert()), step debuggers such as Xdebug (breakpoints, stepping, inspecting variables in an IDE), profilers for performance, and static analysis tools that find bugs without running the code.
A Systematic Approach
1. Reproduce Find exact steps or input that trigger the bug
2. Observe Read the error message and logs; inspect values
3. Isolate Narrow down where actual and expected behavior diverge
4. Hypothesize Form a specific explanation
5. Verify Confirm with a dump, breakpoint, or test
6. Fix Change the code
7. Prevent Add an automated test that would have caught the bug
Most time is lost by skipping steps 2–5 and changing code on a hunch.
Inspecting Values: var_dump(), print_r(), var_export()
<?php
$order = ['id' => 501, 'paid' => true, 'total' => 380.0, 'coupon' => null];
print_r($order);
var_dump($order);
var_export($order);
echo "\n";
Output
Array
(
[id] => 501
[paid] => 1
[total] => 380
[coupon] =>
)
array(4) {
["id"]=>
int(501)
["paid"]=>
bool(true)
["total"]=>
float(380)
["coupon"]=>
NULL
}
array (
'id' => 501,
'paid' => true,
'total' => 380.0,
'coupon' => NULL,
)
| Function | Shows Types? | Best For |
|---|---|---|
print_r() | No (true → 1, null → empty) | Quick, readable overview of arrays and objects |
var_dump() | Yes | Debugging — types often explain the bug ("5" vs 5, null vs "") |
var_export() | Yes, as valid PHP code | Copying data into tests or configuration |
print_r($value, true) and var_export($value, true) return the text instead of printing it, which is useful for logging: error_log(print_r($order, true));.
In a browser, wrap dumps in <pre> tags for readability. The Symfony VarDumper component (composer require --dev symfony/var-dumper) adds dump() and dd() (dump and stop) with colored, collapsible output in browsers and terminals; Laravel includes them by default.
Warning: Remove dumps before committing. A forgotten
var_dump()can break JSON responses, prevent redirects ("headers already sent"), or expose data in production.
Where Did This Call Come From? Backtraces
<?php
function callChain(): string
{
$frames = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
return implode(' ← ', array_column($frames, 'function'));
}
function applyDiscount(float $amount): float
{
echo callChain() . "\n";
return $amount * 0.9;
}
function checkout(): float
{
return applyDiscount(1000);
}
echo checkout() . "\n";
Output
callChain ← applyDiscount ← checkout
900
debug_backtrace() returns the current call stack as an array of frames (function, class, file, line, and optionally arguments). DEBUG_BACKTRACE_IGNORE_ARGS omits arguments, which keeps the output small and avoids exposing sensitive values. Exceptions carry the same information: $e->getTraceAsString() shows where an exception was thrown and through which calls (see PHP Exceptions).
Assertions: Catching Impossible States Early
Assertions document assumptions that must always hold. In development, a failed assertion stops execution immediately — close to the real cause:
<?php
function reserveStock(int $quantity): void
{
assert($quantity > 0);
// ...
}
try {
reserveStock(0);
} catch (AssertionError $e) {
echo "Assertion failed: " . $e->getMessage() . "\n";
}
Output (with zend.assertions = 1)
Assertion failed: assert($quantity > 0)
zend.assertions | Effect |
|---|---|
1 (development) | Assertions are evaluated and throw AssertionError on failure |
-1 (production default in php.ini-production) | Assertions are not even compiled — zero cost |
Use assertions for programming errors (states that "cannot happen"), never for validating user input — in production they are switched off.
Step Debugging With Xdebug
Dumps show values at one moment. A step debugger pauses the program at a breakpoint, lets you inspect every variable, and execute the code line by line. Xdebug is the standard step debugger for PHP and works with VS Code, PhpStorm, and other IDEs.
Configuration (Development Only)
; conf.d/99-xdebug.ini
zend_extension = xdebug
xdebug.mode = debug,develop
xdebug.start_with_request = trigger
xdebug.client_host = 127.0.0.1 ; in Docker: host.docker.internal
xdebug.client_port = 9003
| Setting | Meaning |
|---|---|
xdebug.mode=debug | Enables step debugging (develop adds improved error output, profile enables profiling, coverage collects code coverage) |
start_with_request=trigger | Debug only when a trigger is present, so normal requests stay fast |
client_host / client_port | Where the IDE listens for debugging connections (port 9003 by default) |
VS Code (.vscode/launch.json)
{
"version": "0.2.0",
"configurations": [
{
"name": "Listen for Xdebug",
"type": "php",
"request": "launch",
"port": 9003,
"pathMappings": { "/var/www/shop": "${workspaceFolder}" }
}
]
}
A Debugging Session
- Install the PHP Debug extension in VS Code (or use PhpStorm's built-in support) and start listening.
- Set a breakpoint by clicking next to a line number.
- Trigger debugging: a browser extension ("Xdebug helper") sets the
XDEBUG_TRIGGERcookie; for CLI scripts, runXDEBUG_TRIGGER=1 php script.php. - When execution reaches the breakpoint, inspect variables, the call stack, and evaluate expressions; then step over, step into, or continue.
pathMappings is required when the code runs in Docker or on a VM, where file paths differ from those on your machine.
Warning: Xdebug slows PHP down considerably and allows remote code inspection. Never install it on production servers.
Finding Performance Problems
| Tool | Purpose |
|---|---|
hrtime(true) / microtime(true) | Measure the duration of a specific block of code (see PHP Date and Time) |
memory_get_usage(), memory_get_peak_usage() | Measure memory consumption |
Xdebug profiler (xdebug.mode=profile) | Records every function call; view the files with KCachegrind, QCachegrind, or PhpStorm |
| SPX, Blackfire, Tideways | Low-overhead profilers, some usable in production |
Database query logs and EXPLAIN | Find slow or repeated queries (for example, N+1 problems) |
See OPcache & Performance for configuration-level optimizations.
Preventing Bugs With Static Analysis
Static analysis tools read the code without running it and report type errors, undefined variables, wrong method calls, and impossible conditions:
composer require --dev phpstan/phpstan
vendor/bin/phpstan analyse src --level=6
Running PHPStan or Psalm (and php -l for syntax) in continuous integration catches many bugs before anyone needs to debug them. Combined with strict types, typed properties, and automated tests, they make many classic debugging sessions unnecessary.
Common Mistakes
Debugging in Production With display_errors
Effect: Users and attackers see internal details.
Fix: Reproduce locally; use logs and error tracking in production.
Forgotten Dumps
Effect: Broken pages, broken JSON, exposed data.
Fix: Search for var_dump, dd(, and print_r before committing; let static analysis or code review flag them.
Changing Several Things at Once
Effect: You cannot tell which change fixed (or caused) the problem.
Fix: Change one thing, verify, repeat.
Not Writing a Regression Test
Effect: The same bug returns later.
Fix: Capture the failing case in an automated test before or with the fix.
Best Practices
- Reproduce the bug reliably before changing code.
- Use
var_dump()for precise type information and log structured dumps withprint_r(..., true). - Use a step debugger (Xdebug) for complex flows instead of scattering dumps.
- Use assertions for internal assumptions, and static analysis to prevent whole classes of bugs.
- Keep debugging tools out of production and add a test for every fixed bug.
Official reference: https\://www.php.net/manual/en/debugger.php