10: Debugging - Node Inspector vs Xdebug
Debugging: Node Inspector vs Xdebug
Section titled “Debugging: Node Inspector vs Xdebug”Overview
Section titled “Overview”If you’re comfortable with Node.js debugging tools, you’ll find PHP’s Xdebug equally powerful. Both provide breakpoints, step-through debugging, variable inspection, and profiling. This chapter maps your debugging knowledge to the PHP ecosystem.
Learning Objectives
Section titled “Learning Objectives”By the end of this chapter, you’ll be able to:
- ✅ Install and configure Xdebug
- ✅ Set breakpoints and step through code
- ✅ Inspect variables and call stacks
- ✅ Debug with VS Code and PHPStorm
- ✅ Use remote debugging
- ✅ Profile PHP applications
- ✅ Understand error handling and logging
Code Examples
Section titled “Code Examples”📁 View Code Examples on GitHub
This chapter includes debugging examples:
- Xdebug configuration
- VS Code launch.json setup
- Debugging scenarios
- Logging examples
Setup debugging:
cd code/php-typescript-developers/chapter-10# Review Xdebug configurationcat xdebug.ini# Review VS Code debug configcat .vscode/launch.jsonDebugging Tools Comparison
Section titled “Debugging Tools Comparison”| Feature | Node.js | PHP |
|---|---|---|
| Built-in Debugger | node --inspect | N/A (needs extension) |
| Primary Tool | Chrome DevTools | Xdebug |
| IDE Integration | VS Code Debugger | VS Code + Xdebug extension |
| Breakpoints | ✅ | ✅ |
| Step Through | ✅ | ✅ |
| Variable Inspection | ✅ | ✅ |
| Remote Debugging | ✅ | ✅ |
| Profiling | Chrome DevTools | Xdebug profiler |
| Console Output | console.log() | var_dump(), error_log() |
Installation
Section titled “Installation”Node.js Debugger (Built-in)
Section titled “Node.js Debugger (Built-in)”# Built-in, just run with --inspectnode --inspect server.jsnode --inspect-brk server.js # Break on first lineXdebug Installation
Section titled “Xdebug Installation”macOS (Homebrew):
pecl install xdebugLinux (Ubuntu/Debian):
sudo apt-get install php-xdebugWindows: Download from https://xdebug.org/download
Verify Installation:
php -v# Should show: with Xdebug v3.xXdebug Configuration
Section titled “Xdebug Configuration”php.ini or xdebug.ini:
[xdebug]zend_extension=xdebug.so
; Xdebug 3.x configurationxdebug.mode=debugxdebug.start_with_request=yesxdebug.client_port=9003xdebug.client_host=127.0.0.1
; Optional: Log for troubleshootingxdebug.log=/tmp/xdebug.logFind your php.ini:
php --iniRestart PHP-FPM/Apache after config changes:
# macOS Homebrewbrew services restart php
# Linuxsudo systemctl restart php8.2-fpmsudo systemctl restart apache2VS Code Setup
Section titled “VS Code Setup”Node.js Debugging
Section titled “Node.js Debugging”.vscode/launch.json:
{ "version": "0.2.0", "configurations": [ { "type": "node", "request": "launch", "name": "Debug Node App", "program": "${workspaceFolder}/src/server.ts", "preLaunchTask": "tsc: build - tsconfig.json", "outFiles": ["${workspaceFolder}/dist/**/*.js"], "console": "integratedTerminal" } ]}Usage:
- Set breakpoints (click left of line numbers)
- Press F5 to start debugging
- Code pauses at breakpoints
PHP/Xdebug Debugging
Section titled “PHP/Xdebug Debugging”Install Extension:
- PHP Debug by Xdebug
.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 Built-in Server", "type": "php", "request": "launch", "runtimeArgs": [ "-S", "localhost:8000", "-t", "public" ], "port": 9003 } ]}Usage:
- Start debugging (F5) - VS Code listens for Xdebug
- Set breakpoints
- Load page in browser
- Code pauses at breakpoints
Basic Debugging
Section titled “Basic Debugging”Node.js Example
Section titled “Node.js Example”import express from 'express';
const app = express();
app.get('/user/:id', (req, res) => { const userId = parseInt(req.params.id); debugger; // Breakpoint (or set in IDE)
const user = getUser(userId); res.json(user);});
function getUser(id: number) { return { id, name: 'Alice' };}
app.listen(3000);Debug:
- Run:
node --inspect server.ts - Open Chrome DevTools (chrome://inspect)
- Set breakpoints
- Make request to trigger breakpoint
PHP Example
Section titled “PHP Example”<?php$userId = $_GET['id'] ?? 1;$userId = (int) $userId; // Breakpoint here
$user = getUser($userId);header('Content-Type: application/json');echo json_encode($user);
function getUser(int $id): array { return ['id' => $id, 'name' => 'Alice']; // Breakpoint here}Debug:
- Start debugging in VS Code (F5)
- Set breakpoints (click left of line numbers)
- Visit: http://localhost:8000?id=1
- Code pauses at breakpoints
Debugging Features
Section titled “Debugging Features”Breakpoints
Section titled “Breakpoints”Both Node.js and PHP:
- Line Breakpoints: Click left of line number
- Conditional Breakpoints: Right-click breakpoint → Edit Breakpoint
- Logpoints: Log without stopping execution
VS Code:
// Conditional breakpointuserId === 1 // Only breaks if userId is 1
// LogpointUser ID: {userId} // Logs without stoppingStep Through Code
Section titled “Step Through Code”| Action | Shortcut | Description |
|---|---|---|
| Continue | F5 | Continue to next breakpoint |
| Step Over | F10 | Execute current line, don’t enter functions |
| Step Into | F11 | Enter function call |
| Step Out | Shift+F11 | Exit current function |
| Restart | Ctrl+Shift+F5 | Restart debugging session |
Identical in both Node.js and PHP debugging!
Variable Inspection
Section titled “Variable Inspection”Watch Variables:
- Variables Panel: See all local variables
- Watch Panel: Add specific variables to watch
- Hover: Hover over variables to see values
- Debug Console: Evaluate expressions
Node.js Debug Console:
> userId1> typeof userId'number'> user{ id: 1, name: 'Alice' }PHP Debug Console:
> $userId1> gettype($userId)"integer"> $userarray(2) { ["id"]=> int(1) ["name"]=> string(5) "Alice" }Call Stack
Section titled “Call Stack”Both debuggers show the call stack:
getUser (index.php:12)← processRequest (index.php:5)← main (index.php:2)Click any frame to inspect variables at that point.
Remote Debugging
Section titled “Remote Debugging”Node.js Remote Debugging
Section titled “Node.js Remote Debugging”# On remote servernode --inspect=0.0.0.0:9229 server.jsVS Code launch.json:
{ "type": "node", "request": "attach", "name": "Attach to Remote", "address": "remote-server.com", "port": 9229, "localRoot": "${workspaceFolder}", "remoteRoot": "/var/www/app"}PHP/Xdebug Remote Debugging
Section titled “PHP/Xdebug Remote Debugging”On remote server (php.ini):
xdebug.mode=debugxdebug.start_with_request=yesxdebug.client_host=your-local-ip # Your machine's IPxdebug.client_port=9003VS Code launch.json:
{ "name": "Listen for Xdebug (Remote)", "type": "php", "request": "launch", "port": 9003, "pathMappings": { "/var/www/html": "${workspaceFolder}" }}SSH Tunnel (if behind firewall):
ssh -R 9003:localhost:9003 user@remote-serverPHPStorm Debugging
Section titled “PHPStorm Debugging”PHPStorm has excellent built-in Xdebug support (no extension needed):
Configuration
Section titled “Configuration”-
Settings → PHP → Debug
- Xdebug port: 9003
- Check “Can accept external connections”
-
Run → Edit Configurations
- Add “PHP Web Page”
- Set start URL: http://localhost:8000
-
Enable Debugging
- Click “Start Listening for PHP Debug Connections” (phone icon)
- Set breakpoints
- Load page in browser
Zero-Configuration Debugging
Section titled “Zero-Configuration Debugging”PHPStorm can detect Xdebug automatically:
- Install browser extension: Xdebug Helper
- Click extension icon → Debug
- PHPStorm automatically catches debug session
- Set breakpoints and debug!
Logging vs Debugging
Section titled “Logging vs Debugging”Node.js Logging
Section titled “Node.js Logging”console.log('User ID:', userId);console.error('Error occurred:', error);console.table(users);console.time('query');// ... operationconsole.timeEnd('query');PHP Logging
Section titled “PHP Logging”<?php// var_dump (detailed output)var_dump($userId);// int(1)
// print_r (readable output)print_r($user);// Array ( [id] => 1 [name] => Alice )
// error_log (to file)error_log("User ID: {$userId}");
// Symfony VarDumper (better formatting)dump($user); // Colorful outputdd($user); // Dump and dieInstall Symfony VarDumper:
composer require symfony/var-dumper<?phprequire 'vendor/autoload.php';
$user = ['id' => 1, 'name' => 'Alice'];dump($user); // Pretty output, continues executiondd($user); // Pretty output, stops executionError Handling
Section titled “Error Handling”Node.js Error Display
Section titled “Node.js Error Display”// Unhandled errors show stack tracethrow new Error('Something went wrong');
process.on('uncaughtException', (err) => { console.error('Uncaught Exception:', err);});PHP Error Display
Section titled “PHP Error Display”<?php// Display errors (development only!)ini_set('display_errors', '1');ini_set('display_startup_errors', '1');error_reporting(E_ALL);
// Throw errorthrow new Exception('Something went wrong');
// Custom error handlerset_error_handler(function($errno, $errstr, $errfile, $errline) { echo "Error: {$errstr} in {$errfile}:{$errline}\n";});
// Custom exception handlerset_exception_handler(function($exception) { echo "Uncaught Exception: " . $exception->getMessage() . "\n";});Production: Never display errors! Log them instead:
<?phpini_set('display_errors', '0');ini_set('log_errors', '1');ini_set('error_log', '/var/log/php_errors.log');Performance Profiling
Section titled “Performance Profiling”Node.js Profiling
Section titled “Node.js Profiling”Chrome DevTools:
node --inspect server.js- Open chrome://inspect
- Click “Profiler” tab
- Start profiling
- Perform operations
- Stop profiling → Analyze flame graph
Xdebug Profiling
Section titled “Xdebug Profiling”Enable in php.ini:
xdebug.mode=profilexdebug.output_dir=/tmp/xdebug_profilesxdebug.start_with_request=triggerTrigger profiling:
# Via URL parametercurl "http://localhost:8000?XDEBUG_PROFILE=1"
# Via cookiecurl -b "XDEBUG_PROFILE=1" http://localhost:8000Analyze with tools:
- QCacheGrind (Windows/Linux)
- KCacheGrind (Linux/macOS via Homebrew)
- Webgrind (Web-based)
Blackfire (Alternative Profiler)
Section titled “Blackfire (Alternative Profiler)”Blackfire is a production-grade profiler (like commercial Chrome DevTools):
# Install Blackfire extensionbrew install blackfire
# Profile via CLIblackfire run php script.php
# Or trigger via browser extensionShows:
- Execution time breakdown
- Memory usage
- SQL queries
- Function call graphs
Common Debugging Scenarios
Section titled “Common Debugging Scenarios”Scenario 1: Debug API Request
Section titled “Scenario 1: Debug API Request”Node.js:
app.post('/api/users', async (req, res) => { debugger; // Set breakpoint const { name, email } = req.body;
const user = await createUser(name, email); res.json(user);});PHP:
<?php$data = json_decode(file_get_contents('php://input'), true);// Set breakpoint here
$name = $data['name'] ?? '';$email = $data['email'] ?? '';
$user = createUser($name, $email);header('Content-Type: application/json');echo json_encode($user);Scenario 2: Debug Database Query
Section titled “Scenario 2: Debug Database Query”Node.js (TypeORM):
const user = await userRepository.findOne({ where: { id: userId }});// Set breakpoint to inspect userPHP (Eloquent):
<?php$user = User::find($userId);// Set breakpoint to inspect $userScenario 3: Debug Async Code
Section titled “Scenario 3: Debug Async Code”Node.js:
async function fetchData() { const response = await fetch('https://api.example.com/data'); debugger; // Inspect response return response.json();}PHP (using Guzzle):
<?phpuse GuzzleHttp\Client;
$client = new Client();$response = $client->get('https://api.example.com/data');// Set breakpoint here$data = json_decode($response->getBody(), true);Xdebug Troubleshooting
Section titled “Xdebug Troubleshooting”Xdebug Not Connecting
Section titled “Xdebug Not Connecting”Check:
# 1. Verify Xdebug is loadedphp -v# Should show: with Xdebug v3.x
# 2. Check configurationphp --ini
# 3. Check Xdebug settingsphp -i | grep xdebugCommon issues:
- Wrong port (9003 in Xdebug 3, was 9000 in Xdebug 2)
- Firewall blocking connection
- Path mappings incorrect (Docker/Remote)
Docker Debugging
Section titled “Docker Debugging”docker-compose.yml:
services: php: image: php:8.2-fpm environment: XDEBUG_MODE: debug XDEBUG_CONFIG: client_host=host.docker.internal volumes: - ./:/var/www/htmlVS Code launch.json:
{ "pathMappings": { "/var/www/html": "${workspaceFolder}" }}Best Practices
Section titled “Best Practices”1. Development vs Production
Section titled “1. Development vs Production”Development:
<?phpini_set('display_errors', '1');error_reporting(E_ALL);xdebug.mode=debugProduction:
<?phpini_set('display_errors', '0');ini_set('log_errors', '1');xdebug.mode=off # Disable Xdebug!2. Use Logging for Production
Section titled “2. Use Logging for Production”<?phpuse Monolog\Logger;use Monolog\Handler\StreamHandler;
$log = new Logger('app');$log->pushHandler(new StreamHandler('/var/log/app.log', Logger::DEBUG));
$log->info('User logged in', ['user_id' => $userId]);$log->error('Database connection failed', ['error' => $error]);3. Conditional Debugging
Section titled “3. Conditional Debugging”<?phpif (isset($_GET['debug'])) { ini_set('display_errors', '1'); error_reporting(E_ALL);}Key Takeaways
Section titled “Key Takeaways”- Xdebug = Node.js debugger - Same features, requires extension installation
- Breakpoints work identically - Click line numbers, set conditions, logpoints
- Step-through commands are the same - F10 (over), F11 (into), Shift+F11 (out)
- VS Code supports both - Consistent debugging experience across languages
- Remote debugging available - SSH tunnels and Docker debugging work well
- Profiling included - Xdebug profiles performance (no separate tool needed)
- Always disable in production - Xdebug adds significant overhead (~30%)
- Port 9003 is default for Xdebug 3+ (was 9000 in Xdebug 2)
xdebug_break()is PHP’sdebugger;statement- Use Xdebug mode flags to enable only what you need (debug, profile, trace)
- phpdbg is built-in alternative for CLI debugging (no extension needed)
- Ray/Telescope for Laravel - advanced debugging tools beyond Xdebug
Comparison Table
Section titled “Comparison Table”| Feature | Node.js | PHP/Xdebug |
|---|---|---|
| Setup | Built-in | Requires extension |
| Breakpoints | ✅ | ✅ |
| Step Through | ✅ | ✅ |
| Variable Inspection | ✅ | ✅ |
| Call Stack | ✅ | ✅ |
| Watch Expressions | ✅ | ✅ |
| Remote Debugging | ✅ | ✅ |
| Profiling | Chrome DevTools | Xdebug profiler |
| IDE Support | VS Code, WebStorm | VS Code, PHPStorm |
| Performance Impact | Minimal | Moderate (disable in prod) |
Quick Reference
Section titled “Quick Reference”| Task | Node.js | PHP/Xdebug |
|---|---|---|
| Start debugger | node --inspect | Enable in php.ini |
| Set breakpoint | Click line number | Click line number |
| Step over | F10 | F10 |
| Step into | F11 | F11 |
| Continue | F5 | F5 |
| Inspect variable | Hover or Console | Hover or Console |
| Remote debug | --inspect=0.0.0.0 | Set client_host |
| Log output | console.log() | error_log(), dump() |
Next Steps
Section titled “Next Steps”Congratulations! You’ve completed Phase 2: Ecosystem. You now understand PHP’s tooling landscape: package management, testing, code quality, build tools, and debugging.
Next Chapter: 11: Async in PHP: Promises vs Fibers
Phase 3 Preview: Advanced topics (Async patterns, APIs, Laravel framework, databases, full-stack development)
Resources
Section titled “Resources”- Xdebug Documentation
- VS Code PHP Debug Extension
- PHPStorm Debugging Guide
- Symfony VarDumper
- Blackfire Profiler
- Monolog
Questions or feedback? Open an issue on GitHub