NIXX/DEVv1.14.0
ArticlesFavorites
Sign In
Sign In
Articles

Welcome to our blog

A curated collection of insightful articles, practical guides, and expert tips designed to simplify your workflow

Cover image for: Modern PHP Error Handling with Throwable, try/catch, and finally
July 22, 20256 MIN READ min readBy ℵi✗✗

Modern PHP Error Handling with Throwable, try/catch, and finally

Learn modern PHP error handling with Throwable, try/catch, and finally. This guide explores best practices, custom exceptions, and real-world examples.

phpwebdevtutorial
ℵi✗✗

ℵi✗✗

Full-Stack Developer

Passionate about building tools and sharing knowledge with the developer community.

Was this helpful?

Popular Posts

  • NixOS vs. Arch Linux: Which One Belongs in Your Dev Setup?

    NixOS vs. Arch Linux: Which One Belongs in Your Dev Setup?

    5 MIN READ min read

  • How to Enable HTTPS on Localhost in Under 2 Minutes

    How to Enable HTTPS on Localhost in Under 2 Minutes

    3 MIN READ min read

  • Migrating from Create React App (CRA) to Vite: A Step-by-Step Guide

    Migrating from Create React App (CRA) to Vite: A Step-by-Step Guide

    4 MIN READ min read

  • Array Destructuring in PHP: A Practical Guide for Modern Developers

    Array Destructuring in PHP: A Practical Guide for Modern Developers

    5 MIN READ min read

Recommended Products

  • Apple iPad (7th Gen)

    Apple iPad (7th Gen)

    4.3
  • Fitbit Versa 4

    Fitbit Versa 4

    4.3
  • JBL Flip 6

    JBL Flip 6

    4.8
  • Dell 24 Monitor — SE2425HM Full HD

    Dell 24 Monitor — SE2425HM Full HD

    4.7

May contain affiliate links

Topics

webdev33productivity16cybersecurity12javascript11automation9guide8react7typescript7php6tutorial6freelancing5github actions5privacy5how to4Node.js4
+111 more topics →
🇺🇸USD ACCOUNTOpen a free US-based USD accountReceive & save in USD — powered by ClevaSponsoredInterserver Hosting#1 VALUEAffordable, reliable hosting from $2.50/mo99.9% uptimeSponsored

PHP 7 unified error and exception handling under the Throwable interface, which changed the way failures are caught and handled in PHP applications. Before PHP 7, fatal errors like calling an undefined function were not catchable at all. Now both Error and Exception implement Throwable, and a single catch block can handle either.

This guide covers how Throwable, try/catch/finally, and custom exceptions work, with examples drawn from common application scenarios: file handling, API calls, and payment processing.

What this covers:

  • The Throwable interface and what it unifies

  • The difference between Exception and Error

  • try, catch, and finally syntax and behavior

  • Multiple catch blocks and catch order

  • Creating custom exception classes

  • Best practices for logging, rethrowing, and production error messages

  • Real-world example: file upload validation


The Throwable Interface

In PHP 7 and above, both Error and Exception implement the Throwable interface:

interface Throwable {
    public function getMessage(): string;
    public function getCode(): int;
    public function getFile(): string;
    public function getLine(): int;
    public function getTrace(): array;
    public function getPrevious(): ?Throwable;
}

This means any failure in PHP, whether an application-level exception or a PHP engine error, can be caught by a single catch block:

try {
    // risky operation
} catch (Throwable $e) {
    echo "Caught: " . $e->getMessage();
}

Catching Throwable is the broadest possible net. In most application code, catching specific types is preferable because it makes the handling intent explicit. Throwable is most useful at the outer boundary of an application, such as a global error handler or a middleware layer, where the goal is to prevent unhandled failures from reaching the user.


Exception vs. Error

The two concrete implementations of Throwable serve different purposes.

Exception is thrown by application code to signal expected failure conditions: invalid input, a failed API call, a business rule violation, a missing resource. These are conditions the application knows about and can handle.

Error is thrown by the PHP engine to signal problems with the code itself: calling an undefined function, type errors, parse errors, division by zero. Before PHP 7, these were fatal and not catchable.

try {
    notAFunction(); // throws Error
} catch (Error $e) {
    echo "PHP error: " . $e->getMessage();
}

The practical implication: catch Exception when handling application logic failures. Catch Error when defensive code needs to handle engine-level problems. Catch Throwable when either type is possible and the handler should treat them the same way.


try, catch, and finally

Basic structure

try {
    echo intdiv(10, 0); // throws DivisionByZeroError
} catch (DivisionByZeroError $e) {
    echo "Cannot divide by zero.";
} finally {
    echo "This always runs.";
}

The try block contains the code that may throw. The catch block handles a specific thrown type. The finally block runs regardless of whether an exception was thrown or caught, and regardless of whether the catch block itself threw.

finally is the correct place for cleanup operations that must happen whether or not the operation succeeded: closing a file handle, releasing a database connection, or releasing a lock.

$handle = null;

try {
    $handle = fopen('data.csv', 'r');
    // process file
} catch (Throwable $e) {
    logError($e);
} finally {
    if ($handle !== null) {
        fclose($handle);
    }
}

Note the null check on $handle in the finally block. If fopen itself fails, $handle will be false or null, and calling fclose on it would produce another error. Checking before closing is the defensive pattern.

Multiple catch blocks

Multiple catch blocks handle different exception types from the same try block. PHP matches them in order from top to bottom and executes the first matching block.

try {
    processRequest($input);
} catch (InvalidArgumentException $e) {
    echo "Invalid input: " . $e->getMessage();
} catch (RuntimeException $e) {
    echo "Runtime error: " . $e->getMessage();
} catch (Throwable $e) {
    echo "Unexpected error: " . $e->getMessage();
}

Always order catch blocks from most specific to most general. If Throwable is listed first, it will match everything and the specific blocks below it will never execute.

PHP 8.0 introduced union catches, which handle multiple exception types in a single block when they should be handled the same way:

} catch (InvalidArgumentException | RuntimeException $e) {
    echo "Input or runtime error: " . $e->getMessage();
}

Custom Exception Classes

Custom exception classes improve code clarity by making failure conditions explicit and catchable by specific type.

class PaymentFailedException extends RuntimeException {}
class InsufficientFundsException extends PaymentFailedException {}

Extending RuntimeException rather than the base Exception class is a common convention for exceptions representing runtime failures in application logic. Both work, but a consistent hierarchy makes it easier to catch groups of related exceptions.

Usage:

function processPayment(float $amount): void {
    if ($amount <= 0) {
        throw new InvalidArgumentException("Amount must be greater than zero.");
    }

    if (!hasSufficientFunds($amount)) {
        throw new InsufficientFundsException("Insufficient funds for this transaction.");
    }
}

try {
    processPayment(0);
} catch (InsufficientFundsException $e) {
    // handle low balance specifically
    notifyUser($e->getMessage());
} catch (PaymentFailedException $e) {
    // handle any other payment failure
    logError($e);
}

Because InsufficientFundsException extends PaymentFailedException, catching PaymentFailedException would also catch InsufficientFundsException. Listing the more specific type first handles it separately while allowing the parent catch to handle everything else in the hierarchy.


Best Practices

Catch specific exceptions before general ones. The order of catch blocks determines which handler runs. Starting broad and getting specific is a common mistake that swallows exceptions with the wrong handler.

Log errors with context, not just messages. $e->getMessage() is rarely enough. Log the exception class, the stack trace, and relevant request context so the log entry is actionable.

} catch (Throwable $e) {
    error_log(sprintf(
        "[%s] %s in %s on line %d\n%s",
        get_class($e),
        $e->getMessage(),
        $e->getFile(),
        $e->getLine(),
        $e->getTraceAsString()
    ));
}

Never expose raw exception messages to users in production. Stack traces, file paths, and database error messages can reveal information useful to an attacker. Show users a generic message and log the detail internally.

Use finally for resource cleanup. File handles, database connections, and locks should be released in finally blocks to guarantee cleanup even when exceptions are thrown.

Rethrow when appropriate. If a catch block cannot fully handle an exception, rethrowing it (or wrapping it in a more contextual exception) passes the failure up the call stack to a handler with more context.

try {
    $result = callExternalApi();
} catch (NetworkException $e) {
    throw new ServiceUnavailableException("Payment service unreachable.", 0, $e);
}

Wrapping the original exception as the $previous argument preserves the full error chain for logging while presenting a more meaningful exception type to the caller.


Real-World Example: File Upload Validation

class UploadException extends RuntimeException {}
class FileTooLargeException extends UploadException {}

function validateUpload(array $file): void {
    if ($file['error'] !== UPLOAD_ERR_OK) {
        throw new UploadException("Upload failed with error code: " . $file['error']);
    }

    $maxSize = 2 * 1024 * 1024; // 2 MB
    if ($file['size'] > $maxSize) {
        throw new FileTooLargeException("File exceeds the 2 MB size limit.");
    }
}

try {
    validateUpload($_FILES['avatar']);
    echo "Upload accepted.";
} catch (FileTooLargeException $e) {
    echo "The file is too large. Please upload a file under 2 MB.";
    logError($e);
} catch (UploadException $e) {
    echo "The upload could not be completed. Please try again.";
    logError($e);
}

This example illustrates several of the patterns described above: a custom exception hierarchy, specific catch blocks ordered from most to least specific, user-facing messages that do not expose internal details, and a separate logging call that records the full exception.


Key Takeaways

  • Throwable is the common interface for both Exception and Error in PHP 7 and above. Catching Throwable handles either type.

  • Exception represents application-level failures; Error represents PHP engine-level problems. Catch the appropriate type for the failure being handled.

  • finally runs regardless of whether an exception was thrown or caught. Use it for resource cleanup.

  • Catch blocks are matched in order. List specific exception types before general ones.

  • Custom exception classes make failure conditions explicit and catchable by type. Extending a common base class allows related exceptions to be caught as a group.

  • Never expose raw exception messages to users in production. Log the detail internally and return a generic message externally.

  • Wrap caught exceptions with $previous when rethrowing to preserve the full error chain for debugging.


Conclusion

Structured error handling in PHP is not defensive coding for its own sake. It produces applications that fail in predictable, recoverable ways, give operators enough information to diagnose problems, and give users enough information to understand what happened without exposing anything they should not see.

The patterns here, specific catch blocks, custom exception hierarchies, finally for cleanup, and careful logging, compose well. A codebase that applies them consistently is significantly easier to debug and maintain than one where error handling is an afterthought.


Building an exception hierarchy for a specific domain and want a second opinion on the structure? Share it in the comments.

Topics
phpwebdevtutorial

Discussion

Join the discussion

Sign in to share your thoughts and engage with the community.

Sign In
Loading comments…

Continue Reading

More Articles

View all
Cover image for: AI for DevOps: Tools That Are Already Changing the Game
Jun 17, 20256 MIN READ min read

AI for DevOps: Tools That Are Already Changing the Game

How artificial intelligence is transforming CI/CD pipelines, monitoring, and incident response—today.

Cover image for: What Is Identity Theft (and How to Protect Yourself Online)
Nov 17, 20256 MIN READ min read

What Is Identity Theft (and How to Protect Yourself Online)

Identity theft can happen to anyone — often without you even realizing it. Learn what it means, how it happens, and the smart steps you can take today to keep your personal information safe online.

Cover image for: The 3-Device Rule: How to Simplify Your Digital Life and Stop Overbuying Tech
Aug 5, 20255 MIN READ min read

The 3-Device Rule: How to Simplify Your Digital Life and Stop Overbuying Tech

Tired of juggling too many devices? Learn the 3-device rule that helps you streamline your digital life, reduce clutter, and focus on what really matters.

Cover image for: Best Web Hosting of 2026 (Honest Picks From Real-World Use)
Jan 1, 20267 MIN READ min read

Best Web Hosting of 2026 (Honest Picks From Real-World Use)

Choosing the right web hosting in 2026 isn't just about price. A breakdown of the best providers, focusing on reliability, performance, and support.

|Made with · © 2026|TermsPrivacy
AboutBlogContact

Free, open-source tools for developers and creators · Community driven