Session 4.6 – Error Handling and Exceptions

Module 4: Server-Side Scripting – PHP | Duration: 1 hr

Learning Objectives

By the end of this session, students will be able to:

  • Understand different types of errors in PHP
  • Implement proper error handling mechanisms
  • Use try-catch blocks for exception handling
  • Create custom exception classes
  • Configure error reporting and logging
  • Apply error handling best practices for production applications

Introduction

Error handling is crucial for building robust and maintainable PHP applications. Proper error management helps identify issues during development and provides graceful degradation in production.

Why Error Handling Matters
  • User Experience: Show friendly messages instead of technical errors
  • Security: Prevent exposure of sensitive system information
  • Debugging: Quickly identify and fix issues
  • Maintenance: Track and monitor application health
  • Recovery: Implement fallback mechanisms

PHP Error Types

E_ERROR

Fatal run-time errors. Script execution stops.

Example: Calling undefined function fatal_function();
E_WARNING

Run-time warnings. Script continues.

Example: Include missing file include('nonexistent.php');
E_NOTICE

Run-time notices. Minor issues.

Example: Undefined variable echo $undefined_var;
E_PARSE

Compile-time parse errors.

Example: Syntax error echo "Hello World" // Missing semicolon
E_STRICT

Code suggestions for forward compatibility.

Example: Deprecated features Using old-style constructors
E_DEPRECATED

Warnings about deprecated features.

Example: Using deprecated functions mysql_connect() // Use mysqli instead

Error Handling Methods

Error Reporting Configuration

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

// Production environment - log errors, don't display
error_reporting(E_ALL);
ini_set('display_errors', 0);
ini_set('log_errors', 1);
ini_set('error_log', '/path/to/error.log');

// Report all errors except notices
error_reporting(E_ALL & ~E_NOTICE);

// Report only errors and warnings
error_reporting(E_ERROR | E_WARNING);
?>
Custom Error Handler

<?php
// Define custom error handler
function customErrorHandler($errno, $errstr, $errfile, $errline) {
    // Error type mapping
    $errorTypes = array(
        E_ERROR => 'Error',
        E_WARNING => 'Warning',
        E_NOTICE => 'Notice',
        E_USER_ERROR => 'User Error',
        E_USER_WARNING => 'User Warning',
        E_USER_NOTICE => 'User Notice'
    );

    $errorType = $errorTypes[$errno] ?? 'Unknown Error';

    // Log error
    $message = "[$errorType] $errstr in $errfile on line $errline";
    error_log($message);

    // Display user-friendly message (production)
    if (ini_get('display_errors') == 0) {
        echo "<div class='alert alert-danger'>An error occurred. Please try again later.</div>";
    } else {
        // Development - show detailed error
        echo "<div class='alert alert-danger'>";
        echo "<strong>$errorType:</strong> $errstr<br>";
        echo "<strong>File:</strong> $errfile<br>";
        echo "<strong>Line:</strong> $errline";
        echo "</div>";
    }

    // Don't execute PHP internal error handler
    return true;
}

// Set custom error handler
set_error_handler('customErrorHandler');

// Trigger errors for testing
echo $undefined;  // E_NOTICE
include('nonexistent.php');  // E_WARNING
?>
Trigger Custom Errors

<?php
function divide($a, $b) {
    if ($b == 0) {
        trigger_error("Division by zero attempted", E_USER_WARNING);
        return false;
    }
    return $a / $b;
}

$result = divide(10, 0);  // Triggers warning

// Different error levels
trigger_error("This is a notice", E_USER_NOTICE);
trigger_error("This is a warning", E_USER_WARNING);
trigger_error("This is an error", E_USER_ERROR);  // Fatal - stops execution
?>

Exception Handling

Try-Catch Blocks

<?php
// Basic try-catch
try {
    $result = 10 / 0;
    echo "This won't execute";
} catch (Exception $e) {
    echo "Error: " . $e->getMessage();
}

// Multiple catch blocks
try {
    $pdo = new PDO("mysql:host=localhost;dbname=test", "user", "pass");
} catch (PDOException $e) {
    echo "Database Error: " . $e->getMessage();
} catch (Exception $e) {
    echo "General Error: " . $e->getMessage();
}

// Try-catch-finally
try {
    $file = fopen("data.txt", "r");
    // Process file
} catch (Exception $e) {
    echo "Error: " . $e->getMessage();
} finally {
    // Always executed
    if (isset($file)) {
        fclose($file);
    }
    echo "Cleanup completed";
}
?>
Throwing Exceptions

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

try {
    echo divide(10, 2);  // 5
    echo divide(10, 0);  // Throws exception
} catch (Exception $e) {
    echo "Error: " . $e->getMessage();
}

// Throw with error code
function validateAge($age) {
    if ($age < 0) {
        throw new Exception("Age cannot be negative", 1001);
    }
    if ($age < 18) {
        throw new Exception("Must be 18 or older", 1002);
    }
    return true;
}

try {
    validateAge(15);
} catch (Exception $e) {
    echo "Error Code " . $e->getCode() . ": " . $e->getMessage();
}
?>
Exception Object Methods

<?php
try {
    throw new Exception("Something went wrong");
} catch (Exception $e) {
    echo "Message: " . $e->getMessage() . "\n";
    echo "Code: " . $e->getCode() . "\n";
    echo "File: " . $e->getFile() . "\n";
    echo "Line: " . $e->getLine() . "\n";
    echo "Trace: " . $e->getTraceAsString() . "\n";
    echo "String: " . $e->__toString() . "\n";
}

// Get trace as array
$trace = $e->getTrace();
foreach ($trace as $frame) {
    echo "Function: " . $frame['function'];
    echo "File: " . $frame['file'];
    echo "Line: " . $frame['line'];
}
?>
Re-throwing Exceptions
    
<?php
function processData($data) {
    try {
        // Some operation
        if (empty($data)) {
            throw new Exception("Data is empty");
        }
    } catch (Exception $e) {
        // Log the error
        error_log($e->getMessage());

        // Re-throw to calling function
        throw $e;
    }
}

try {
    processData([]);
} catch (Exception $e) {
    echo "Caught re-thrown exception: " . $e->getMessage();
}
?>

Custom Exception Classes

Creating Custom Exceptions

<?php
// Basic custom exception
class DatabaseException extends Exception {
    public function errorMessage() {
        return "Database Error [{$this->code}]: {$this->message} in {$this->file} on line {$this->line}";
    }
}

// Custom exception with additional properties
class ValidationException extends Exception {
    private $field;
    private $value;

    public function __construct($message, $field, $value, $code = 0, Exception $previous = null) {
        parent::__construct($message, $code, $previous);
        $this->field = $field;
        $this->value = $value;
    }

    public function getField() {
        return $this->field;
    }

    public function getValue() {
        return $this->value;
    }

    public function getFullError() {
        return "Validation failed for field '{$this->field}' with value '{$this->value}': {$this->message}";
    }
}

// Use custom exceptions
try {
    throw new ValidationException("Invalid email format", "email", "notanemail", 1001);
} catch (ValidationException $e) {
    echo $e->getFullError();
    echo "\nField: " . $e->getField();
    echo "\nValue: " . $e->getValue();
}
?>
Exception Hierarchy

<?php
// Base exception class
class ApplicationException extends Exception {}

// Specific exception types
class DatabaseException extends ApplicationException {}
class ValidationException extends ApplicationException {}
class AuthenticationException extends ApplicationException {}
class AuthorizationException extends ApplicationException {}

// Even more specific
class DatabaseConnectionException extends DatabaseException {}
class DatabaseQueryException extends DatabaseException {}

// Usage with multiple catch blocks
try {
    // throw new DatabaseQueryException("Query failed");
    // throw new ValidationException("Invalid input");
    throw new AuthenticationException("User not authenticated");

} catch (DatabaseException $e) {
    echo "Database issue: " . $e->getMessage();
    // Handle database errors
} catch (ValidationException $e) {
    echo "Validation issue: " . $e->getMessage();
    // Handle validation errors
} catch (AuthenticationException $e) {
    echo "Auth issue: " . $e->getMessage();
    // Redirect to login
} catch (ApplicationException $e) {
    echo "Application issue: " . $e->getMessage();
    // General application error
} catch (Exception $e) {
    echo "Unexpected error: " . $e->getMessage();
    // Catch-all for unexpected errors
}
?>
Practical Example: User Registration

<?php
class UserService {
    public function register($username, $email, $password) {
        try {
            // Validate username
            if (strlen($username) < 3) {
                throw new ValidationException("Username too short", "username", $username);
            }

            // Validate email
            if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
                throw new ValidationException("Invalid email format", "email", $email);
            }

            // Check if user exists
            if ($this->userExists($username)) {
                throw new ValidationException("Username already taken", "username", $username);
            }

            // Connect to database
            $db = $this->getConnection();
            if (!$db) {
                throw new DatabaseConnectionException("Cannot connect to database");
            }

            // Insert user
            $stmt = $db->prepare("INSERT INTO users (username, email, password) VALUES (?, ?, ?)");
            $hashedPassword = password_hash($password, PASSWORD_DEFAULT);

            if (!$stmt->execute([$username, $email, $hashedPassword])) {
                throw new DatabaseQueryException("Failed to create user");
            }

            return true;

        } catch (ValidationException $e) {
            // Log validation error
            error_log($e->getFullError());
            throw $e;  // Re-throw to controller

        } catch (DatabaseException $e) {
            // Log database error with details
            error_log("Database Error: " . $e->getMessage());
            // Don't expose database details to user
            throw new ApplicationException("Registration failed. Please try again later.");

        } catch (Exception $e) {
            // Catch unexpected errors
            error_log("Unexpected error: " . $e->getMessage());
            throw new ApplicationException("An unexpected error occurred");
        }
    }
}

// Controller handling
try {
    $userService = new UserService();
    $userService->register("john", "john@example.com", "password123");
    echo "Registration successful!";

} catch (ValidationException $e) {
    echo "<div class='alert alert-warning'>{$e->getFullError()}</div>";

} catch (ApplicationException $e) {
    echo "<div class='alert alert-danger'>{$e->getMessage()}</div>";

} catch (Exception $e) {
    echo "<div class='alert alert-danger'>System error. Please contact support.</div>";
    error_log("Critical error: " . $e->getMessage());
}
?>

Error Handling Best Practices

Do's
  • Use exceptions for exceptional conditions
  • Create specific exception classes
  • Log all errors with context
  • Show user-friendly messages in production
  • Use finally blocks for cleanup
  • Catch specific exceptions first
  • Document what exceptions functions throw
  • Use meaningful error messages
  • Implement error monitoring tools
  • Test error handling paths
Don'ts
  • Don't display errors in production
  • Don't ignore exceptions (empty catch)
  • Don't use exceptions for flow control
  • Don't expose system details to users
  • Don't catch Exception without good reason
  • Don't suppress errors with @
  • Don't have unclear error messages
  • Don't forget to log errors
  • Don't throw generic exceptions
  • Don't mix errors and exceptions poorly
Production Configuration

<?php
// config/production.php

// Error reporting
error_reporting(E_ALL);
ini_set('display_errors', 0);
ini_set('display_startup_errors', 0);
ini_set('log_errors', 1);
ini_set('error_log', '/var/log/php/error.log');

// Global exception handler
set_exception_handler(function($exception) {
    // Log error with full details
    error_log(sprintf(
        "Uncaught Exception: %s in %s:%d\nStack trace:\n%s",
        $exception->getMessage(),
        $exception->getFile(),
        $exception->getLine(),
        $exception->getTraceAsString()
    ));

    // Show generic message to user
    http_response_code(500);
    include 'templates/error500.php';
});

// Global error handler
set_error_handler(function($errno, $errstr, $errfile, $errline) {
    throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
});
?>

Session Summary

Key Points
  • PHP has various error types: E_ERROR, E_WARNING, E_NOTICE, etc.
  • Configure error_reporting() and ini_set() based on environment
  • Use custom error handlers with set_error_handler()
  • Exceptions provide structured error handling with try-catch-finally
  • Create custom exception classes for specific error types
  • Build exception hierarchies for better error categorization
  • Always log errors; never expose sensitive information to users
  • Use finally blocks for cleanup operations
  • Implement appropriate error handling strategies for production
Next Session Preview

In the next session, we will explore PHP File Handling, learning how to read, write, and manipulate files and directories in PHP applications.