Skip to main content

Course Progress

Loading...

Error Handling and Debugging

Duration: 85 minutes
Module 3: 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
  • Never display raw error messages to end users
  • Log all errors for debugging and monitoring
  • Use appropriate error levels (E_ERROR, E_WARNING, etc.)
  • Implement graceful degradation
  • Provide helpful error messages and recovery options
  • Monitor errors in production environments

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
Implement these debugging features:
  1. Create a comprehensive error logging system
  2. Build custom exception classes for your app
  3. Implement error monitoring dashboard
  4. Create debugging toolbar for development
  5. Build query profiler and analyzer
  6. Implement error recovery mechanisms
  7. Create user-friendly error pages
  8. Build automated error alerting system

Additional Resources