Skip to content

PHP Error Handling — Exceptions, Error Levels, and Custom Error Handlers

DodaTech Updated 2026-06-28 8 min read

In this tutorial, you will learn about PHP Error Handling. We cover key concepts, practical examples, and best practices to help you master this topic.

PHP error handling uses a combination of error levels for warnings and notices, exceptions for recoverable errors with try-catch blocks, and custom handlers for centralized error management across the application lifecycle.

Why It Matters

Errors happen. A file is missing, a database connection fails, or a user enters invalid data. How your application responds determines whether users see a white screen of death or a helpful error message. PHP's error handling system has evolved from simple error reporting to a robust exception model. Understanding errors vs exceptions, when to use each, and how to log and display them appropriately separates professional applications from hobby projects.

Real-World Use

Laravel's error handler converts PHP errors into exceptions and renders beautiful debug pages in development. Symfony's error handler logs errors and returns appropriate HTTP status codes. WordPress uses a custom error handler to suppress notices in production. DodaTech's production servers log all errors to a central system while returning generic error messages to users to avoid leaking sensitive information.

What You Will Learn

  • PHP error levels: E_WARNING, E_NOTICE, E_ERROR, and others
  • The error_reporting and display_errors configuration
  • Try-catch-finally for Exception Handling
  • Custom error handlers with set_error_handler
  • Custom exception handlers with set_exception_handler
  • Logging errors to files and external services
  • Development vs production error configuration

Learning Path

flowchart LR
  A[JSON API] --> B[Error Handling
You are here] B --> C[Classes and OOP] C --> D[Inheritance and Traits] D --> E[Namespaces] style B fill:#f90,color:#fff

PHP Error Levels

PHP categorizes errors by severity:

<?php
// Fatal errors - script stops
// E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR

// Warnings - non-fatal, script continues
// E_WARNING, E_NOTICE, E_USER_WARNING, E_DEPRECATED

// Notices - minor issues
// E_NOTICE, E_STRICT

Common error levels:

Constant Value Description
E_ERROR 1 Fatal runtime error, execution stops
E_WARNING 2 Non-fatal warning, execution continues
E_PARSE 4 Compile-time parse error
E_NOTICE 8 Notice of possible issue
E_DEPRECATED 8192 Warning about deprecated code
E_USER_ERROR 256 User-generated error with trigger_error
E_ALL 32767 All errors and warnings

Error Reporting Configuration

Control which errors are reported and displayed:

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

// Production: log errors, do not display
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_errors.log');

Triggering Errors

You can trigger errors programmatically:

<?php
function divide(int $a, int $b): float {
    if ($b === 0) {
        trigger_error('Division by zero', E_USER_WARNING);
        return 0;
    }
    return $a / $b;
}

echo divide(10, 0);  // Triggers E_USER_WARNING

Try-Catch Exceptions

PHP 5 introduced exceptions for object-oriented error handling:

<?php
function readConfig(string $file): array {
    if (!file_exists($file)) {
        throw new \RuntimeException("Config file not found: $file");
    }

    $data = parse_ini_file($file);
    if ($data === false) {
        throw new \RuntimeException("Failed to parse config: $file");
    }

    return $data;
}

try {
    $config = readConfig('/etc/app/config.ini');
    echo "Database host: " . $config['db_host'];
} catch (\RuntimeException $e) {
    echo "Configuration error: " . $e->getMessage();
    // Log the error
    error_log($e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine());
}

Multiple Catch Blocks

<?php
try {
    $result = processPayment($amount, $cardNumber);
} catch (\InvalidArgumentException $e) {
    // Validation error - show to user
    echo "Invalid input: " . $e->getMessage();
} catch (\PDOException $e) {
    // Database error - log, do not show details
    error_log("Database error: " . $e->getMessage());
    echo "A system error occurred. Please try again later.";
} catch (\Throwable $e) {
    // Any other error
    error_log("Unexpected error: " . $e->getMessage());
    echo "An unexpected error occurred.";
}

Finally Block

The finally block always executes, whether or not an exception was thrown:

<?php
function processFile(string $path): void {
    $handle = fopen($path, 'r');
    if (!$handle) {
        throw new \RuntimeException("Cannot open file: $path");
    }

    try {
        // Process file
        while (($line = fgets($handle)) !== false) {
            // Parse line
        }
    } finally {
        fclose($handle);  // Always close the handle
    }
}

Custom Exception Classes

Create domain-specific exceptions for better error handling:

<?php
namespace App\Exception;

class DatabaseException extends \RuntimeException {}
class ValidationException extends \InvalidArgumentException {}
class AuthenticationException extends \RuntimeException {}
class NotFoundException extends \RuntimeException {}

// Usage
class UserService {
    public function findById(int $id): array {
        $user = $this->db->find('users', $id);
        if (!$user) {
            throw new NotFoundException("User with ID $id not found");
        }
        return $user;
    }

    public function register(array $data): array {
        if (empty($data['email'])) {
            throw new ValidationException('Email is required');
        }
        if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
            throw new ValidationException('Invalid email format');
        }
        // ...
    }
}

Custom Error Handler

Convert PHP errors to exceptions for uniform handling:

<?php
set_error_handler(function (int $level, string $message, string $file, int $line): bool {
    // Convert error to ErrorException
    if (error_reporting() & $level) {
        throw new \ErrorException($message, 0, $level, $file, $line);
    }
    return true;  // Prevent PHP's internal error handler
});

// Now all PHP errors become exceptions
try {
    echo $undefinedVariable;  // Triggers E_NOTICE, now thrown as exception
} catch (\ErrorException $e) {
    echo "Caught error: " . $e->getMessage();
}

Restoring the Error Handler

// Temporarily override
set_error_handler($customHandler);
// Do risky operation
restore_error_handler();  // Restore previous handler

Custom Exception Handler

Catch all uncaught exceptions at the application level:

<?php
set_exception_handler(function (\Throwable $e): never {
    // Log the error
    error_log(sprintf(
        "Uncaught %s: %s in %s:%d\n%s",
        get_class($e),
        $e->getMessage(),
        $e->getFile(),
        $e->getLine(),
        $e->getTraceAsString()
    ));

    // Return appropriate response
    if (PHP_SAPI === 'cli') {
        echo "Fatal error: " . $e->getMessage() . "\n";
        exit(1);
    }

    http_response_code(500);
    header('Content-Type: application/json');
    echo json_encode([
        'error' => true,
        'message' => 'An internal error occurred',
    ]);
    exit;
});

Development vs Production Configuration

<?php
class ErrorConfig {
    public static function configure(string $environment): void {
        if ($environment === 'development') {
            error_reporting(E_ALL);
            ini_set('display_errors', '1');
            ini_set('display_startup_errors', '1');
            ini_set('log_errors', '1');
        } else {
            error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT);
            ini_set('display_errors', '0');
            ini_set('display_startup_errors', '0');
            ini_set('log_errors', '1');
            ini_set('error_log', '/var/log/app/php_errors.log');
        }
    }
}

// Usage at application entry point
ErrorConfig::configure($_ENV['APP_ENV'] ?? 'production');

Graceful Shutdown

Register a shutdown function for fatal errors:

<?php
register_shutdown_function(function (): void {
    $error = error_get_last();
    if ($error !== null && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
        error_log(sprintf(
            "Fatal: %s in %s:%d",
            $error['message'],
            $error['file'],
            $error['line']
        ));

        if (PHP_SAPI !== 'cli') {
            http_response_code(500);
            echo "A critical error occurred.";
        }
    }
});

Common Mistakes

1. Suppressing Errors with @

$result = @file_get_contents('/nonexistent/file');  // Bad practice

Use proper error handling instead of suppression. The @ operator hides all errors and makes debugging difficult.

2. Displaying Detailed Errors in Production

ini_set('display_errors', '1');  // Exposes file paths, stack traces

Set display_errors = Off in production. Log errors instead.

3. Catching Exception Too Broadly

try {
    // code
} catch (\Exception $e) {  // Too broad

Catch specific exception types. Use \Throwable only at the top-level handler.

4. Not Logging Errors

catch (\Exception $e) {
    echo $e->getMessage();  // User sees message, but it is not logged
}

Always log errors for diagnosis. Display only generic messages to users.

5. Forgetting Finally for Resource Cleanup

If an exception occurs, file handles and database connections may leak. Use finally or destructors for cleanup.

Practice Questions

  1. What is the difference between E_ERROR and E_WARNING? E_ERROR stops script execution. E_WARNING allows the script to continue. Both are logged if logging is enabled.

  2. What does set_error_handler do? It overrides PHP's default error handler with a custom function. The custom handler receives error level, message, file, and line.

  3. What is the \Throwable interface? It is the base interface for all errors and exceptions in PHP 7+. Both \Exception and \Error implement it.

  4. How do you prevent sensitive information from leaking in production errors? Set display_errors=Off, log errors to a file, return a generic error page, and never include details in the response.

  5. Challenge: Build a complete error handling system with a custom error handler that converts errors to exceptions, an exception handler for uncaught exceptions, a shutdown handler for fatal errors, and environment-aware configuration.

Mini Project: Error Handling Bootstrap

Create an application bootstrap with comprehensive error handling:

<?php
class Application {
    private string $env;

    public function __construct(string $env = 'production') {
        $this->env = $env;
        $this->configureErrorHandling();
    }

    private function configureErrorHandling(): void {
        if ($this->env === 'development') {
            error_reporting(E_ALL);
            ini_set('display_errors', '1');
        } else {
            error_reporting(E_ALL & ~E_DEPRECATED);
            ini_set('display_errors', '0');
            ini_set('log_errors', '1');
            ini_set('error_log', __DIR__ . '/../var/log/error.log');
        }

        set_error_handler(function ($level, $message, $file, $line) {
            if (error_reporting() & $level) {
                throw new \ErrorException($message, 0, $level, $file, $line);
            }
            return true;
        });

        set_exception_handler(function (\Throwable $e) {
            error_log(sprintf("[%s] %s in %s:%d", get_class($e), $e->getMessage(), $e->getFile(), $e->getLine()));
            if ($this->env === 'development') {
                echo "<pre>" . $e . "</pre>";
            } else {
                http_response_code(500);
                echo "An internal error occurred.";
            }
            exit;
        });

        register_shutdown_function(function () {
            $error = error_get_last();
            if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
                error_log("FATAL: {$error['message']} in {$error['file']}:{$error['line']}");
                if ($this->env !== 'development') {
                    http_response_code(500);
                    echo "A critical error occurred.";
                }
            }
        });
    }
}

$app = new Application($_ENV['APP_ENV'] ?? 'production');

FAQ

What is the difference between an error and an exception?

Errors are PHP-level issues (warnings, notices) that use the old error system. Exceptions are object-oriented, throwable, and catchable with try-catch. PHP 7 unified both under \Throwable.

Should I catch \Exception or \Throwable?

Catch \Throwable to handle both exceptions and PHP 7 errors. Catch \Exception or specific types when you only want to handle traditional exceptions.

What does the @ operator do?

The @ operator suppresses error messages for a single expression. It is bad practice because it hides all errors, making debugging difficult.

How do I log errors to a file?

Set error_log = /path/to/file in php.ini or use ini_set(). Call error_log('message') to write to the log programmatically.

What is the difference between display_errors and log_errors?

display_errors shows errors in the output (for development). log_errors writes errors to a file (for production). Both should be enabled but display_errors off in production.

What is Next

Now that you understand error handling, proceed to Classes and OOP to learn object-oriented programming in PHP. Then read Inheritance and Traits to understand code reuse through inheritance.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro