Exceptions & Try-Catch
Exceptions provide a powerful way to handle errors in PHP. They allow you to separate error-handling code from normal code, making your applications more maintainable and robust.
What are Exceptions?
Exceptions are objects that represent errors or exceptional conditions. When an error occurs, an exception is "thrown" and can be "caught" by exception handlers.
<?php
// Basic exception structure
try {
// Code that might throw an exception
throw new Exception("Something went wrong!");
} catch (Exception $e) {
// Handle the exception
echo "Caught exception: " . $e->getMessage();
}
// Output: Caught exception: Something went wrong!
?>
Note: Unlike errors, exceptions can be caught and handled gracefully, allowing your application to recover or fail elegantly.
Try-Catch-Finally Blocks
The try-catch-finally structure provides complete control over exception handling:
<?php
function readConfigFile($filename) {
$file = null;
try {
// Try to open the file
if (!file_exists($filename)) {
throw new Exception("Config file not found: $filename");
}
$file = fopen($filename, 'r');
if ($file === false) {
throw new Exception("Cannot open file: $filename");
}
$content = fread($file, filesize($filename));
return $content;
} catch (Exception $e) {
// Handle the exception
error_log($e->getMessage());
return false;
} finally {
// Always executed, even if exception occurs
if ($file !== null) {
fclose($file);
}
echo "Cleanup completed";
}
}
$config = readConfigFile('config.ini');
?>
Tip: The finally block is perfect for cleanup operations like closing files or database connections, as it runs regardless of whether an exception was thrown.
Exception Properties and Methods
Exception objects have useful properties and methods for debugging:
<?php
try {
throw new Exception("Test exception", 100);
} catch (Exception $e) {
echo "Message: " . $e->getMessage() . "\n"; // Error message
echo "Code: " . $e->getCode() . "\n"; // Error code
echo "File: " . $e->getFile() . "\n"; // File where thrown
echo "Line: " . $e->getLine() . "\n"; // Line number
echo "Trace: " . $e->getTraceAsString() . "\n"; // Stack trace
echo "String: " . $e->__toString() . "\n"; // Full exception info
}
// Output:
// Message: Test exception
// Code: 100
// File: /path/to/file.php
// Line: 3
// Trace: #0 {main}
// String: Exception: Test exception in /path/to/file.php:3
?>
Custom Exception Classes
You can create custom exception classes for specific error types:
<?php
// Base custom exception
class DatabaseException extends Exception {}
// Specific database exceptions
class ConnectionException extends DatabaseException {}
class QueryException extends DatabaseException {}
// Usage
class Database {
private $connection;
public function connect($host, $username, $password) {
$this->connection = @mysqli_connect($host, $username, $password);
if (!$this->connection) {
throw new ConnectionException(
"Failed to connect: " . mysqli_connect_error(),
mysqli_connect_errno()
);
}
}
public function query($sql) {
if (!$this->connection) {
throw new ConnectionException("No database connection");
}
$result = mysqli_query($this->connection, $sql);
if ($result === false) {
throw new QueryException(
"Query failed: " . mysqli_error($this->connection)
);
}
return $result;
}
}
// Handling custom exceptions
try {
$db = new Database();
$db->connect('localhost', 'user', 'wrong_password');
$result = $db->query('SELECT * FROM users');
} catch (ConnectionException $e) {
echo "Connection error: " . $e->getMessage();
} catch (QueryException $e) {
echo "Query error: " . $e->getMessage();
} catch (DatabaseException $e) {
echo "Database error: " . $e->getMessage();
}
?>
Note: Catch more specific exceptions before general ones. PHP matches catch blocks in order from top to bottom.
Multiple Catch Blocks
You can catch different exception types with different handlers:
<?php
class ValidationException extends Exception {}
class FileException extends Exception {}
function processUpload($file) {
try {
// Validate file
if (empty($file['name'])) {
throw new ValidationException("No file uploaded");
}
if ($file['size'] > 1000000) {
throw new ValidationException("File too large");
}
// Process file
if (!move_uploaded_file($file['tmp_name'], 'uploads/' . $file['name'])) {
throw new FileException("Failed to move uploaded file");
}
return true;
} catch (ValidationException $e) {
// Handle validation errors - show to user
echo "<div class='error'>" . $e->getMessage() . "</div>";
return false;
} catch (FileException $e) {
// Handle file errors - log and show generic message
error_log($e->getMessage());
echo "<div class='error'>Upload failed. Please try again.</div>";
return false;
}
}
?>
Re-throwing Exceptions
You can catch an exception, perform some action, and then re-throw it:
<?php
function processPayment($amount) {
try {
if ($amount <= 0) {
throw new Exception("Invalid amount");
}
// Process payment logic here
// If payment fails:
throw new Exception("Payment gateway error");
} catch (Exception $e) {
// Log the error
error_log("Payment failed: " . $e->getMessage());
// Re-throw for higher level handling
throw new Exception("Payment processing failed", 0, $e);
}
}
try {
processPayment(-50);
} catch (Exception $e) {
echo "Error: " . $e->getMessage();
// Get previous exception
if ($e->getPrevious()) {
echo "\nOriginal error: " . $e->getPrevious()->getMessage();
}
}
?>
Tip: Re-throwing exceptions is useful when you want to log errors at multiple levels or add context to error messages.
Catching Multiple Exception Types (PHP 7.1+)
You can catch multiple exception types in one catch block:
<?php
class NetworkException extends Exception {}
class TimeoutException extends Exception {}
class PermissionException extends Exception {}
try {
// Some operation that might throw different exceptions
throw new NetworkException("Connection lost");
} catch (NetworkException | TimeoutException $e) {
// Handle network and timeout errors the same way
echo "Connection issue: " . $e->getMessage();
// Retry logic here
} catch (PermissionException $e) {
// Handle permission errors differently
echo "Access denied: " . $e->getMessage();
}
?>
Exception Best Practices
<?php
// 1. Use specific exception types
class UserNotFoundException extends Exception {}
class InvalidEmailException extends Exception {}
// 2. Provide meaningful messages
throw new InvalidEmailException(
"Email '$email' is not valid. Expected format: user@domain.com"
);
// 3. Include error codes for categorization
throw new Exception("Database error", 1001);
// 4. Don't catch exceptions you can't handle
try {
$result = riskyOperation();
} catch (SpecificException $e) {
// Only catch if you can do something useful
handleError($e);
}
// Let other exceptions bubble up
// 5. Always log exceptions in production
try {
criticalOperation();
} catch (Exception $e) {
error_log($e->getMessage() . "\n" . $e->getTraceAsString());
throw $e; // Re-throw after logging
}
?>
Converting Errors to Exceptions
You can convert PHP errors into exceptions for consistent error handling:
<?php
// Error handler that converts errors to exceptions
function errorToException($severity, $message, $file, $line) {
throw new ErrorException($message, 0, $severity, $file, $line);
}
set_error_handler('errorToException');
try {
// This would normally trigger a warning
$result = 10 / 0;
} catch (ErrorException $e) {
echo "Caught error as exception: " . $e->getMessage();
}
restore_error_handler(); // Restore default error handler
?>
Global Exception Handler
Set a global exception handler for uncaught exceptions:
<?php
function globalExceptionHandler($exception) {
// Log the exception
error_log("Uncaught exception: " . $exception->getMessage());
error_log($exception->getTraceAsString());
// Show user-friendly error page
if (php_sapi_name() !== 'cli') {
http_response_code(500);
echo "<h1>Something went wrong</h1>";
echo "<p>We're sorry for the inconvenience. Please try again later.</p>";
} else {
echo "Error: " . $exception->getMessage() . "\n";
}
}
set_exception_handler('globalExceptionHandler');
// Any uncaught exception will be handled by globalExceptionHandler
throw new Exception("This will be caught by global handler");
?>
Warning: Never display detailed exception information (stack traces, file paths) to users in production. Always log details and show generic error messages.
Practical Exception Handling Example
<?php
class UserService {
private $db;
public function registerUser($email, $password) {
try {
// Validate input
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new ValidationException("Invalid email format");
}
if (strlen($password) < 8) {
throw new ValidationException("Password must be at least 8 characters");
}
// Check if user exists
$existing = $this->db->query("SELECT id FROM users WHERE email = ?", [$email]);
if ($existing) {
throw new ValidationException("Email already registered");
}
// Create user
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
$this->db->query(
"INSERT INTO users (email, password) VALUES (?, ?)",
[$email, $hashedPassword]
);
return true;
} catch (ValidationException $e) {
// Show validation errors to user
throw $e;
} catch (DatabaseException $e) {
// Log database errors, show generic message
error_log("Registration failed: " . $e->getMessage());
throw new Exception("Registration failed. Please try again.");
} catch (Exception $e) {
// Catch-all for unexpected errors
error_log("Unexpected error in registerUser: " . $e->getMessage());
throw new Exception("An unexpected error occurred");
}
}
}
// Usage
try {
$userService = new UserService();
$userService->registerUser('user@example.com', 'password123');
echo "Registration successful!";
} catch (Exception $e) {
echo "Error: " . $e->getMessage();
}
?>
Exercise:
- Create a file upload class that throws custom exceptions for different error conditions
- Write a function that reads JSON from a file and throws exceptions for file not found, invalid JSON, etc.
- Implement a calculator class with try-catch blocks that handle division by zero
- Create a global exception handler that logs errors and displays appropriate messages based on environment (development vs production)
Common Exception Patterns
<?php
// Pattern 1: Fail fast with exceptions
function processOrder($orderId) {
if (empty($orderId)) {
throw new InvalidArgumentException("Order ID is required");
}
// Continue processing...
}
// Pattern 2: Exception chaining for context
try {
connectToAPI();
} catch (Exception $e) {
throw new ApiException("Failed to connect to payment API", 0, $e);
}
// Pattern 3: Resource cleanup with finally
function downloadFile($url) {
$handle = null;
try {
$handle = fopen($url, 'r');
return stream_get_contents($handle);
} finally {
if ($handle) {
fclose($handle);
}
}
}
?>