Skip to content

Error Handling

Overview

Proper error handling is crucial for building robust PHP applications. This chapter covers PHP's error handling mechanisms, exceptions, custom error handlers, logging, debugging techniques, and best practices for managing errors in production environments.

PHP Error Types

Understanding Error Levels

php
<?php
// Display all error types for learning purposes
error_reporting(E_ALL);
ini_set('display_errors', 1);

// Different types of errors

// Fatal error - stops script execution
// function_that_does_not_exist(); // Fatal error: Call to undefined function

// Parse error - syntax error
// echo "Hello World" // Parse error: syntax error, unexpected end of file

// Warning - script continues execution
$file = fopen('nonexistent_file.txt', 'r'); // Warning: fopen(): No such file or directory

// Notice - script continues execution
echo $undefined_variable; // Notice: Undefined variable

// Strict standards - code suggestions
// class MyClass {
//     function myMethod() {} // Should be public, protected, or private
// }

// User-generated errors
trigger_error("This is a user error", E_USER_ERROR);
trigger_error("This is a user warning", E_USER_WARNING);
trigger_error("This is a user notice", E_USER_NOTICE);

// Error constants
echo "E_ERROR: " . E_ERROR . "\n";           // 1
echo "E_WARNING: " . E_WARNING . "\n";       // 2
echo "E_PARSE: " . E_PARSE . "\n";           // 4
echo "E_NOTICE: " . E_NOTICE . "\n";         // 8
echo "E_STRICT: " . E_STRICT . "\n";         // 2048
echo "E_ALL: " . E_ALL . "\n";               // All errors
?>

Error Reporting Configuration

php
<?php
// Development environment - display all errors
error_reporting(E_ALL);
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);

// Production environment - hide errors from users
error_reporting(E_ALL);
ini_set('display_errors', 0);
ini_set('log_errors', 1);
ini_set('error_log', '/path/to/error.log');

// Custom error reporting levels
error_reporting(E_ERROR | E_WARNING | E_PARSE); // Only show critical errors
error_reporting(E_ALL & ~E_NOTICE);             // All errors except notices
error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT); // All errors except notices and strict mode

// Check current error reporting level
$currentLevel = error_reporting();
echo "Current error reporting level: $currentLevel\n";

// Temporarily disable error reporting
$oldLevel = error_reporting(0);
// Code that might generate errors
error_reporting($oldLevel); // Restore previous level

// Function to get error level name
function getErrorLevelName($level) {
    $levels = [
        E_ERROR => 'E_ERROR',
        E_WARNING => 'E_WARNING',
        E_PARSE => 'E_PARSE',
        E_NOTICE => 'E_NOTICE',
        E_CORE_ERROR => 'E_CORE_ERROR',
        E_CORE_WARNING => 'E_CORE_WARNING',
        E_COMPILE_ERROR => 'E_COMPILE_ERROR',
        E_COMPILE_WARNING => 'E_COMPILE_WARNING',
        E_USER_ERROR => 'E_USER_ERROR',
        E_USER_WARNING => 'E_USER_WARNING',
        E_USER_NOTICE => 'E_USER_NOTICE',
        E_STRICT => 'E_STRICT',
        E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR',
        E_DEPRECATED => 'E_DEPRECATED',
        E_USER_DEPRECATED => 'E_USER_DEPRECATED'
    ];
    
    return $levels[$level] ?? 'UNKNOWN';
}
?>

Exception Handling

Basic Try-Catch Blocks

php
<?php
// Basic exception handling
try {
    $result = 10 / 0; // This is OK in PHP (returns INF)
    echo "Result: $result\n";
    
    // Throw custom exception
    throw new Exception("Something went wrong!");
    
} catch (Exception $e) {
    echo "Caught exception: " . $e->getMessage() . "\n";
    echo "File: " . $e->getFile() . "\n";
    echo "Line: " . $e->getLine() . "\n";
}

// Multiple catch blocks
try {
    $data = json_decode('{"invalid": json}');
    if (json_last_error() !== JSON_ERROR_NONE) {
        throw new InvalidArgumentException("Invalid JSON data");
    }
    
    $file = fopen('nonexistent.txt', 'r');
    if (!$file) {
        throw new RuntimeException("Cannot open file");
    }
    
} catch (InvalidArgumentException $e) {
    echo "Invalid argument: " . $e->getMessage() . "\n";
} catch (RuntimeException $e) {
    echo "Runtime error: " . $e->getMessage() . "\n";
} catch (Exception $e) {
    echo "Generic exception: " . $e->getMessage() . "\n";
}

// Finally block (PHP 5.5+)
try {
    $file = fopen('data.txt', 'r');
    // Process file
    throw new Exception("Processing error");
} catch (Exception $e) {
    echo "Error: " . $e->getMessage() . "\n";
} finally {
    // This always executes
    if (isset($file) && $file) {
        fclose($file);
        echo "File closed\n";
    }
}
?>

Custom Exception Classes

php
<?php
// Basic custom exception
class AppException extends Exception {
    protected $context = [];
    
    public function __construct($message = "", $code = 0, Exception $previous = null, array $context = []) {
        parent::__construct($message, $code, $previous);
        $this->context = $context;
    }
    
    public function getContext() {
        return $this->context;
    }
    
    public function getFullMessage() {
        $message = $this->getMessage();
        if (!empty($this->context)) {
            $message .= " Context: " . json_encode($this->context);
        }
        return $message;
    }
}

// Specific exception types
class ValidationException extends AppException {
    private $errors = [];
    
    public function __construct($errors, $message = "Validation failed") {
        $this->errors = $errors;
        parent::__construct($message, 400, null, ['errors' => $errors]);
    }
    
    public function getErrors() {
        return $this->errors;
    }
}

class DatabaseException extends AppException {
    public function __construct($message, $query = null, Exception $previous = null) {
        $context = [];
        if ($query) {
            $context['query'] = $query;
        }
        parent::__construct($message, 500, $previous, $context);
    }
}

class AuthenticationException extends AppException {
    public function __construct($message = "Authentication failed") {
        parent::__construct($message, 401);
    }
}

class AuthorizationException extends AppException {
    public function __construct($message = "Access denied") {
        parent::__construct($message, 403);
    }
}

// Usage examples
function validateUser($data) {
    $errors = [];
    
    if (empty($data['name'])) {
        $errors['name'] = 'Name is required';
    }
    
    if (empty($data['email']) || !filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
        $errors['email'] = 'Valid email is required';
    }
    
    if (!empty($errors)) {
        throw new ValidationException($errors);
    }
    
    return true;
}

function authenticateUser($username, $password) {
    // Simulate authentication
    if ($username !== 'admin' || $password !== 'secret') {
        throw new AuthenticationException("Invalid username or password");
    }
    
    return ['id' => 1, 'username' => $username, 'role' => 'admin'];
}

function checkPermission($user, $resource) {
    if ($user['role'] !== 'admin') {
        throw new AuthorizationException("Access to $resource requires admin privileges");
    }
}

// Exception handling in practice
try {
    $userData = ['name' => '', 'email' => 'invalid-email'];
    validateUser($userData);
    
} catch (ValidationException $e) {
    echo "Validation errors:\n";
    foreach ($e->getErrors() as $field => $error) {
        echo "- $field: $error\n";
    }
}

try {
    $user = authenticateUser('user', 'wrong');
    checkPermission($user, 'admin_panel');
    
} catch (AuthenticationException $e) {
    echo "Authentication error: " . $e->getMessage() . "\n";
} catch (AuthorizationException $e) {
    echo "Authorization error: " . $e->getMessage() . "\n";
}
?>

Exception Chaining and Re-throwing

php
<?php
class DataProcessor {
    public function processFile($filename) {
        try {
            $data = $this->readFile($filename);
            return $this->parseData($data);
        } catch (Exception $e) {
            // Re-throw with additional context
            throw new RuntimeException(
                "Failed to process file: $filename",
                0,
                $e // Previous exception
            );
        }
    }
    
    private function readFile($filename) {
        try {
            if (!file_exists($filename)) {
                throw new InvalidArgumentException("File does not exist: $filename");
            }
            
            $content = file_get_contents($filename);
            if ($content === false) {
                throw new RuntimeException("Cannot read file: $filename");
            }
            
            return $content;
        } catch (Exception $e) {
            // Add more context and re-throw
            throw new RuntimeException(
                "File reading failed",
                0,
                $e
            );
        }
    }
    
    private function parseData($data) {
        $parsed = json_decode($data, true);
        
        if (json_last_error() !== JSON_ERROR_NONE) {
            throw new InvalidArgumentException(
                "Invalid JSON data: " . json_last_error_msg()
            );
        }
        
        return $parsed;
    }
}

// Using exception tracing
$processor = new DataProcessor();

try {
    $result = $processor->processFile('nonexistent.json');
} catch (Exception $e) {
    echo "Main error: " . $e->getMessage() . "\n";
    
    // Trace through exception chain
    $current = $e;
    $level = 0;
    
    while ($current !== null) {
        echo str_repeat("  ", $level) . "Level $level: " . $current->getMessage() . "\n";
        echo str_repeat("  ", $level) . "File: " . $current->getFile() . ":" . $current->getLine() . "\n";
        
        $current = $current->getPrevious();
        $level++;
    }
}
?>

Custom Error Handlers

Setting Up Error Handlers

php
<?php
class ErrorHandler {
    private $logFile;
    private $displayErrors;
    
    public function __construct($logFile = 'error.log', $displayErrors = false) {
        $this->logFile = $logFile;
        $this->displayErrors = $displayErrors;
    }
    
    public function register() {
        set_error_handler([$this, 'handleError']);
        set_exception_handler([$this, 'handleException']);
        register_shutdown_function([$this, 'handleShutdown']);
    }
    
    public function handleError($severity, $message, $file, $line) {
        // Don't handle errors suppressed with @
        if (!(error_reporting() & $severity)) {
            return false;
        }
        
        $errorInfo = [
            'type' => 'PHP Error',
            'severity' => $this->getSeverityName($severity),
            'message' => $message,
            'file' => $file,
            'line' => $line,
            'timestamp' => date('Y-m-d H:i:s'),
            'url' => $_SERVER['REQUEST_URI'] ?? 'CLI',
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'CLI'
        ];
        
        $this->logError($errorInfo);
        
        if ($this->displayErrors) {
            $this->displayError($errorInfo);
        }
        
        // Convert errors to exceptions for fatal errors
        if ($severity === E_ERROR || $severity === E_CORE_ERROR || $severity === E_COMPILE_ERROR) {
            throw new ErrorException($message, 0, $severity, $file, $line);
        }
        
        return true; // Don't execute PHP internal error handler
    }
    
    public function handleException($exception) {
        $errorInfo = [
            'type' => 'Uncaught Exception',
            'class' => get_class($exception),
            'message' => $exception->getMessage(),
            'file' => $exception->getFile(),
            'line' => $exception->getLine(),
            'trace' => $exception->getTraceAsString(),
            'timestamp' => date('Y-m-d H:i:s'),
            'url' => $_SERVER['REQUEST_URI'] ?? 'CLI'
        ];
        
        $this->logError($errorInfo);
        
        if ($this->displayErrors) {
            $this->displayException($errorInfo);
        } else {
            $this->displayGenericError();
        }
    }
    
    public function handleShutdown() {
        $error = error_get_last();
        
        if ($error && in_array($error['type'], [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE])) {
            $errorInfo = [
                'type' => 'Fatal Error',
                'severity' => $this->getSeverityName($error['type']),
                'message' => $error['message'],
                'file' => $error['file'],
                'line' => $error['line'],
                'timestamp' => date('Y-m-d H:i:s')
            ];
            
            $this->logError($errorInfo);
            
            if ($this->displayErrors) {
                $this->displayError($errorInfo);
            } else {
                $this->displayGenericError();
            }
        }
    }
    
    private function logError($errorInfo) {
        $logEntry = "[{$errorInfo['timestamp']}] {$errorInfo['type']}: {$errorInfo['message']} " .
                   "in {$errorInfo['file']} on line {$errorInfo['line']}\n";
        
        if (isset($errorInfo['trace'])) {
            $logEntry .= "Stack trace:\n{$errorInfo['trace']}\n";
        }
        
        $logEntry .= str_repeat('-', 80) . "\n";
        
        file_put_contents($this->logFile, $logEntry, FILE_APPEND | LOCK_EX);
    }
    
    private function displayError($errorInfo) {
        if (php_sapi_name() === 'cli') {
            echo "\n{$errorInfo['type']}: {$errorInfo['message']}\n";
            echo "File: {$errorInfo['file']}:{$errorInfo['line']}\n\n";
        } else {
            echo "<div style='background: #ffebee; border: 1px solid #f44336; padding: 10px; margin: 10px;'>";
            echo "<strong>{$errorInfo['type']}:</strong> {$errorInfo['message']}<br>";
            echo "<strong>File:</strong> {$errorInfo['file']}:{$errorInfo['line']}";
            echo "</div>";
        }
    }
    
    private function displayException($errorInfo) {
        if (php_sapi_name() === 'cli') {
            echo "\nUncaught {$errorInfo['class']}: {$errorInfo['message']}\n";
            echo "File: {$errorInfo['file']}:{$errorInfo['line']}\n";
            echo "Stack trace:\n{$errorInfo['trace']}\n\n";
        } else {
            echo "<div style='background: #ffebee; border: 1px solid #f44336; padding: 15px; margin: 10px;'>";
            echo "<h3>Uncaught {$errorInfo['class']}</h3>";
            echo "<p><strong>Message:</strong> {$errorInfo['message']}</p>";
            echo "<p><strong>File:</strong> {$errorInfo['file']}:{$errorInfo['line']}</p>";
            echo "<details><summary>Stack trace</summary><pre>{$errorInfo['trace']}</pre></details>";
            echo "</div>";
        }
    }
    
    private function displayGenericError() {
        if (php_sapi_name() !== 'cli') {
            http_response_code(500);
            echo "<h1>Internal Server Error</h1>";
            echo "<p>An error occurred while processing your request. Please try again later.</p>";
        } else {
            echo "An internal error occurred.\n";
        }
    }
    
    private function getSeverityName($severity) {
        $severities = [
            E_ERROR => 'E_ERROR',
            E_WARNING => 'E_WARNING',
            E_PARSE => 'E_PARSE',
            E_NOTICE => 'E_NOTICE',
            E_CORE_ERROR => 'E_CORE_ERROR',
            E_CORE_WARNING => 'E_CORE_WARNING',
            E_COMPILE_ERROR => 'E_COMPILE_ERROR',
            E_COMPILE_WARNING => 'E_COMPILE_WARNING',
            E_USER_ERROR => 'E_USER_ERROR',
            E_USER_WARNING => 'E_USER_WARNING',
            E_USER_NOTICE => 'E_USER_NOTICE',
            E_STRICT => 'E_STRICT',
            E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR',
            E_DEPRECATED => 'E_DEPRECATED',
            E_USER_DEPRECATED => 'E_USER_DEPRECATED'
        ];
        
        return $severities[$severity] ?? 'UNKNOWN';
    }
}

// Usage
$errorHandler = new ErrorHandler('app_errors.log', true); // Display errors in development
$errorHandler->register();

// Test error handler
echo $undefinedVariable; // Notice
trigger_error("Custom warning", E_USER_WARNING); // Warning
throw new Exception("Test exception"); // Exception
?>

Logging System

Simple Logger Implementation

php
<?php
class Logger {
    const LEVEL_DEBUG = 1;
    const LEVEL_INFO = 2;
    const LEVEL_WARNING = 3;
    const LEVEL_ERROR = 4;
    const LEVEL_CRITICAL = 5;
    
    private $logFile;
    private $minLevel;
    private $maxFileSize;
    
    public function __construct($logFile = 'app.log', $minLevel = self::LEVEL_INFO, $maxFileSize = 10485760) {
        $this->logFile = $logFile;
        $this->minLevel = $minLevel;
        $this->maxFileSize = $maxFileSize; // Default 10MB
    }
    
    public function debug($message, array $context = []) {
        $this->log(self::LEVEL_DEBUG, $message, $context);
    }
    
    public function info($message, array $context = []) {
        $this->log(self::LEVEL_INFO, $message, $context);
    }
    
    public function warning($message, array $context = []) {
        $this->log(self::LEVEL_WARNING, $message, $context);
    }
    
    public function error($message, array $context = []) {
        $this->log(self::LEVEL_ERROR, $message, $context);
    }
    
    public function critical($message, array $context = []) {
        $this->log(self::LEVEL_CRITICAL, $message, $context);
    }
    
    public function log($level, $message, array $context = []) {
        if ($level < $this->minLevel) {
            return;
        }
        
        $this->rotateLogIfNeeded();
        
        $levelName = $this->getLevelName($level);
        $timestamp = date('Y-m-d H:i:s');
        $contextStr = !empty($context) ? ' ' . json_encode($context) : '';
        
        $logEntry = "[$timestamp] [$levelName] $message$contextStr\n";
        
        file_put_contents($this->logFile, $logEntry, FILE_APPEND | LOCK_EX);
    }
    
    public function logException(Exception $exception, $level = self::LEVEL_ERROR) {
        $context = [
            'exception' => get_class($exception),
            'file' => $exception->getFile(),
            'line' => $exception->getLine(),
            'trace' => $exception->getTraceAsString()
        ];
        
        $this->log($level, $exception->getMessage(), $context);
    }
    
    private function rotateLogIfNeeded() {
        if (!file_exists($this->logFile)) {
            return;
        }
        
        if (filesize($this->logFile) > $this->maxFileSize) {
            $backupFile = $this->logFile . '.' . date('Y-m-d-H-i-s');
            rename($this->logFile, $backupFile);
        }
    }
    
    private function getLevelName($level) {
        $levels = [
            self::LEVEL_DEBUG => 'DEBUG',
            self::LEVEL_INFO => 'INFO',
            self::LEVEL_WARNING => 'WARNING',
            self::LEVEL_ERROR => 'ERROR',
            self::LEVEL_CRITICAL => 'CRITICAL'
        ];
        
        return $levels[$level] ?? 'UNKNOWN';
    }
}

// Advanced logger with multiple handlers
class MultiLogger {
    private $handlers = [];
    
    public function addHandler(LoggerInterface $handler) {
        $this->handlers[] = $handler;
    }
    
    public function log($level, $message, array $context = []) {
        foreach ($this->handlers as $handler) {
            $handler->log($level, $message, $context);
        }
    }
    
    // Delegate methods
    public function debug($message, array $context = []) {
        $this->log(Logger::LEVEL_DEBUG, $message, $context);
    }
    
    public function info($message, array $context = []) {
        $this->log(Logger::LEVEL_INFO, $message, $context);
    }
    
    public function warning($message, array $context = []) {
        $this->log(Logger::LEVEL_WARNING, $message, $context);
    }
    
    public function error($message, array $context = []) {
        $this->log(Logger::LEVEL_ERROR, $message, $context);
    }
    
    public function critical($message, array $context = []) {
        $this->log(Logger::LEVEL_CRITICAL, $message, $context);
    }
}

interface LoggerInterface {
    public function log($level, $message, array $context = []);
}

class FileLoggerHandler implements LoggerInterface {
    private $logger;
    
    public function __construct($logFile) {
        $this->logger = new Logger($logFile);
    }
    
    public function log($level, $message, array $context = []) {
        $this->logger->log($level, $message, $context);
    }
}

class EmailLoggerHandler implements LoggerInterface {
    private $email;
    private $minLevel;
    
    public function __construct($email, $minLevel = Logger::LEVEL_ERROR) {
        $this->email = $email;
        $this->minLevel = $minLevel;
    }
    
    public function log($level, $message, array $context = []) {
        if ($level >= $this->minLevel) {
            $subject = "Application Error - Level $level";
            $body = "Message: $message\n";
            $body .= "Context: " . json_encode($context, JSON_PRETTY_PRINT);
            $body .= "\nTime: " . date('Y-m-d H:i:s');
            
            mail($this->email, $subject, $body);
        }
    }
}

// Usage
$logger = new MultiLogger();
$logger->addHandler(new FileLoggerHandler('app.log'));
$logger->addHandler(new EmailLoggerHandler('admin@example.com'));

$logger->info('Application started');
$logger->warning('Low disk space', ['available' => '500MB']);
$logger->error('Database connection failed', ['host' => 'localhost', 'port' => 3306]);
?>

Debugging Techniques

Debug Information and Backtrace

php
<?php
class Debugger {
    public static function dump($variable, $label = null) {
        if ($label) {
            echo "<h4>$label</h4>";
        }
        
        echo "<pre>";
        var_dump($variable);
        echo "</pre>";
    }
    
    public static function backtrace($limit = 10) {
        $trace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, $limit);
        
        echo "<h4>Stack Trace:</h4>";
        echo "<ol>";
        
        foreach ($trace as $i => $frame) {
            $file = $frame['file'] ?? 'Unknown';
            $line = $frame['line'] ?? 'Unknown';
            $function = $frame['function'] ?? 'Unknown';
            $class = $frame['class'] ?? '';
            $type = $frame['type'] ?? '';
            
            echo "<li>";
            echo "<strong>$class$type$function()</strong><br>";
            echo "File: $file:$line";
            echo "</li>";
        }
        
        echo "</ol>";
    }
    
    public static function printMemoryUsage() {
        $memory = memory_get_usage(true);
        $peak = memory_get_peak_usage(true);
        
        echo "Current memory usage: " . self::formatBytes($memory) . "\n";
        echo "Peak memory usage: " . self::formatBytes($peak) . "\n";
    }
    
    public static function printExecutionTime($startTime = null) {
        static $start;
        
        if ($startTime !== null) {
            $start = $startTime;
            return;
        }
        
        if ($start === null) {
            $start = microtime(true);
            return;
        }
        
        $end = microtime(true);
        $execution = $end - $start;
        
        echo "Execution time: " . number_format($execution, 4) . " seconds\n";
    }
    
    public static function profileFunction($callback, $iterations = 1) {
        $startTime = microtime(true);
        $startMemory = memory_get_usage();
        
        for ($i = 0; $i < $iterations; $i++) {
            call_user_func($callback);
        }
        
        $endTime = microtime(true);
        $endMemory = memory_get_usage();
        
        $executionTime = $endTime - $startTime;
        $memoryUsed = $endMemory - $startMemory;
        
        echo "Function profiling results:\n";
        echo "Iterations: $iterations\n";
        echo "Total time: " . number_format($executionTime, 4) . " seconds\n";
        echo "Average time: " . number_format($executionTime / $iterations, 6) . " seconds\n";
        echo "Memory used: " . self::formatBytes($memoryUsed) . "\n";
    }
    
    private static function formatBytes($bytes) {
        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
        
        for ($i = 0; $bytes > 1024 && $i < count($units) - 1; $i++) {
            $bytes /= 1024;
        }
        
        return round($bytes, 2) . ' ' . $units[$i];
    }
}

// Debug helper functions
function dd($variable, $label = null) {
    Debugger::dump($variable, $label);
    die();
}

function bt($limit = 10) {
    Debugger::backtrace($limit);
}

// Usage examples
$data = ['name' => 'Zhang San', 'age' => 30, 'hobbies' => ['Reading', 'Programming']];
Debugger::dump($data, 'User Data');

function testFunction() {
    function nestedFunction() {
        Debugger::backtrace();
    }
    nestedFunction();
}

testFunction();

// Profile function
Debugger::profileFunction(function() {
    $sum = 0;
    for ($i = 0; $i < 100000; $i++) {
        $sum += $i;
    }
    return $sum;
}, 10);

Debugger::printMemoryUsage();
?>

Assertions and Test Helpers

php
<?php
class Assert {
    public static function true($condition, $message = 'Assertion failed') {
        if (!$condition) {
            throw new AssertionError($message);
        }
    }
    
    public static function false($condition, $message = 'Assertion failed') {
        if ($condition) {
            throw new AssertionError($message);
        }
    }
    
    public static function equals($expected, $actual, $message = 'Values are not equal') {
        if ($expected !== $actual) {
            $message .= ". Expected: " . var_export($expected, true) . 
                       ", Actual: " . var_export($actual, true);
            throw new AssertionError($message);
        }
    }
    
    public static function notNull($value, $message = 'Value is null') {
        if ($value === null) {
            throw new AssertionError($message);
        }
    }
    
    public static function instanceOf($object, $class, $message = 'Object is not instance of class') {
        if (!($object instanceof $class)) {
            $actualClass = is_object($object) ? get_class($object) : gettype($object);
            $message .= ". Expected: $class, Actual: $actualClass";
            throw new AssertionError($message);
        }
    }
    
    public static function throws($callback, $expectedException = Exception::class, $message = 'Expected exception was not thrown') {
        try {
            call_user_func($callback);
            throw new AssertionError($message);
        } catch (Exception $e) {
            if (!($e instanceof $expectedException)) {
                $actualClass = get_class($e);
                throw new AssertionError("Expected $expectedException, got $actualClass: " . $e->getMessage());
            }
        }
    }
}

class AssertionError extends Exception {}

// Test helper class
class TestRunner {
    private $tests = [];
    private $passed = 0;
    private $failed = 0;
    
    public function addTest($name, $callback) {
        $this->tests[$name] = $callback;
    }
    
    public function run() {
        echo "Running tests…\n\n";
        
        foreach ($this->tests as $name => $callback) {
            try {
                call_user_func($callback);
                echo "✓ $name\n";
                $this->passed++;
            } catch (Exception $e) {
                echo "✗ $name: " . $e->getMessage() . "\n";
                $this->failed++;
            }
        }
        
        echo "\nResults: {$this->passed} passed, {$this->failed} failed\n";
    }
}

// Example usage
$runner = new TestRunner();

$runner->addTest('Test basic math', function() {
    Assert::equals(4, 2 + 2, 'Addition should work');
    Assert::equals(0, 2 - 2, 'Subtraction should work');
});

$runner->addTest('Test string operations', function() {
    Assert::equals('Hello World', 'Hello' . ' World', 'String concatenation');
    Assert::equals(2, mb_strlen('你好'), 'String length');
});

$runner->addTest('Test exception throwing', function() {
    Assert::throws(function() {
        throw new InvalidArgumentException('Test exception');
    }, InvalidArgumentException::class);
});

$runner->run();
?>

Production Error Handling

Error Monitoring and Alerts

php
<?php
class ErrorMonitor {
    private $config;
    private $logger;
    
    public function __construct($config) {
        $this->config = $config;
        $this->logger = new Logger($config['log_file']);
    }
    
    public function handleError($error) {
        // Log error
        $this->logger->error($error['message'], $error);
        
        // Check if should alert
        if ($this->shouldAlert($error)) {
            $this->sendAlert($error);
        }
        
        // Update error statistics
        $this->updateStats($error);
    }
    
    private function shouldAlert($error) {
        // Alert on critical errors
        if ($error['severity'] >= Logger::LEVEL_CRITICAL) {
            return true;
        }
        
        // Alert if error rate is too high
        $recentErrors = $this->getRecentErrorCount();
        if ($recentErrors > $this->config['max_errors_per_minute']) {
            return true;
        }
        
        // Alert on specific error patterns
        foreach ($this->config['alert_patterns'] as $pattern) {
            if (strpos($error['message'], $pattern) !== false) {
                return true;
            }
        }
        
        return false;
    }
    
    private function sendAlert($error) {
        $alertData = [
            'timestamp' => date('Y-m-d H:i:s'),
            'error' => $error,
            'server' => $_SERVER['SERVER_NAME'] ?? 'CLI',
            'url' => $_SERVER['REQUEST_URI'] ?? 'CLI'
        ];
        
        // Send email alert
        if ($this->config['email_alerts']) {
            $this->sendEmailAlert($alertData);
        }
        
        // Send to monitoring service (e.g., Slack, PagerDuty)
        if ($this->config['webhook_url']) {
            $this->sendWebhookAlert($alertData);
        }
    }
    
    private function sendEmailAlert($alertData) {
        $subject = "Critical Error Alert - " . $alertData['server'];
        $body = "A critical error occurred:\n\n";
        $body .= "Time: " . $alertData['timestamp'] . "\n";
        $body .= "Message: " . $alertData['error']['message'] . "\n";
        $body .= "File: " . $alertData['error']['file'] . ":" . $alertData['error']['line'] . "\n";
        $body .= "URL: " . $alertData['url'] . "\n";
        
        mail($this->config['alert_email'], $subject, $body);
    }
    
    private function sendWebhookAlert($alertData) {
        $payload = json_encode([
            'text' => 'Critical Error Alert',
            'attachments' => [
                [
                    'color' => 'danger',
                    'fields' => [
                        ['title' => 'Message', 'value' => $alertData['error']['message'], 'short' => false],
                        ['title' => 'File', 'value' => $alertData['error']['file'] . ':' . $alertData['error']['line'], 'short' => true],
                        ['title' => 'Server', 'value' => $alertData['server'], 'short' => true]
                    ]
                ]
            ]
        ]);
        
        $ch = curl_init($this->config['webhook_url']);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
        curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_exec($ch);
        curl_close($ch);
    }
    
    private function getRecentErrorCount() {
        // Implementation would check error count in the last minute
        // This could use database, cache, or log file analysis
        return 0; // Placeholder
    }
    
    private function updateStats($error) {
        // Update error statistics for monitoring dashboard
        // This could write to database or metrics service
    }
}

// Configuration
$errorConfig = [
    'log_file' => 'errors.log',
    'email_alerts' => true,
    'alert_email' => 'admin@example.com',
    'webhook_url' => 'https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK',
    'max_errors_per_minute' => 10,
    'alert_patterns' => ['Database', 'Payment', 'Authentication']
];

$monitor = new ErrorMonitor($errorConfig);

// Integration with error handler
set_exception_handler(function($exception) use ($monitor) {
    $error = [
        'severity' => Logger::LEVEL_CRITICAL,
        'message' => $exception->getMessage(),
        'file' => $exception->getFile(),
        'line' => $exception->getLine(),
        'trace' => $exception->getTraceAsString()
    ];
    
    $monitor->handleError($error);
    
    // Display user-friendly error page
    if (!headers_sent()) {
        http_response_code(500);
        include 'error_pages/500.html';
    }
});
?>

Next Steps

Now that you understand error handling, let's explore file handling in File Handling.

Practice Exercises

  1. Create a comprehensive error handling system for a web application
  2. Build a logging system with multiple output formats (file, database, email)
  3. Implement custom exception classes for different types of application errors
  4. Create a debugging toolkit with performance profiling and memory monitoring
  5. Set up error monitoring and alerts for a production application

Proper error handling is essential for building robust, maintainable PHP applications!

Content is for learning and research only.