Skip to content

PHP Exception

Exceptions provide an object-oriented way to handle errors and exceptional situations, allowing for cleaner and more robust error management than traditional methods.

PHP has a robust exception model. An exception is an object that describes an error or unexpected event that occurred during script execution.

When an exceptional situation occurs, the script can “throw” an exception. This interrupts the normal flow of execution.

What happens when an exception is thrown:

  • The current code state is saved.
  • Execution jumps to the nearest enclosing catch block that can handle the type of exception thrown.
  • If no matching catch block is found in the current function, it looks up the call stack until a handler is found.
  • If the exception reaches the global scope without being caught, the script terminates with a fatal error (unless a global exception handler is set).

Key concepts we will cover:

  • Using try, throw, and catch blocks
  • The finally block (ensuring code execution)
  • Creating custom exception classes
  • Handling multiple exception types
  • Re-throwing exceptions
  • Setting a global exception handler

Note: Exceptions are intended for exceptional circumstances (errors, invalid state), not for normal program control flow like if/else or loops.

Basic Exception Handling: try, throw, catch

Section titled “Basic Exception Handling: try, throw, catch”

When an exception is thrown using the throw keyword, the code immediately following it within the try block is skipped. PHP searches for the first matching catch block.

If an exception isn’t caught, it results in a fatal error:

Example: Throwing an uncaught exception:

<?php
declare(strict_types=1);
function checkNumber(int $number): bool {
if ($number > 1) {
// Throwing an instance of the base Exception class
throw new Exception("Value must be 1 or below");
}
return true;
}
// Trigger exception - no try/catch block here!
try {
checkNumber(2);
} catch (Exception $e) {
// This catch block will handle the exception
echo "Caught exception: " . $e->getMessage();
}
// If we called checkNumber(2) outside a try block:
// It would result in a Fatal error: Uncaught Exception: Value must be 1 or below...
?>

To handle exceptions gracefully, use the try...catch structure:

  1. try - Encloses the code that might throw an exception.
  2. throw - The keyword used to signal an exception. You throw an object that is an instance of Exception or its subclasses (including Error subclasses in PHP 7+).
  3. catch - Declares an exception handler. It specifies the type of exception it can catch and provides a variable (e.g., $e) to access the exception object.

Example: Catching an exception:

<?php
declare(strict_types=1);
function checkNumber(int $number): bool {
if ($number > 1) {
throw new Exception("Value must be 1 or below");
}
return true;
}
try {
// Code that might throw an exception
checkNumber(5);
// This line will not be executed if checkNumber(5) throws
echo 'If you see this, the number is 1 or below.';
} catch (Exception $e) {
// Code to handle the exception
// $e is an object containing details about the exception
echo 'Caught Exception: ' . $e->getMessage() . PHP_EOL;
echo 'File: ' . $e->getFile() . PHP_EOL;
echo 'Line: ' . $e->getLine() . PHP_EOL;
}
echo 'Script continues after the try/catch block.';
?>

Output of the above example:

Caught Exception: Value must be 1 or below
File: /path/to/your/script.php
Line: 7
Script continues after the try/catch block.

A finally block can be added after the catch blocks. The code inside finally will always execute after the try (and any catch) blocks finish, regardless of whether an exception was thrown or caught.

This is useful for cleanup operations, like closing file handles or database connections, that must happen whether the operation succeeded or failed.

<?php
declare(strict_types=1);
function processResource(bool $shouldFail) {
echo "Opening resource...\n";
try {
echo "Processing resource...\n";
if ($shouldFail) {
throw new Exception("Processing failed!");
}
echo "Resource processed successfully.\n";
} catch (Exception $e) {
echo "Caught exception: " . $e->getMessage() . "\n";
} finally {
// This code runs whether an exception occurred or not
echo "Closing resource...\n";
}
}
echo "--- Running without failure ---\n";
processResource(false);
echo "\n--- Running with failure ---\n";
processResource(true);
?>

Output:

--- Running without failure ---
Opening resource...
Processing resource...
Resource processed successfully.
Closing resource...
--- Running with failure ---
Opening resource...
Processing resource...
Caught exception: Processing failed!
Closing resource...

You can create your own exception types by extending the base Exception class (or other exception classes). This allows you to create more specific error handlers and add custom properties or methods to your exceptions.

Custom exception classes inherit properties and methods from Exception (like getMessage(), getFile(), getLine(), getCode(), getTraceAsString()).

Example: Creating and using a ValidationException:

<?php
declare(strict_types=1);
// Custom exception class
class ValidationException extends Exception {
// You can add custom properties or methods
public function getDetailedMessage(): string {
return "Validation Error: {$this->getMessage()} in {$this->getFile()} on line {$this->getLine()}";
}
}
function validateEmail(string $email): bool {
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
// Throw our custom exception
throw new ValidationException("'{$email}' is not a valid email address");
}
// Example: Check if domain is blocked (conceptual)
if (str_ends_with($email, '@blocked.com')) {
// Throwing a standard exception for a different issue
throw new Exception("Email domain is blocked");
}
return true;
}
$email = "invalid-email";
try {
validateEmail($email);
echo "Email is valid.";
} catch (ValidationException $e) {
// Catch specifically our custom validation exception
echo $e->getDetailedMessage();
} catch (Exception $e) {
// Catch any other standard exceptions
echo "General Error: " . $e->getMessage();
}
?>

Output:

Validation Error: 'invalid-email' is not a valid email address in /path/to/your/script.php on line 13

You can use multiple catch blocks to handle different types of exceptions specifically.

PHP checks the catch blocks in order. The first block with a type hint matching the thrown exception (or one of its parent classes) will be executed.

Example: Using multiple catch blocks:

<?php
declare(strict_types=1);
class NetworkException extends Exception {}
class DatabaseException extends Exception {}
function performAction(int $type) {
if ($type === 1) {
throw new NetworkException("Could not connect to server");
} elseif ($type === 2) {
throw new DatabaseException("Error querying database");
} elseif ($type === 3) {
throw new InvalidArgumentException("Invalid action type"); // Built-in SPL exception
} else {
throw new Exception("An unknown error occurred");
}
}
for ($i = 1; $i <= 4; $i++) {
try {
echo "Attempting action type $i: ";
performAction($i);
echo "Success!\n";
} catch (NetworkException $e) {
echo "Network Error: " . $e->getMessage() . "\n";
} catch (DatabaseException $e) {
echo "Database Error: " . $e->getMessage() . "\n";
} catch (InvalidArgumentException $e) { // Catching a specific built-in type
echo "Input Error: " . $e->getMessage() . "\n";
} catch (Exception $e) { // Catch-all for other Exceptions
echo "General Error: " . $e->getMessage() . "\n";
}
}
?>

Output:

Attempting action type 1: Network Error: Could not connect to server
Attempting action type 2: Database Error: Error querying database
Attempting action type 3: Input Error: Invalid action type
Attempting action type 4: General Error: An unknown error occurred

PHP 7.1+ also allows catching multiple types in a single block using the pipe (|) character:

<?php
// ... (Exception classes defined as above)
try {
// ... code that might throw NetworkException or DatabaseException ...
} catch (NetworkException | DatabaseException $e) {
echo "Connectivity Error: " . $e->getMessage();
} catch (Exception $e) {
echo "Other Error: " . $e->getMessage();
}
?>

Sometimes, a catch block might perform partial handling (like logging) and then decide to throw the same or a different exception up the call stack for a higher-level handler to deal with.

This is useful for wrapping low-level exceptions with more context or user-friendly messages.

<?php
declare(strict_types=1);
class ConfigurationException extends Exception {}
function loadConfigFile(string $path) {
if (!file_exists($path)) {
throw new Exception("File not found at: {$path}"); // Generic low-level exception
}
// ... process file ...
if (false /* some config error */) {
throw new Exception("Invalid config format");
}
}
function initializeApp(string $configPath) {
try {
loadConfigFile($configPath);
} catch (Exception $e) {
// Log the original error
error_log("Config Error: " . $e->getMessage());
// Re-throw a more specific, higher-level exception
// Pass the original exception as the 'previous' exception (PHP 5.3+)
throw new ConfigurationException("Failed to initialize application due to configuration issue.", 0, $e);
}
}
try {
initializeApp("nonexistent_config.ini");
} catch (ConfigurationException $e) {
echo "Application Error: " . $e->getMessage() . "\n";
// Optionally inspect the original cause:
if ($previous = $e->getPrevious()) {
echo "Original Cause: " . $previous->getMessage() . "\n";
}
}
?>

Output (will also log the original error):

Application Error: Failed to initialize application due to configuration issue.
Original Cause: File not found at: nonexistent_config.ini

The set_exception_handler() function registers a callback function that will be called for any exception that is not caught by a try...catch block.

This is useful for providing a fallback mechanism to log uncaught exceptions or display a generic error page in production environments, preventing sensitive error details from being shown to users.

<?php
declare(strict_types=1);
// Define the global exception handler function
// It receives the Throwable object (Exception or Error)
function globalExceptionHandler(Throwable $exception) {
// Log the error (recommended for production)
error_log("Uncaught Exception: " . $exception->getMessage() . " in " . $exception->getFile() . ":" . $exception->getLine());
// Display a user-friendly message (avoid showing $exception details directly in production)
http_response_code(500); // Internal Server Error
echo "<h1>Oops! Something went wrong.</h1>";
echo "<p>We've been notified and are looking into it. Please try again later.</p>";
}
// Register the handler
set_exception_handler('globalExceptionHandler');
// Example of code throwing an exception that won't be caught elsewhere
function riskyOperation() {
throw new Exception('Something bad happened unexpectedly!');
}
riskyOperation();
echo "This line will not be reached.";
?>

Output (to browser):

<h1>Oops! Something went wrong.</h1>
<p>We've been notified and are looking into it. Please try again later.</p>

An error message would also appear in the PHP error log.

In PHP 7 and later, many traditional fatal errors (like calling a non-existent function or type errors when strict types are enabled) now throw Error exceptions. Both Error and Exception implement the Throwable interface.

This means you can often catch these critical errors using catch (Error $e) or catch (Throwable $t).

Generally:

  • Exception: Used for errors in the application logic that can potentially be handled (e.g., invalid input, file not found, failed database query).
  • Error: Used for internal PHP errors (e.g., type errors, parsing errors, undefined functions). Catching these is less common, usually indicating a programming mistake that should be fixed, but can be useful for graceful shutdown or logging.

Your catch blocks can catch Throwable to handle both Error and Exception types if needed, especially in global handlers or finally blocks.

<?php
declare(strict_types=1);
try {
// This will throw a TypeError in PHP 7+
strlen(123);
} catch (TypeError $e) {
echo "Caught TypeError: " . $e->getMessage();
} catch (Throwable $t) {
// Fallback for any other Throwable (Error or Exception)
echo "Caught Throwable: " . $t->getMessage();
}
?>