Error Handling and Debugging
Learning Objectives
- Implement comprehensive error handling strategies
- Master exception handling in PHP
- Set up effective logging systems
- Use debugging tools and techniques
- Handle database-specific errors
- Create custom error handlers
- Implement error monitoring and alerting
- Debug performance issues
Error Handling Strategy
Robust error handling is crucial for maintaining stable applications and providing good user experience. Let's build a comprehensive system! 🛡️
Error Handling Best Practices
Comprehensive Error Handling System
graph TB
ERROR[Error Occurs] --> CATCH[Error Handler]
CATCH --> TYPE{Error Type}
TYPE --> |Database| DB[Database Handler]
TYPE --> |Validation| VAL[Validation Handler]
TYPE --> |System| SYS[System Handler]
DB --> LOG[Log Error]
VAL --> LOG
SYS --> LOG
LOG --> NOTIFY{Severity}
NOTIFY --> |Critical| ALERT[Send Alert]
NOTIFY --> |Warning| MONITOR[Monitor]
LOG --> USER[User Message]
style ERROR fill:#fee2e2
style CATCH fill:#dbeafe
style LOG fill:#dcfce7
style ALERT fill:#fef3c7
Complete Error Handling Implementation
<?php
// ========================================
// ERROR HANDLER CLASS
// ========================================
class ErrorHandler {
private static $instance = null;
private $logger;
private $isDevelopment;
private $errorLevels = [
E_ERROR => 'Fatal Error',
E_WARNING => 'Warning',
E_PARSE => 'Parse Error',
E_NOTICE => 'Notice',
E_CORE_ERROR => 'Core Error',
E_CORE_WARNING => 'Core Warning',
E_COMPILE_ERROR => 'Compile Error',
E_COMPILE_WARNING => 'Compile Warning',
E_USER_ERROR => 'User Error',
E_USER_WARNING => 'User Warning',
E_USER_NOTICE => 'User Notice',
E_STRICT => 'Strict Notice',
E_RECOVERABLE_ERROR => 'Recoverable Error',
E_DEPRECATED => 'Deprecated',
E_USER_DEPRECATED => 'User Deprecated'
];
private function __construct() {
$this->isDevelopment = $_ENV['APP_ENV'] === 'development';
$this->logger = new Logger();
$this->registerHandlers();
}
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Register all error handlers
*/
private function registerHandlers() {
// Error handler
set_error_handler([$this, 'handleError']);
// Exception handler
set_exception_handler([$this, 'handleException']);
// Fatal error handler
register_shutdown_function([$this, 'handleShutdown']);
// Set error reporting level
if ($this->isDevelopment) {
error_reporting(E_ALL);
ini_set('display_errors', 1);
} else {
error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);
ini_set('display_errors', 0);
}
ini_set('log_errors', 1);
ini_set('error_log', __DIR__ . '/logs/php-error.log');
}
/**
* Handle PHP errors
*/
public function handleError($errno, $errstr, $errfile, $errline) {
// Check if error reporting is disabled
if (!(error_reporting() & $errno)) {
return false;
}
$errorType = $this->errorLevels[$errno] ?? 'Unknown Error';
// Create error context
$error = [
'type' => $errorType,
'message' => $errstr,
'file' => $errfile,
'line' => $errline,
'trace' => debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS),
'time' => date('Y-m-d H:i:s'),
'request' => [
'method' => $_SERVER['REQUEST_METHOD'] ?? 'CLI',
'uri' => $_SERVER['REQUEST_URI'] ?? 'CLI',
'ip' => $_SERVER['REMOTE_ADDR'] ?? 'localhost'
]
];
// Log error
$this->logError($error, $errno);
// Display error in development
if ($this->isDevelopment) {
$this->displayError($error);
}
// Don't execute PHP internal error handler
return true;
}
/**
* Handle uncaught exceptions
*/
public function handleException($exception) {
$error = [
'type' => 'Exception',
'class' => get_class($exception),
'message' => $exception->getMessage(),
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'trace' => $exception->getTrace(),
'time' => date('Y-m-d H:i:s'),
'request' => [
'method' => $_SERVER['REQUEST_METHOD'] ?? 'CLI',
'uri' => $_SERVER['REQUEST_URI'] ?? 'CLI',
'ip' => $_SERVER['REMOTE_ADDR'] ?? 'localhost'
]
];
// Log exception
$this->logger->critical('Uncaught Exception', $error);
// Display error page
$this->displayErrorPage($exception);
}
/**
* Handle fatal errors
*/
public function handleShutdown() {
$error = error_get_last();
if ($error !== null && in_array($error['type'], [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE])) {
$this->handleError($error['type'], $error['message'], $error['file'], $error['line']);
// Send alert for fatal errors
$this->sendAlert('Fatal Error', $error);
}
}
/**
* Log error based on severity
*/
private function logError($error, $errno) {
switch ($errno) {
case E_ERROR:
case E_CORE_ERROR:
case E_COMPILE_ERROR:
case E_PARSE:
$this->logger->critical($error['message'], $error);
$this->sendAlert('Critical Error', $error);
break;
case E_WARNING:
case E_CORE_WARNING:
case E_COMPILE_WARNING:
case E_USER_WARNING:
$this->logger->warning($error['message'], $error);
break;
case E_NOTICE:
case E_USER_NOTICE:
$this->logger->notice($error['message'], $error);
break;
default:
$this->logger->info($error['message'], $error);
}
}
/**
* Display error in development
*/
private function displayError($error) {
echo "<div style='border:2px solid #ff0000; padding:10px; margin:10px;'>";
echo "<h3>{$error['type']}: {$error['message']}</h3>";
echo "<p>File: {$error['file']} on line {$error['line']}</p>";
if (!empty($error['trace'])) {
echo "<h4>Stack Trace:</h4><pre>";
foreach ($error['trace'] as $i => $trace) {
echo "#{$i} ";
echo isset($trace['file']) ? $trace['file'] : '[internal]';
echo isset($trace['line']) ? "({$trace['line']})" : '';
echo isset($trace['function']) ? ": {$trace['function']}()" : '';
echo "\n";
}
echo "</pre>";
}
echo "</div>";
}
/**
* Display user-friendly error page
*/
private function displayErrorPage($exception = null) {
// Clear any existing output
if (ob_get_length()) {
ob_clean();
}
// Set appropriate HTTP status code
http_response_code(500);
// Load error template
if ($this->isDevelopment && $exception) {
include __DIR__ . '/templates/error-dev.php';
} else {
include __DIR__ . '/templates/error-500.php';
}
exit;
}
/**
* Send alert for critical errors
*/
private function sendAlert($subject, $error) {
// Email alert
if (defined('ALERT_EMAIL')) {
$message = "Error Details:\n" . print_r($error, true);
mail(ALERT_EMAIL, $subject, $message);
}
// Slack notification
if (defined('SLACK_WEBHOOK')) {
$this->sendSlackNotification($subject, $error);
}
}
/**
* Send Slack notification
*/
private function sendSlackNotification($subject, $error) {
$payload = [
'text' => $subject,
'attachments' => [[
'color' => 'danger',
'fields' => [
['title' => 'Error', 'value' => $error['message'], 'short' => false],
['title' => 'File', 'value' => $error['file'], 'short' => true],
['title' => 'Line', 'value' => $error['line'], 'short' => true],
['title' => 'Time', 'value' => $error['time'], 'short' => true],
['title' => 'URL', 'value' => $error['request']['uri'] ?? 'N/A', 'short' => true]
]
]]
];
$ch = curl_init(SLACK_WEBHOOK);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_exec($ch);
curl_close($ch);
}
}
// ========================================
// DATABASE ERROR HANDLER
// ========================================
class DatabaseErrorHandler {
/**
* Handle database connection errors
*/
public static function handleConnectionError(PDOException $e) {
$error = [
'type' => 'Database Connection Error',
'message' => self::sanitizeMessage($e->getMessage()),
'code' => $e->getCode(),
'time' => date('Y-m-d H:i:s')
];
// Log error
error_log(json_encode($error));
// User-friendly message
if ($_ENV['APP_ENV'] === 'production') {
throw new Exception('Unable to connect to the database. Please try again later.');
} else {
throw $e;
}
}
/**
* Handle query errors
*/
public static function handleQueryError(PDOException $e, $query = null) {
$error = [
'type' => 'Database Query Error',
'message' => self::sanitizeMessage($e->getMessage()),
'code' => $e->getCode(),
'query' => $query,
'time' => date('Y-m-d H:i:s')
];
// Log error
error_log(json_encode($error));
// Check for specific error types
if (strpos($e->getMessage(), 'Duplicate entry') !== false) {
throw new ValidationException('This record already exists.');
} elseif (strpos($e->getMessage(), 'foreign key constraint') !== false) {
throw new ValidationException('This record cannot be deleted as it is referenced by other data.');
} else {
throw new Exception('A database error occurred. Please try again.');
}
}
/**
* Sanitize error messages
*/
private static function sanitizeMessage($message) {
// Remove sensitive information
$message = preg_replace('/password=[\S]+/', 'password=***', $message);
$message = preg_replace('/\b\d{4,}\b/', '***', $message); // Hide long numbers
return $message;
}
}
// ========================================
// CUSTOM EXCEPTIONS
// ========================================
class ValidationException extends Exception {
protected $errors = [];
public function __construct($message = "", $errors = [], $code = 400) {
parent::__construct($message, $code);
$this->errors = $errors;
}
public function getErrors() {
return $this->errors;
}
}
class AuthenticationException extends Exception {
public function __construct($message = "Authentication failed", $code = 401) {
parent::__construct($message, $code);
}
}
class AuthorizationException extends Exception {
public function __construct($message = "Access denied", $code = 403) {
parent::__construct($message, $code);
}
}
class NotFoundException extends Exception {
public function __construct($message = "Resource not found", $code = 404) {
parent::__construct($message, $code);
}
}
// ========================================
// LOGGER CLASS
// ========================================
class Logger {
private $logFile;
private $logLevels = [
'DEBUG' => 0,
'INFO' => 1,
'NOTICE' => 2,
'WARNING' => 3,
'ERROR' => 4,
'CRITICAL' => 5,
'ALERT' => 6,
'EMERGENCY' => 7
];
public function __construct($logFile = null) {
$this->logFile = $logFile ?: __DIR__ . '/logs/app.log';
$this->ensureLogDirectory();
}
private function ensureLogDirectory() {
$dir = dirname($this->logFile);
if (!is_dir($dir)) {
mkdir($dir, 0777, true);
}
}
public function log($level, $message, $context = []) {
$timestamp = date('Y-m-d H:i:s');
$contextJson = !empty($context) ? json_encode($context) : '';
$logEntry = "[{$timestamp}] {$level}: {$message} {$contextJson}" . PHP_EOL;
file_put_contents($this->logFile, $logEntry, FILE_APPEND | LOCK_EX);
// Also log to separate files by level
if (in_array($level, ['ERROR', 'CRITICAL', 'ALERT', 'EMERGENCY'])) {
$errorFile = str_replace('.log', '-error.log', $this->logFile);
file_put_contents($errorFile, $logEntry, FILE_APPEND | LOCK_EX);
}
}
public function debug($message, $context = []) {
$this->log('DEBUG', $message, $context);
}
public function info($message, $context = []) {
$this->log('INFO', $message, $context);
}
public function notice($message, $context = []) {
$this->log('NOTICE', $message, $context);
}
public function warning($message, $context = []) {
$this->log('WARNING', $message, $context);
}
public function error($message, $context = []) {
$this->log('ERROR', $message, $context);
}
public function critical($message, $context = []) {
$this->log('CRITICAL', $message, $context);
}
public function alert($message, $context = []) {
$this->log('ALERT', $message, $context);
}
public function emergency($message, $context = []) {
$this->log('EMERGENCY', $message, $context);
}
}
Debugging Techniques
Advanced Debugging Implementation
<?php
// ========================================
// DEBUG HELPER CLASS
// ========================================
class Debug {
private static $startTime;
private static $queries = [];
private static $timers = [];
/**
* Pretty print variable
*/
public static function dump($variable, $label = null, $exit = false) {
echo "<pre style='background:#f4f4f4; padding:10px; border:1px solid #ddd;'>";
if ($label) {
echo "<strong>{$label}:</strong>\n";
}
if (is_bool($variable)) {
echo $variable ? 'true' : 'false';
} elseif (is_null($variable)) {
echo 'null';
} else {
print_r($variable);
}
echo "</pre>";
if ($exit) {
exit;
}
}
/**
* Debug with backtrace
*/
public static function trace($variable = null, $depth = 5) {
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, $depth);
echo "<div style='background:#fff3cd; padding:10px; border:1px solid #ffc107;'>";
echo "<h4>Debug Trace</h4>";
if ($variable !== null) {
echo "<strong>Value:</strong> ";
var_dump($variable);
echo "<br><br>";
}
echo "<strong>Call Stack:</strong><ol>";
foreach ($trace as $item) {
echo "<li>";
echo isset($item['file']) ? $item['file'] : '[internal]';
echo isset($item['line']) ? " ({$item['line']})" : '';
echo isset($item['function']) ? " - {$item['function']}()" : '';
echo "</li>";
}
echo "</ol></div>";
}
/**
* Log to file with context
*/
public static function log($data, $label = null) {
$logFile = __DIR__ . '/logs/debug.log';
$timestamp = date('Y-m-d H:i:s');
$entry = "[{$timestamp}]";
if ($label) {
$entry .= " {$label}:";
}
$entry .= " " . (is_string($data) ? $data : json_encode($data)) . PHP_EOL;
file_put_contents($logFile, $entry, FILE_APPEND | LOCK_EX);
}
/**
* Start timer
*/
public static function startTimer($name = 'default') {
self::$timers[$name] = microtime(true);
}
/**
* End timer and display result
*/
public static function endTimer($name = 'default', $display = true) {
if (!isset(self::$timers[$name])) {
return false;
}
$elapsed = microtime(true) - self::$timers[$name];
$milliseconds = round($elapsed * 1000, 2);
if ($display) {
echo "<div style='background:#d4edda; padding:5px; border:1px solid #c3e6cb;'>";
echo "Timer '{$name}': {$milliseconds}ms";
echo "</div>";
}
unset(self::$timers[$name]);
return $milliseconds;
}
/**
* Memory usage
*/
public static function memory($label = null) {
$usage = memory_get_usage(true);
$peak = memory_get_peak_usage(true);
$format = function($bytes) {
$units = ['B', 'KB', 'MB', 'GB'];
$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);
return round($bytes / pow(1024, $pow), 2) . ' ' . $units[$pow];
};
echo "<div style='background:#e2e3e5; padding:5px; border:1px solid #d6d8db;'>";
if ($label) {
echo "<strong>{$label}</strong><br>";
}
echo "Memory: " . $format($usage) . " / Peak: " . $format($peak);
echo "</div>";
}
/**
* SQL query logger
*/
public static function logQuery($query, $params = [], $time = null) {
self::$queries[] = [
'query' => $query,
'params' => $params,
'time' => $time,
'backtrace' => debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3)
];
}
/**
* Display all queries
*/
public static function showQueries() {
if (empty(self::$queries)) {
return;
}
echo "<div style='background:#f8f9fa; padding:10px; border:1px solid #dee2e6;'>";
echo "<h4>SQL Queries (" . count(self::$queries) . ")</h4>";
$totalTime = 0;
foreach (self::$queries as $i => $query) {
echo "<div style='margin-bottom:10px; padding:5px; background:white;'>";
echo "<strong>Query #" . ($i + 1) . "</strong>";
if ($query['time']) {
echo " ({$query['time']}ms)";
$totalTime += $query['time'];
}
echo "<pre style='margin:5px 0;'>" . htmlspecialchars($query['query']) . "</pre>";
if (!empty($query['params'])) {
echo "<small>Params: " . json_encode($query['params']) . "</small>";
}
echo "</div>";
}
echo "<strong>Total time: {$totalTime}ms</strong>";
echo "</div>";
}
/**
* Debug bar for development
*/
public static function renderDebugBar() {
if (($_ENV['APP_ENV'] ?? 'production') !== 'development') {
return;
}
$executionTime = isset($_SERVER['REQUEST_TIME_FLOAT'])
? round((microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']) * 1000, 2)
: 0;
$memoryUsage = round(memory_get_peak_usage(true) / 1024 / 1024, 2);
$queryCount = count(self::$queries);
echo <<prepare($query);
$stmt->execute(['active']);
Debug::logQuery($query, ['active'], Debug::endTimer('database', false));
} catch (PDOException $e) {
DatabaseErrorHandler::handleConnectionError($e);
}
// Debug output
Debug::dump($_POST, 'POST Data');
Debug::trace('Checkpoint reached');
Debug::memory('After processing');
Debug::showQueries();
Debug::renderDebugBar();
Practice Exercise
Error Handling Challenges