Understanding PHP's Error Handling Hierarchy

PHP provides multiple mechanisms for handling errors, each serving different purposes. The key distinction lies between traditional error reporting and exception handling, which form the foundation of modern PHP error management.

// Traditional error handling with error_reporting()
error_reporting(E_ALL);
set_error_handler(function($errno, $errstr, $errfile, $errline) {
    // Custom error handling logic
    error_log("[$errno] $errstr in $errfile on line $errline");
});

// Exception handling with custom exceptions
class DatabaseConnectionException extends Exception {}
class ValidationException extends Exception {}

try {
    $connection = new PDO($dsn, $username, $password);
} catch (PDOException $e) {
    throw new DatabaseConnectionException("Database connection failed: " . $e->getMessage());
}

Exception Handling Patterns

1. Custom Exception Classes

Creating meaningful custom exceptions improves code clarity and makes error handling more predictable:

class ApiClientException extends Exception {
    private $responseCode;
    
    public function __construct($message, $responseCode = 0, $previous = null) {
        parent::__construct($message, 0, $previous);
        $this->responseCode = $responseCode;
    }
    
    public function getResponseCode() {
        return $this->responseCode;
    }
}

// Usage example
try {
    $response = file_get_contents('https://api.example.com/data');
    if ($http_response_header[0] !== 'HTTP/1.1 200 OK') {
        throw new ApiClientException(
            'API request failed', 
            (int)substr($http_response_header[0], 9, 3)
        );
    }
} catch (ApiClientException $e) {
    error_log("API Error: " . $e->getMessage() . " (Code: " . $e->getResponseCode() . ")");
}

2. Exception Chaining for Better Debugging

Exception chaining preserves the original error context while adding new information:

class OrderProcessingException extends Exception {
    public function __construct($message, $previous = null) {
        parent::__construct($message, 0, $previous);
    }
}

try {
    $order = processOrder($orderData);
} catch (ValidationException $e) {
    throw new OrderProcessingException(
        "Failed to process order due to validation errors", 
        $e
    );
} catch (DatabaseException $e) {
    throw new OrderProcessingException(
        "Failed to save order to database", 
        $e
    );
}

Logging Strategies and Best Practices

1. Structured Logging with Context

Implement structured logging that includes relevant context for debugging:

class Logger {
    private $logFile;
    
    public function __construct($logFile) {
        $this->logFile = $logFile;
    }
    
    public function error($message, array $context = []) {
        $logEntry = [
            'timestamp' => date('Y-m-d H:i:s'),
            'level' => 'ERROR',
            'message' => $message,
            'context' => $context,
            'trace' => debug_backtrace()
        ];
        
        file_put_contents(
            $this->logFile, 
            json_encode($logEntry) . PHP_EOL, 
            FILE_APPEND | LOCK_EX
        );
    }
}

// Usage example
$logger = new Logger('/var/log/app_errors.log');

try {
    $result = riskyOperation();
} catch (Exception $e) {
    $logger->error('Database operation failed', [
        'user_id' => $_SESSION['user_id'] ?? null,
        'operation' => 'user_update',
        'error_code' => $e->getCode(),
        'stack_trace' => $e->getTraceAsString()
    ]);
}

2. Log Level Configuration

Implement different log levels to control verbosity based on environment:

class LogLevel {
    const DEBUG = 1;
    const INFO = 2;
    const WARNING = 3;
    const ERROR = 4;
    const CRITICAL = 5;
}

class EnvironmentLogger {
    private $logLevel;
    private $logFile;
    
    public function __construct($logLevel, $logFile) {
        $this->logLevel = $logLevel;
        $this->logFile = $logFile;
    }
    
    public function log($level, $message, array $context = []) {
        if ($level >= $this->logLevel) {
            $entry = [
                'timestamp' => date('Y-m-d H:i:s'),
                'level' => array_search($level, (new ReflectionClass('LogLevel'))->getConstants()),
                'message' => $message,
                'context' => $context
            ];
            file_put_contents($this->logFile, json_encode($entry) . PHP_EOL, FILE_APPEND);
        }
    }
    
    public function debug($message, array $context = []) {
        $this->log(LogLevel::DEBUG, $message, $context);
    }
    
    public function error($message, array $context = []) {
        $this->log(LogLevel::ERROR, $message, $context);
    }
}

// Environment-specific configuration
$environment = $_ENV['APP_ENV'] ?? 'development';
$logger = new EnvironmentLogger(
    $environment === 'production' ? LogLevel::WARNING : LogLevel::DEBUG,
    '/var/log/app.log'
);

Error Handling in Web Applications

1. Graceful Degradation for User Experience

Implement error handling that maintains usability even when errors occur:

class ErrorHandler {
    public static function handleException(\Throwable $exception) {
        // Log the exception
        error_log($exception->getMessage() . PHP_EOL . $exception->getTraceAsString());
        
        // Return appropriate HTTP response
        if (headers_sent()) {
            return;
        }
        
        http_response_code(500);
        header('Content-Type: application/json');
        
        if ($_ENV['APP_ENV'] === 'development') {
            echo json_encode([
                'error' => $exception->getMessage(),
                'file' => $exception->getFile(),
                'line' => $exception->getLine(),
                'trace' => $exception->getTrace()
            ]);
        } else {
            echo json_encode(['error' => 'Internal server error']);
        }
    }
    
    public static function handleError($errno, $errstr, $errfile, $errline) {
        if (!(error_reporting() & $errno)) {
            return false;
        }
        
        error_log("[$errno] $errstr in $errfile on line $errline");
        
        // Convert to exception for consistent handling
        throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
    }
}

// Set up error handlers
set_exception_handler([ErrorHandler::class, 'handleException']);
set_error_handler([ErrorHandler::class, 'handleError']);

2. Database Error Specific Handling

Handle database-specific errors with appropriate fallbacks:

class DatabaseManager {
    private $pdo;
    
    public function __construct($dsn, $username, $password) {
        try {
            $this->pdo = new PDO($dsn, $username, $password, [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
            ]);
        } catch (PDOException $e) {
            throw new DatabaseConnectionException("Failed to connect to database: " . $e->getMessage());
        }
    }
    
    public function executeQuery($sql, $params = []) {
        try {
            $stmt = $this->pdo->prepare($sql);
            $stmt->execute($params);
            return $stmt->fetchAll();
        } catch (PDOException $e) {
            // Log the specific database error
            error_log("Database query failed: " . $e->getMessage() . " SQL: $sql");
            
            // Handle specific error types
            switch ($e->getCode()) {
                case 23000: // Integrity constraint violation
                    throw new IntegrityConstraintException("Data integrity error occurred");
                case 1146: // Table doesn't exist
                    throw new TableNotFoundException("Required table not found in database");
                default:
                    throw new DatabaseQueryException("Database query failed: " . $e->getMessage());
            }
        }
    }
}

Comparison of Error Handling Approaches

ApproachProsConsBest Use Case
Traditional Error ReportingSimple setup, built-inLimited control, no contextQuick scripts, simple applications
Exception HandlingStructured, extensible, context-awareMore complex setupEnterprise applications
Custom LoggingDetailed debugging, monitoringRequires additional infrastructureProduction systems
Graceful DegradationBetter user experienceRequires additional logicWeb applications

Key Takeaways

Effective error handling in PHP requires a multi-layered approach combining proper exception management, structured logging, and environment-specific configurations. Custom exceptions provide clarity and context, while structured logging ensures proper debugging capabilities. Implementing these patterns early in your application's development lifecycle prevents costly maintenance issues and improves overall system reliability.

Remember to always log errors appropriately, provide meaningful error messages to users without exposing sensitive information, and implement fallback mechanisms for critical operations. The investment in robust error handling pays dividends in application stability and developer productivity.


Learn more with useful resources

  1. PHP Error Handling Documentation - Official PHP documentation on exception handling and error management
  2. PSR-3 Logger Interface Standard - Standard for PHP logging interfaces and best practices
  3. Modern PHP Error Handling Patterns - Comprehensive guide to error handling best practices in modern PHP applications