PHP: Error Handling and Debugging

After 32 lessons of writing code, you've undoubtedly stumbled into your share of pitfalls—blank white pages, inscrutable error messages, and not knowing how to use try/catch. This lesson ties all debugging skills together, upgrading you from "finding bugs by luck" to "systematic troubleshooting."

1. Error Levels

PHP has three types of errors:

TEXT 📖 Display only
Notice      ← "You may have overlooked something" (undefined variable, offset)
Warning     ← "The code has a problem but can continue" (include file not found)
Fatal Error ← "The code has a serious error and must stop" (calling a non-existent function)
PHP
<?php
// Notice: undefined variable (PHP continues execution)
echo $undefinedVar;  // Notice: Undefined variable $undefinedVar

// Warning: file not found (PHP continues execution)
include 'nonexistent.php';  // Warning: Failed opening...

// Fatal Error: calling a non-existent function (PHP stops)
thisFunctionDoesNotExist();  // Fatal Error: Call to undefined function
?>

2. Controlling Error Reporting

PHP
<?php
// Development: show all errors
error_reporting(E_ALL);
ini_set('display_errors', '1');

// Production: log but don't display
error_reporting(E_ALL);
ini_set('display_errors', '0');
ini_set('log_errors', '1');
ini_set('error_log', __DIR__ . '/php-errors.log');
?>
🔥 Common Mistake: Never set display_errors = On on a production server—error messages can expose file paths, database passwords, and other sensitive information. Users should see a generic 500 error page; the error details go to the log.

▶ Example: Environment-Aware Configuration

Output:

TEXT 📖 Display only
(no visible output)
PHP
<?php
$isDev = ($_SERVER['SERVER_NAME'] ?? '') === 'localhost';

if ($isDev) {
    error_reporting(E_ALL);
    ini_set('display_errors', '1');
} else {
    error_reporting(E_ALL);
    ini_set('display_errors', '0');
    ini_set('log_errors', '1');
    ini_set('error_log', '/var/log/php/errors.log');
}
?>

Output:

TEXT 📖 Display only
result<br>
result<br>
[Caught Exception] {value->getMessage()}<br>
File: {value->getFile()}<br>
Line: {value->getLine()}<br>
Whether success or failure, finally always runs (e.g., close files, release resources)<br>

3. Exception Handling: try/catch/finally

PHP
<?php
function divide(float $a, float $b): float {
    if ($b === 0.0) {
        throw new Exception("Division by zero is not allowed!");
    }
    return $a / $b;
}

try {
    echo divide(10, 2) . "<br>";  // 5
    echo divide(10, 0) . "<br>";  // Throws an exception
} catch (Exception $e) {
    echo "[Caught Exception] {$e->getMessage()}<br>";
    echo "File: {$e->getFile()}<br>";
    echo "Line: {$e->getLine()}<br>";
} finally {
    echo "Whether success or failure, finally always runs (e.g., close files, release resources)<br>";
}
?>
Block When It Runs
try Execution starts here—exceptions are monitored
catch Runs if an exception is thrown in the try block
finally Always runs regardless (commonly used for cleanup)

4. Custom Exceptions

PHP
<?php
class ValidationException extends Exception {
    private array $errors;
    
    public function __construct(array $errors, string $message = "Validation failed") {
        parent::__construct($message);
        $this->errors = $errors;
    }
    
    public function getErrors(): array {
        return $this->errors;
    }
}

class UserNotFoundException extends Exception {}

// Using custom exceptions
function findUser(int $id): array {
    $users = [1 => ['name' => 'John'], 2 => ['name' => 'Jane']];
    
    if (!isset($users[$id])) {
        throw new UserNotFoundException("User ID {$id} does not exist");
    }
    return $users[$id];
}

function validateUsername(string $name): void {
    $errors = [];
    if ($name === '') $errors[] = 'Username cannot be empty';
    if (strlen($name) > 50) $errors[] = 'Username is too long';
    if (!empty($errors)) throw new ValidationException($errors);
}

// Unified exception handling
try {
    validateUsername('');
    $user = findUser(99);
} catch (ValidationException $e) {
    echo "[Validation Failed]<br>" . implode("<br>", $e->getErrors());
} catch (UserNotFoundException $e) {
    echo "[User Not Found] {$e->getMessage()}";
} catch (Exception $e) {
    echo "[Unknown Error] {$e->getMessage()}";
}
?>

5. Logging

PHP
<?php
class AppLogger {
    public function __construct(
        private string $logDir
    ) {
        if (!is_dir($logDir)) {
            mkdir($logDir, 0755, true);
        }
    }
    
    public function info(string $message, array $context = []): void {
        $this->log('INFO', $message, $context);
    }
    
    public function error(string $message, array $context = []): void {
        $this->log('ERROR', $message, $context);
    }
    
    private function log(string $level, string $message, array $context): void {
        $date = date('Y-m-d');
        $time = date('H:i:s');
        
        $line = "[{$time}] [{$level}] {$message}";
        if (!empty($context)) {
            $line .= ' ' . json_encode($context, JSON_UNESCAPED_UNICODE);
        }
        $line .= "\n";
        
        $file = "{$this->logDir}/app-{$date}.log";
        file_put_contents($file, $line, FILE_APPEND | LOCK_EX);
    }
}

$log = new AppLogger(__DIR__ . '/logs');
$log->info('User logged in', ['username' => 'John', 'ip' => $_SERVER['REMOTE_ADDR'] ?? '']);
$log->error('Payment failed', ['order_id' => 12345, 'reason' => 'Insufficient balance']);
?>

6. Debugging Techniques

▶ Example: Debugging Utility Functions

Output:

TEXT 📖 Display only
array(3) {
  ["name"]=>
  string(4) "John"
  ["skills"]=>
  string(6) "['PHP'"
  [2]=>
  string(7) "MySQL']"
}
Array
(
    [0] => Alice
    [1] => Bob
    [2] => Charlie
)
<pre>
array(3) {
  ["name"]=>
  string(4) "John"
  ["skills"]=>
  string(6) "['PHP'"
  [2]=>
  string(7) "MySQL']"
}
PHP
<?php
// Most commonly used debug output
$data = ['name' => 'John', 'skills' => ['PHP', 'MySQL']];

var_dump($data);  // Detailed output (type + value)
// array(2) { ["name"]=> string(4) "John" ["skills"]=> ... }

print_r($data);   // Concise output
// Array ( [name] => John [skills] => Array ( [0] => PHP [1] => MySQL ) )

// Formatted var_dump (recommended)
echo '<pre>'; print_r($data); echo '</pre>';

// Debug and terminate
var_dump($data); exit;  // or die(var_dump($data));
?>

Output:

TEXT 📖 Display only
int(42)
<pre>
HERE
value

(1) Quick Debugging Reference

PHP
<?php
// Value + type
var_dump($variable);

// Only show in development
if ($isDev) {
    echo '<pre>'; var_dump($data); echo '</pre>';
}

// Log it
error_log("Debug info: " . print_r($data, true));

// Check if execution reaches a certain line
echo "HERE"; exit;  // or error_log("Reached this line");

// View the call stack
debug_print_backtrace();

// Check if a variable is set
var_dump(isset($var), empty($var));
?>

7. Output Buffering and Custom Error Handlers

PHP
<?php
// Output buffering: capture echo/print output
ob_start();
echo "This text won't be sent directly to the browser.";
$content = ob_get_clean();  // Get and clear the buffer
// $content is now "This text won't be sent directly to the browser."

// Custom error handler
function myErrorHandler(int $errno, string $errstr, string $errfile, int $errline): bool {
    $message = "[{$errno}] {$errstr} in {$errfile}:{$errline}";
    error_log($message);
    
    if (ini_get('display_errors')) {
        echo "<div style='background:#ffebee;padding:10px;margin:10px'>
            <strong>PHP Error:</strong> {$message}
        </div>";
    }
    return true;  // Prevent PHP's default handling
}
set_error_handler('myErrorHandler');

// Test the custom error handler
echo $undefined;  // Now displays in a nice HTML box
?>

▶ Example: Catching Multiple Exception Types

Output:

TEXT 📖 Display only

Timeout: {value->getMessage()} — please retry later
Network error: {value->getMessage()}
Unexpected: {value->getMessage()}
PHP
<?php
class NetworkException extends Exception {}
class TimeoutException extends NetworkException {}

function fetchData(string $url): string {
    if (str_contains($url, 'invalid')) {
        throw new NetworkException("Cannot reach {$url}");
    }
    if (str_contains($url, 'slow')) {
        throw new TimeoutException("Request to {$url} timed out");
    }
    return "Data from {$url}";
}

try {
    echo fetchData("https://slow-api.example.com");
} catch (TimeoutException $e) {
    echo "Timeout: {$e->getMessage()} — please retry later";
} catch (NetworkException $e) {
    echo "Network error: {$e->getMessage()}";
} catch (Exception $e) {
    echo "Unexpected: {$e->getMessage()}";
}
?>

Output:

TEXT 📖 Display only
Output displayed

❓ FAQ

Q How do I debug a blank white page?
A The most common cause is a Fatal Error with display_errors = Off. Troubleshooting steps: (1) add error_reporting(E_ALL); ini_set('display_errors', '1');; (2) check the PHP error log; (3) insert echo "1"; exit; at intervals to find which line stops execution.
Q What is Xdebug? Do I need it?
A Xdebug is a professional PHP debugging extension—set breakpoints, step through code, inspect variables. As a beginner, var_dump is enough. When your project exceeds 1000 lines, it's worth installing. The VS Code + Xdebug combo is as comfortable as browser DevTools.

📖 Summary

📝 Exercises

  1. Write a division calculator page that uses try/catch to handle division by zero and display a friendly message.
  2. Create an AppException base class with ValidationException and DatabaseException subclasses. Use them in user registration logic and catch them separately.
  3. Add logging to exercise 2: when exceptions are caught, write error details (timestamp, file, line number) to a log file.
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏