Skip to content

Handling Runtime Exceptions in PHP

Think of an exception as a signal that something unexpected happened in your code. Just like when you’re driving and encounter a roadblock: you need to find an alternative route.

In programming terms:

  • An exception is an event that occurs during program execution that disrupts the normal flow,
  • Instead of your program crashing, exceptions allow you to handle errors gracefully,
  • You can throw exceptions when problems occur and catch them to handle the situation.

PHP offers two main categories of exceptions:

  1. Built-in Exceptions: PHP comes with ready-to-use exceptions for common problems:

    • Exception - The base exception class
    • InvalidArgumentException - When wrong data is passed to a function
    • RuntimeException - When something goes wrong during execution
    • PDOException - Database-related errors
  2. Custom Exceptions: You can create your own specialized exceptions for specific situations in your application by extending the base Exception class.


Exception handling follows a simple pattern: try to do something, and if it fails, catch the problem.

Think of it like this:

  • Try: “Let me attempt this risky operation”
  • Catch: “If something goes wrong, here’s how I’ll handle it”
try {
// Code that might fail
$result = someRiskyFunction();
echo "Success: $result";
} catch (Exception $e) {
// What to do if it fails
echo 'Something went wrong: ' . $e->getMessage();
}

When you detect a problem in your code, you can “throw” an exception to signal the issue:

function divide($a, $b) {
// Check for invalid input
if ($b === 0) {
throw new Exception("Cannot divide by zero!");
}
return $a / $b;
}
// Using the function with exception handling
try {
echo divide(10, 2); // Works fine: outputs 5
echo divide(10, 0); // This will throw an exception
} catch (Exception $e) {
echo "Error: " . $e->getMessage();
}

Custom exceptions help you create meaningful error categories for your application. Instead of using generic exceptions, you can create specific ones that make your code more readable.

Why use custom exceptions?

  • More descriptive error handling
  • Easier to catch specific types of errors
  • Better code organization
// Create meaningful exception names
class InvalidEmailException extends Exception {}
class UserNotFoundException extends Exception {}
class InsufficientFundsException extends Exception {}
// Example usage
function validateEmail($email) {
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new InvalidEmailException("Invalid email format: $email");
}
return true;
}
try {
validateEmail("not-an-email");
} catch (InvalidEmailException $e) {
echo "Email validation failed: " . $e->getMessage();
} catch (Exception $e) {
echo "Something else went wrong: " . $e->getMessage();
}

The finally block is like a cleanup crew. It always runs, whether an exception occurred or not.

When to use finally:

  • Closing files or database connections
  • Cleaning up temporary resources
  • Logging operations
$file = null;
try {
$file = fopen('data.txt', 'r');
// Read file content
$content = fread($file, 1000);
echo $content;
} catch (Exception $e) {
echo "Error reading file: " . $e->getMessage();
} finally {
// Always close the file, even if an error occurred
if ($file) {
fclose($file);
echo "File closed successfully.";
}
}

Sometimes you need layers of error handling. Nested try-catch blocks let you handle different types of problems at different levels. For example, processing user input and saving it to a database may throw exceptions at different stages.

function saveUserData($data) {
try {
// Validate input — throws if name is empty
if (empty($data['name'])) {
throw new InvalidArgumentException("Name cannot be empty");
}
try {
// Database operation — can fail independently
if ($data['name'] == 'error') {
throw new RuntimeException("Database error occurred");
}
echo "User data saved successfully\n";
} catch (RuntimeException $e) {
echo "Database problem: " . $e->getMessage() . "\n";
}
} catch (InvalidArgumentException $e) {
echo "Validation failed: " . $e->getMessage() . "\n";
}
}
// Test with valid and invalid input
saveUserData(['name' => 'John']); // "User data saved successfully"
saveUserData(['name' => 'error']); // "Database problem: Database error occurred"
saveUserData(['name' => '']); // "Validation failed: Name cannot be empty"

  1. Keep it simple: Don’t over-nest try-catch blocks. If you have more than 2-3 levels, consider refactoring your code.

  2. Be specific: Catch specific exceptions instead of generic ones. This makes debugging much easier.

    // Good
    catch (InvalidArgumentException $e) { /* handle */ }
    catch (DatabaseException $e) { /* handle */ }
    // Less helpful
    catch (Exception $e) { /* handle everything the same way */ }
  3. Always log errors: Keep a record of what went wrong for debugging later.

    catch (Exception $e) {
    error_log("Error in function X: " . $e->getMessage());
    // Then handle the error appropriately
    }
  4. Fail gracefully: Show user-friendly messages, not technical error details.

    catch (DatabaseException $e) {
    error_log($e->getMessage()); // Log the technical details
    echo "Sorry, we're having trouble saving your data right now."; // User-friendly message
    }
  5. Don’t ignore exceptions: If you can’t handle an exception properly, re-throw it so something else can deal with it.