Debugging is finding out why code does not do what you expect. A good method is more useful than any tool.
A simple method
- Reproduce the problem with the smallest input you can.
- Read the error message and the line number carefully.
- Look at the actual values (not the values you think you have).
- Change one thing at a time.
- Confirm the fix, and think about why the bug happened.
Look at values: var_dump() and print_r()
<?php
$user = ["name" => "Ana", "age" => "29", "tags" => []];
var_dump($user["age"]);
print_r($user);
string(2) "29"
Array
(
[name] => Ana
[age] => 29
[tags] => Array
(
)
)
var_dump() shows the type (string(2) "29", a string, not a number). That detail solves many bugs.
Make debug output readable in a browser
In HTML, whitespace collapses. Wrap the output in <pre>.
<?php
echo "<pre>";
var_dump($data);
echo "</pre>";
Log instead of printing
On a live site (or when printing would break the page, such as in JSON or a redirect), write to the log.
<?php
error_log("order total: " . var_export($total, true));
error_log(print_r($order, true));
var_export($x, true) and print_r($x, true) return a string instead of printing.
Read the stack trace
<?php
function a(): void { b(); }
function b(): void { c(); }
function c(): void { throw new RuntimeException("deep problem"); }
try {
a();
} catch (RuntimeException $e) {
foreach ($e->getTrace() as $i => $frame) {
echo "#$i " . $frame["function"] . "()\n";
}
}
#0 c()
#1 b()
#2 a()
The trace lists the calls from the point of failure back to where it started. Start at the top: that is where the exception was created.
Assertions with clear messages
Check your assumptions early.
<?php
function average(array $numbers): float
{
if ($numbers === []) {
throw new InvalidArgumentException("average() needs at least one number");
}
return array_sum($numbers) / count($numbers);
}
try {
average([]);
} catch (InvalidArgumentException $e) {
echo $e->getMessage();
}
average() needs at least one number
Bisect: cut the problem in half
If a long script fails somewhere, add echo "reached here\n"; exit; in the middle. If you see the message, the bug is below it. Move the line and repeat. You find the line in a few steps.
Xdebug: a real debugger
Xdebug is a PHP extension that lets you:
- Pause the program at a breakpoint.
- Step through the code line by line.
- Inspect every variable.
- See the call stack.
You install it, and connect your editor (VS Code with a PHP debug extension, or PhpStorm). Then you click in the margin to set a breakpoint and load the page. Setup steps depend on your system, so follow the guide at xdebug.org.
Other helpful tools
- Browser Network tab: see the real response, status code and headers.
php -l file.php: check syntax without running the file.- Static analysers such as PHPStan and Psalm find bugs without running the code.
- Tests with PHPUnit or Pest keep a bug from returning.
Do not debug on the live site
Turn on error display only on your own machine. If you must debug production, use logs and a debugger on a copy, not display_errors on a public page.
Rubber duck debugging
Exercise: find the bug
This function should return the average, but it prints the wrong result for ["10", "20"]. Use var_dump() mentally. What is the problem?
<?php
function average(array $numbers) {
$sum = "";
foreach ($numbers as $n) { $sum .= $n; }
return $sum / count($numbers);
}
Show the answer
$sum starts as an empty string, and .= joins the values (“1020”). The result is 1020 / 2 = 510. The fix is $sum = 0; and $sum += $n;, or simply array_sum($numbers).