Xdebug is an essential tool for PHP developers, providing step debugging, profiling, and code coverage capabilities. While var_dump and logging have their place, nothing beats proper debugging for understanding complex code flows. This guide covers setting up and using Xdebug effectively from a senior developer's perspective.
Why Xdebug
Xdebug transforms PHP development:
- Step Debugging: Set breakpoints, inspect variables, step through code
- Profiling: Find performance bottlenecks with detailed timing data
- Stack Traces: Get meaningful error traces with local variables
- Code Coverage: Measure test coverage accurately
- IDE Integration: Works with PhpStorm, VS Code, and other editors
Warning: Never enable Xdebug on production servers—it significantly impacts performance.
Installation
Standard Installation
# Using PECL
pecl install xdebug
# On Ubuntu/Debian
sudo apt install php-xdebug
# On CentOS/RHEL
sudo dnf install php-pecl-xdebug
Installation in Docker
Add to your development Dockerfile:
FROM php:8.2-fpm
# Install Xdebug
RUN pecl install xdebug \
&& docker-php-ext-enable xdebug
# Copy Xdebug configuration
COPY xdebug.ini $PHP_INI_DIR/conf.d/xdebug.ini
Create xdebug.ini:
[xdebug]
zend_extension=xdebug
; Enable step debugging
xdebug.mode=debug
xdebug.start_with_request=trigger
xdebug.client_host=host.docker.internal
xdebug.client_port=9003
xdebug.idekey=PHPSTORM
; Optionally enable profiling
; xdebug.mode=debug,profile
; xdebug.output_dir=/var/www/html/profiles
For Docker Compose, ensure proper networking:
version: '3.8'
services:
php:
build:
context: .
dockerfile: Dockerfile.dev
volumes:
- ./:/var/www/html
environment:
- PHP_IDE_CONFIG=serverName=docker
extra_hosts:
- "host.docker.internal:host-gateway"
Xdebug 3 Configuration Modes
Xdebug 3 uses modes to control features. Set via xdebug.mode:
| Mode | Description |
|---|
off | Nothing is enabled (default) |
develop | Development aids (var_dump improvements) |
debug | Step debugging |
profile | Profiling |
coverage | Code coverage |
trace | Function trace |
Combine modes with commas:
xdebug.mode=debug,profile,coverage
Step Debugging Setup
Configuration for Debugging
Create or edit xdebug.ini:
[xdebug]
zend_extension=xdebug
xdebug.mode=debug
xdebug.start_with_request=trigger
xdebug.client_host=127.0.0.1
xdebug.client_port=9003
xdebug.idekey=PHPSTORM
xdebug.log=/tmp/xdebug.log
Trigger Methods
With startwithrequest=trigger, debugging starts when:
- GET/POST parameter:
?XDEBUGSESSIONSTART=PHPSTORM - Cookie:
XDEBUG_SESSION=PHPSTORM - Browser extension: Xdebug Helper for Chrome/Firefox
PhpStorm Configuration
- Go to Settings → PHP → Debug
- Set Debug port to
9003 - Enable "Can accept external connections"
- Go to Settings → PHP → Servers
- Add a server with path mappings for Docker:
- Name: docker - Host: localhost - Port: 80 - Debugger: Xdebug - Path mappings: /local/path → /var/www/html
VS Code Configuration
Install the PHP Debug extension, then create .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "Listen for Xdebug",
"type": "php",
"request": "launch",
"port": 9003,
"pathMappings": {
"/var/www/html": "${workspaceFolder}"
}
},
{
"name": "Launch currently open script",
"type": "php",
"request": "launch",
"program": "${file}",
"cwd": "${fileDirname}",
"port": 9003
}
]
}
Debugging CLI Scripts
For command-line PHP scripts:
# Set the environment variable
export XDEBUG_SESSION=1
php artisan my:command
# Or inline
XDEBUG_SESSION=1 php script.php
Performance Profiling
Enable Profiling
Configure for on-demand profiling:
[xdebug]
zend_extension=xdebug
xdebug.mode=profile
xdebug.start_with_request=trigger
xdebug.output_dir=/var/www/html/profiles
xdebug.profiler_output_name=cachegrind.out.%p.%t
Trigger profiling by adding ?XDEBUG_PROFILE to any URL:
http://localhost/api/users?XDEBUG_PROFILE
Analyzing Profile Data
Profile files use cachegrind format. Analyze with:
KCachegrind (Linux/macOS):
# Install
sudo apt install kcachegrind # Ubuntu
brew install qcachegrind # macOS
# Open profile
kcachegrind profiles/cachegrind.out.12345
Webgrind (Web-based):
# Clone Webgrind
git clone https://github.com/jokkedk/webgrind.git
# Point the web server to the Webgrind folder
# Access via http://localhost/webgrind
Reading Profiler Output
Key metrics to analyze:
- Self Time: Time spent in the function itself
- Inclusive Time: Time including called functions
- Call Count: Number of times the function was called
Focus on:
- Functions with high self time (optimization targets)
- Functions called excessively (potential N+1 queries)
- Unexpected function calls (debugging)
Profiling Best Practices
// Profile specific code sections
xdebug_start_profiling();
// Your code here
$result = expensiveOperation();
xdebug_stop_profiling();
Code Coverage
Configure for Coverage
[xdebug]
xdebug.mode=coverage
PHPUnit Integration
<!--phpunit.xml-->
<phpunit>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./app</directory>
</include>
<exclude>
<directory>./vendor</directory>
</exclude>
<report>
<html outputDirectory="coverage-report"/>
<text outputFile="coverage.txt"/>
</report>
</coverage>
</phpunit>
Run with coverage:
php artisan test --coverage
# or
./vendor/bin/phpunit --coverage-html coverage-report
Development Aids
Enhanced var_dump
With xdebug.mode=develop:
$data = ['users' => [
['name' => 'John', 'email' => '[email protected]'],
['name' => 'Jane', 'email' => '[email protected]'],
]];
var_dump($data); // Now shows colored, formatted output
Stack Traces
Xdebug enhances error stack traces automatically:
function level3() {
throw new Exception("Something went wrong");
}
function level2() {
level3();
}
function level1() {
level2();
}
level1(); // Stack trace shows all levels with local variables
Troubleshooting
Verify Installation
<?php
phpinfo(); // Look for the Xdebug section
// or
php -v // Should show "with Xdebug"
Check Configuration
php -i | grep xdebug
Common Issues
Connection refused:
- Verify the IDE is listening on the correct port
- Check firewall rules
- Ensure
client_host is correct (use host.docker.internal for Docker)
Debugging doesn't start:
- Check
xdebug.mode includes debug - Verify the trigger method (cookie, GET parameter)
- Review the Xdebug log:
xdebug.log=/tmp/xdebug.log
Path mapping issues:
- Ensure the IDE server configuration matches actual paths
- For Docker, map container paths to local paths
Performance impact:
- Use
startwithrequest=trigger instead of yes - Disable Xdebug when not needed
- Never use in production
Docker-Specific Issues
If host.docker.internal doesn't work:
# docker-compose.yml
services:
php:
extra_hosts:
- "host.docker.internal:host-gateway"
Or use your host machine's IP address directly in xdebug.client_host.
Multi-Mode Configuration
For development environments needing all features:
[xdebug]
zend_extension=xdebug
; Enable multiple modes
xdebug.mode=debug,develop,coverage
; Debugging settings
xdebug.start_with_request=trigger
xdebug.client_host=host.docker.internal
xdebug.client_port=9003
xdebug.idekey=PHPSTORM
; Profiling (enable mode when needed)
; xdebug.mode=profile
xdebug.output_dir=/tmp/xdebug
xdebug.profiler_output_name=cachegrind.out.%p
; Logging
xdebug.log=/tmp/xdebug.log
xdebug.log_level=3
Key Takeaways
- Use trigger mode: Don't enable debugging for all requests
- Profile before optimizing: Find real bottlenecks; don't guess
- Configure path mappings: Essential for Docker and remote debugging
- Never use in production: Performance impact is significant
- Leverage IDE features: Breakpoints, watches, and call stacks
- Check the log: When debugging fails, Xdebug logs tell you why
Xdebug transforms PHP debugging from frustrating print statements to a professional development workflow. Master it, and you'll solve complex bugs in minutes instead of hours.