code quality and tooling
Xdebug Step Debugging
Step debugging means running PHP under a debugger, pausing at a breakpoint, and moving through the code one step at a time. Xdebug provides the PHP side of that connection, while your IDE or editor provides the debugger interface.
This lesson focuses on the workflow. The exact buttons vary between PhpStorm, VS Code, and other editors, but the ideas are the same.
Use a small bug
Start with a script that has a clear boundary bug.
<?php
declare(strict_types=1);
function discountForSubtotal(int $subtotal): int
{
if ($subtotal > 5000) {
return 500;
}
return 0;
}
$subtotal = 5000;
$discount = discountForSubtotal($subtotal);
echo $discount . PHP_EOL;
// Prints:
// 0
The rule is "GBP 50 or more gets GBP 5 off", so the output should be 500. Step debugging lets you watch why the code returns 0.
Prepare the debugger
Before running the script:
- make sure Xdebug is installed for the PHP binary or container that runs the code
- make sure
xdebug.modeincludesdebug - make sure the IDE is listening on the Xdebug port
- set a breakpoint on the call to
discountForSubtotal()
For a CLI script, you can run with debug mode enabled for that command:
XDEBUG_MODE=debug php discount.php
If the IDE is listening and the connection settings are correct, execution should pause at the breakpoint.
Step over
Step over runs the current line without entering any function called on that line.
If the current line is:
$discount = discountForSubtotal($subtotal);
stepping over runs the function call and lands on the next line. That is useful when you trust the function and only care about the result.
Step into
Step into enters the function call on the current line.
For this bug, step into discountForSubtotal(). Once inside, inspect $subtotal. It should be 5000.
Now look at the condition:
if ($subtotal > 5000) {
return 500;
}
With $subtotal equal to 5000, this condition is false. The debugger has shown the bug without guessing.
Step out
Step out finishes the current function and returns to the caller. Use it when you have seen enough inside a function and want to go back to the higher-level flow.
In this example, stepping out returns to the line after the call to discountForSubtotal().
Inspect variables
While paused, inspect the important values:
$subtotal$discount- the return value after the function finishes
Do not inspect every variable. Focus on the values that could explain the wrong behaviour.
Fix and verify
The fix is to use >=.
<?php
declare(strict_types=1);
function discountForSubtotal(int $subtotal): int
{
if ($subtotal >= 5000) {
return 500;
}
return 0;
}
var_dump(discountForSubtotal(4999));
var_dump(discountForSubtotal(5000));
var_dump(discountForSubtotal(5001));
// Prints:
// int(0)
// int(500)
// int(500)
After changing the code, rerun the script and check the boundary values.
Step debugging web requests
For a web request, the same ideas apply:
- the IDE listens for Xdebug
- the request is triggered from the browser or HTTP client
- Xdebug connects back to the IDE
- breakpoints pause the request
- you inspect variables and step through the code
The extra complications are usually environment-related: Docker networking, path mappings, hostnames, ports, and whether the request actually triggers Xdebug.
Debugger controls in plain English
The names vary slightly, but most debuggers give you these controls:
- continue: run until the next breakpoint or the end
- step over: run this line without entering called functions
- step into: enter the function called on this line
- step out: finish this function and return to the caller
- stop: end the debug session
What to remember
Step debugging is useful when reading the code is not enough and you need to see runtime state. Use it to confirm what value the code actually has, which branch it actually takes, and where the wrong value first appears.
Before moving on, make sure you can describe the difference between step over, step into, and step out.
Practice
Task: Step Through A Discount Bug
Write the step-debugging plan for this bug.
<?php
declare(strict_types=1);
function discountForSubtotal(int $subtotal): int
{
if ($subtotal > 5000) {
return 500;
}
return 0;
}
$subtotal = 5000;
$discount = discountForSubtotal($subtotal);
echo $discount . PHP_EOL;
The expected discount is 500, but the script prints 0.
Requirements
- Say where you would put the breakpoint.
- Say whether you would step over or step into the function call.
- Say which variable you would inspect inside the function.
- Identify the faulty condition.
- State the final fix.
Show solution
$discount = discountForSubtotal($subtotal);
When execution pauses there, step into the function call. Inside discountForSubtotal(), inspect $subtotal. It should be 5000.
The faulty condition is:
if ($subtotal > 5000) {
return 500;
}
That condition is false when $subtotal is exactly 5000, even though the business rule says GBP 50 or more.
The fix is:
if ($subtotal >= 5000) {
return 500;
}
After the fix, check 4999, 5000, and 5001 so the boundary behaviour is proven.